retuve.hip_us.handlers.side

Handles 3D and 2D Sweep Hip Ultrasounds that do not have the Anterior and Posterior sides correctly set.

Retuve is set up to have the Anterior side first and the Posterior side second.

  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 3D and 2D Sweep Hip Ultrasounds that do not have the
 17Anterior and Posterior sides correctly set.
 18
 19Retuve is set up to have the Anterior side first and the Posterior side second.
 20"""
 21
 22from typing import List, Tuple
 23
 24from retuve.classes.seg import SegFrameObjects
 25from retuve.hip_us.classes.enums import HipLabelsUS, Side
 26from retuve.hip_us.classes.general import HipDatasUS, HipDataUS
 27
 28
 29def get_side_metainfo(
 30    hip: HipDataUS, results: List[SegFrameObjects]
 31) -> Tuple[Tuple[int], List[int]]:
 32    """
 33    Get the side metainfo for a HipDataUS object.
 34
 35    :param hip: HipDataUS object.
 36    :param results: List of SegFrameObjects.
 37    """
 38    if not hip.landmarks:
 39        return None, None
 40
 41    left = hip.landmarks.left
 42    apex = hip.landmarks.apex
 43
 44    mid = (left[0] + apex[0]) / 2, (left[1] + apex[1]) / 2
 45
 46    illium = [
 47        seg_obj
 48        for seg_obj in results
 49        if seg_obj.cls == HipLabelsUS.IlliumAndAcetabulum
 50    ]
 51
 52    if len(illium) == 0:
 53        return None, None
 54
 55    illium = illium[0]
 56
 57    illium_midline = illium.midline
 58
 59    # reverse x and y in midline
 60    illium_midline = [(y, x) for x, y in illium_midline]
 61
 62    closest_illium = min(
 63        illium_midline,
 64        key=lambda point: abs(point[0] - mid[0]),
 65    )
 66
 67    return closest_illium, mid
 68
 69
 70def set_side(
 71    hip_datas: HipDatasUS,
 72    results: List[SegFrameObjects],
 73    allow_flipping,
 74) -> Tuple[HipDatasUS, List[SegFrameObjects]]:
 75    """
 76    Set the side for each HipDataUS object in the HipDatasUS object.
 77
 78    Returns the HipDatasUS object and the results with the Anterior side first.
 79
 80    :param hip_datas: HipDatasUS object.
 81    :param results: List of SegFrameObjects.
 82    :param allow_flipping: Boolean indicating if flipping is allowed.
 83
 84    :return: Tuple of HipDatasUS object and List of SegFrameObjects.
 85    """
 86
 87    # split the hip datas into two sides, based on graf frame
 88    front_side = []
 89    back_side = []
 90
 91    for hip_data, seg_frame_objs in zip(hip_datas, results):
 92        if hip_data.landmarks is None or hip_data.landmarks.apex is None:
 93            continue
 94
 95        closest_illium, mid = get_side_metainfo(hip_data, seg_frame_objs)
 96
 97        if closest_illium is None:
 98            continue
 99
100        # Find the y distance between the midline points
101        delta = mid[1] - closest_illium[1]
102
103        if hip_data.frame_no < hip_datas.graf_frame:
104            front_side.append(delta)
105        else:
106            back_side.append(delta)
107
108    for side in [("Front", front_side), ("Back", back_side)]:  # noqa
109        if len(side[1]) == 0:
110            hip_datas.recorded_error.append(f"No {side[0]} Side.")
111            hip_datas.recorded_error.critical = True
112
113    if len(front_side) == 0:
114        front_side = [0]
115    if len(back_side) == 0:
116        back_side = [0]
117
118    if front_side == [0] and back_side == [0]:
119        hip_datas.recorded_error.append("No Side Detected.")
120        return hip_datas, results
121
122    back_side_delta = sum(back_side) / len(back_side)
123    front_side_delta = sum(front_side) / len(front_side)
124
125    flip_frames = False
126    if (back_side_delta > front_side_delta) and allow_flipping:
127        hip_datas.recorded_error.append("Swapped Post and Ant")
128        hip_datas.hip_datas = hip_datas.hip_datas[::-1]
129        results = results[::-1]
130        hip_datas.graf_frame = len(hip_datas) - hip_datas.graf_frame
131        flip_frames = True
132
133    # set the side for each hip data
134    for i, hip_data in enumerate(hip_datas):
135        if flip_frames:
136            hip_data.frame_no = i
137        if hip_data.frame_no < hip_datas.graf_frame:
138            hip_data.side = Side.ANT
139        else:
140            hip_data.side = Side.POST
141
142    return hip_datas, results
def get_side_metainfo( hip: retuve.hip_us.classes.general.HipDataUS, results: List[retuve.classes.seg.SegFrameObjects]) -> Tuple[Tuple[int], List[int]]:
30def get_side_metainfo(
31    hip: HipDataUS, results: List[SegFrameObjects]
32) -> Tuple[Tuple[int], List[int]]:
33    """
34    Get the side metainfo for a HipDataUS object.
35
36    :param hip: HipDataUS object.
37    :param results: List of SegFrameObjects.
38    """
39    if not hip.landmarks:
40        return None, None
41
42    left = hip.landmarks.left
43    apex = hip.landmarks.apex
44
45    mid = (left[0] + apex[0]) / 2, (left[1] + apex[1]) / 2
46
47    illium = [
48        seg_obj
49        for seg_obj in results
50        if seg_obj.cls == HipLabelsUS.IlliumAndAcetabulum
51    ]
52
53    if len(illium) == 0:
54        return None, None
55
56    illium = illium[0]
57
58    illium_midline = illium.midline
59
60    # reverse x and y in midline
61    illium_midline = [(y, x) for x, y in illium_midline]
62
63    closest_illium = min(
64        illium_midline,
65        key=lambda point: abs(point[0] - mid[0]),
66    )
67
68    return closest_illium, mid

Get the side metainfo for a HipDataUS object.

Parameters
  • hip: HipDataUS object.
  • results: List of SegFrameObjects.
 71def set_side(
 72    hip_datas: HipDatasUS,
 73    results: List[SegFrameObjects],
 74    allow_flipping,
 75) -> Tuple[HipDatasUS, List[SegFrameObjects]]:
 76    """
 77    Set the side for each HipDataUS object in the HipDatasUS object.
 78
 79    Returns the HipDatasUS object and the results with the Anterior side first.
 80
 81    :param hip_datas: HipDatasUS object.
 82    :param results: List of SegFrameObjects.
 83    :param allow_flipping: Boolean indicating if flipping is allowed.
 84
 85    :return: Tuple of HipDatasUS object and List of SegFrameObjects.
 86    """
 87
 88    # split the hip datas into two sides, based on graf frame
 89    front_side = []
 90    back_side = []
 91
 92    for hip_data, seg_frame_objs in zip(hip_datas, results):
 93        if hip_data.landmarks is None or hip_data.landmarks.apex is None:
 94            continue
 95
 96        closest_illium, mid = get_side_metainfo(hip_data, seg_frame_objs)
 97
 98        if closest_illium is None:
 99            continue
100
101        # Find the y distance between the midline points
102        delta = mid[1] - closest_illium[1]
103
104        if hip_data.frame_no < hip_datas.graf_frame:
105            front_side.append(delta)
106        else:
107            back_side.append(delta)
108
109    for side in [("Front", front_side), ("Back", back_side)]:  # noqa
110        if len(side[1]) == 0:
111            hip_datas.recorded_error.append(f"No {side[0]} Side.")
112            hip_datas.recorded_error.critical = True
113
114    if len(front_side) == 0:
115        front_side = [0]
116    if len(back_side) == 0:
117        back_side = [0]
118
119    if front_side == [0] and back_side == [0]:
120        hip_datas.recorded_error.append("No Side Detected.")
121        return hip_datas, results
122
123    back_side_delta = sum(back_side) / len(back_side)
124    front_side_delta = sum(front_side) / len(front_side)
125
126    flip_frames = False
127    if (back_side_delta > front_side_delta) and allow_flipping:
128        hip_datas.recorded_error.append("Swapped Post and Ant")
129        hip_datas.hip_datas = hip_datas.hip_datas[::-1]
130        results = results[::-1]
131        hip_datas.graf_frame = len(hip_datas) - hip_datas.graf_frame
132        flip_frames = True
133
134    # set the side for each hip data
135    for i, hip_data in enumerate(hip_datas):
136        if flip_frames:
137            hip_data.frame_no = i
138        if hip_data.frame_no < hip_datas.graf_frame:
139            hip_data.side = Side.ANT
140        else:
141            hip_data.side = Side.POST
142
143    return hip_datas, results

Set the side for each HipDataUS object in the HipDatasUS object.

Returns the HipDatasUS object and the results with the Anterior side first.

Parameters
  • hip_datas: HipDatasUS object.
  • results: List of SegFrameObjects.
  • allow_flipping: Boolean indicating if flipping is allowed.
Returns

Tuple of HipDatasUS object and List of SegFrameObjects.