retuve.hip_us.multiframe.main

High Level Functions for Multiframe Analysis

  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"""
 16High Level Functions for Multiframe Analysis
 17"""
 18
 19import time
 20from typing import List
 21
 22import numpy as np
 23import open3d as o3d
 24from numpy import ndarray as NDArray
 25from plotly.graph_objects import Figure
 26
 27from retuve.classes.seg import SegFrameObjects
 28from retuve.hip_us.classes.enums import Side
 29from retuve.hip_us.classes.general import HipDatasUS, Metric3D
 30from retuve.hip_us.metrics.aca import get_aca
 31from retuve.hip_us.metrics.c_ratio import get_centering_ratio
 32from retuve.hip_us.multiframe.models import (
 33    build_3d_visual,
 34    get_femoral_sphere,
 35    get_illium_mesh,
 36)
 37from retuve.hip_us.typing import CoordinatesArray3D, FemoralHeadSphere
 38from retuve.keyphrases.config import Config
 39from retuve.keyphrases.enums import MetricUS
 40from retuve.logs import ulogger
 41from retuve.utils import rmean
 42
 43
 44class FemSphere:
 45    def __init__(self, center: CoordinatesArray3D, radius: float):
 46        self.center = center
 47        self.radius = radius
 48
 49
 50def get_3d_metrics_and_visuals(
 51    hip_datas: HipDatasUS, results: List[SegFrameObjects], config: Config
 52) -> tuple[
 53    HipDatasUS,
 54    Figure,
 55    FemSphere,
 56    o3d.geometry.TriangleMesh,
 57    NDArray[np.float64],
 58    FemoralHeadSphere,
 59    CoordinatesArray3D,
 60    CoordinatesArray3D,
 61]:
 62    """
 63    Get the 3D metrics and visuals for the hip US module.
 64
 65    :param hip_datas: The hip data.
 66    :param results: The segmentation results.
 67    :param config: The configuration object.
 68
 69    :return: The hip data, the 3D visual, the femoral sphere,
 70             the illium mesh, the apex points, the femoral sphere,
 71             the average normals data of the ACA Vectors, and the
 72             normals data of the ACA Vectors.
 73    """
 74    start = time.time()
 75    illium_mesh, apex_points = get_illium_mesh(hip_datas, results, config)
 76    visual_3d = None
 77
 78    if illium_mesh is None:
 79        hip_datas.recorded_error.append("No Illium Mesh.")
 80        hip_datas.recorded_error.critical = True
 81
 82    femoral_sphere, radius = get_femoral_sphere(hip_datas, config)
 83
 84    if illium_mesh:
 85        aca, avg_normals_data, normals_data, aca_error_msg = get_aca(
 86            illium_mesh,
 87            apex_points,
 88            hip_datas.grafs_hip,
 89            len(hip_datas),
 90            config,
 91        )
 92
 93        hip_datas.recorded_error.append(aca_error_msg)
 94    else:
 95        aca = None
 96        avg_normals_data = None
 97        normals_data = None
 98
 99    if femoral_sphere and illium_mesh:
100        c_ratio, cr_points = get_centering_ratio(
101            illium_mesh, femoral_sphere, hip_datas, config
102        )
103        hip_datas.cr_points = cr_points
104
105    else:
106        c_ratio = 0
107        cr_points = []
108
109    if illium_mesh and femoral_sphere:
110        visual_3d = build_3d_visual(
111            illium_mesh,
112            femoral_sphere,
113            avg_normals_data,
114            normals_data,
115            cr_points,
116            hip_datas,
117            config,
118        )
119
120    if aca and MetricUS.ACA in config.hip.measurements:
121        aca_metric = Metric3D(
122            name=MetricUS.ACA,
123            graf=aca[Side.GRAF],
124            post=aca[Side.POST],
125            ant=aca[Side.ANT],
126        )
127        hip_datas.metrics.append(aca_metric)
128
129    if c_ratio and MetricUS.CENTERING_RATIO in config.hip.measurements:
130        c_ratio = Metric3D(
131            name=MetricUS.CENTERING_RATIO,
132            full=c_ratio,
133        )
134        hip_datas.metrics.append(c_ratio)
135
136    for name in config.hip.measurements:
137        if not any(metric.name == name for metric in hip_datas.metrics):
138            post_values = [
139                hip_data.get_metric(name)
140                for hip_data in hip_datas
141                if hip_data.side == Side.POST
142                and hip_data.get_metric(name) != 0
143            ] or [0]
144            ant_values = [
145                hip_data.get_metric(name)
146                for hip_data in hip_datas
147                if hip_data.side == Side.ANT and hip_data.get_metric(name) != 0
148            ] or [0]
149
150            graf_value = 0
151            if hip_datas.graf_frame:
152                graf_value = hip_datas.grafs_hip.get_metric(name)
153
154            hip_datas.metrics.append(
155                Metric3D(
156                    name=name,
157                    graf=graf_value,
158                    post=rmean(post_values),
159                    ant=rmean(ant_values),
160                )
161            )
162
163    if all(metric.post == 0 for metric in hip_datas.metrics):
164        hip_datas.recorded_error.append("No Posterior values recorded.")
165        hip_datas.recorded_error.critical = True
166
167    if all(metric.ant == 0 for metric in hip_datas.metrics):
168        hip_datas.recorded_error.append("No Anterior values recorded.")
169        hip_datas.recorded_error.critical = True
170    if len(cr_points) != 0:
171        fem_sph = FemSphere(cr_points[0], radius)
172    else:
173        fem_sph = None
174
175    ulogger.info(
176        f"Time for all 3D Elements: {round(time.time() - start, 2)} s"
177    )
178
179    return (
180        hip_datas,
181        visual_3d,
182        fem_sph,
183        illium_mesh,
184        apex_points,
185        femoral_sphere,
186        avg_normals_data,
187        normals_data,
188    )
class FemSphere:
45class FemSphere:
46    def __init__(self, center: CoordinatesArray3D, radius: float):
47        self.center = center
48        self.radius = radius
FemSphere( center: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], radius: float)
46    def __init__(self, center: CoordinatesArray3D, radius: float):
47        self.center = center
48        self.radius = radius
center
radius
def get_3d_metrics_and_visuals( hip_datas: retuve.hip_us.classes.general.HipDatasUS, results: List[retuve.classes.seg.SegFrameObjects], config: retuve.keyphrases.config.Config) -> tuple[retuve.hip_us.classes.general.HipDatasUS, plotly.graph_objs._figure.Figure, FemSphere, open3d.cpu.pybind.geometry.TriangleMesh, numpy.ndarray[numpy.float64], typing.Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]], numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]]:
 51def get_3d_metrics_and_visuals(
 52    hip_datas: HipDatasUS, results: List[SegFrameObjects], config: Config
 53) -> tuple[
 54    HipDatasUS,
 55    Figure,
 56    FemSphere,
 57    o3d.geometry.TriangleMesh,
 58    NDArray[np.float64],
 59    FemoralHeadSphere,
 60    CoordinatesArray3D,
 61    CoordinatesArray3D,
 62]:
 63    """
 64    Get the 3D metrics and visuals for the hip US module.
 65
 66    :param hip_datas: The hip data.
 67    :param results: The segmentation results.
 68    :param config: The configuration object.
 69
 70    :return: The hip data, the 3D visual, the femoral sphere,
 71             the illium mesh, the apex points, the femoral sphere,
 72             the average normals data of the ACA Vectors, and the
 73             normals data of the ACA Vectors.
 74    """
 75    start = time.time()
 76    illium_mesh, apex_points = get_illium_mesh(hip_datas, results, config)
 77    visual_3d = None
 78
 79    if illium_mesh is None:
 80        hip_datas.recorded_error.append("No Illium Mesh.")
 81        hip_datas.recorded_error.critical = True
 82
 83    femoral_sphere, radius = get_femoral_sphere(hip_datas, config)
 84
 85    if illium_mesh:
 86        aca, avg_normals_data, normals_data, aca_error_msg = get_aca(
 87            illium_mesh,
 88            apex_points,
 89            hip_datas.grafs_hip,
 90            len(hip_datas),
 91            config,
 92        )
 93
 94        hip_datas.recorded_error.append(aca_error_msg)
 95    else:
 96        aca = None
 97        avg_normals_data = None
 98        normals_data = None
 99
100    if femoral_sphere and illium_mesh:
101        c_ratio, cr_points = get_centering_ratio(
102            illium_mesh, femoral_sphere, hip_datas, config
103        )
104        hip_datas.cr_points = cr_points
105
106    else:
107        c_ratio = 0
108        cr_points = []
109
110    if illium_mesh and femoral_sphere:
111        visual_3d = build_3d_visual(
112            illium_mesh,
113            femoral_sphere,
114            avg_normals_data,
115            normals_data,
116            cr_points,
117            hip_datas,
118            config,
119        )
120
121    if aca and MetricUS.ACA in config.hip.measurements:
122        aca_metric = Metric3D(
123            name=MetricUS.ACA,
124            graf=aca[Side.GRAF],
125            post=aca[Side.POST],
126            ant=aca[Side.ANT],
127        )
128        hip_datas.metrics.append(aca_metric)
129
130    if c_ratio and MetricUS.CENTERING_RATIO in config.hip.measurements:
131        c_ratio = Metric3D(
132            name=MetricUS.CENTERING_RATIO,
133            full=c_ratio,
134        )
135        hip_datas.metrics.append(c_ratio)
136
137    for name in config.hip.measurements:
138        if not any(metric.name == name for metric in hip_datas.metrics):
139            post_values = [
140                hip_data.get_metric(name)
141                for hip_data in hip_datas
142                if hip_data.side == Side.POST
143                and hip_data.get_metric(name) != 0
144            ] or [0]
145            ant_values = [
146                hip_data.get_metric(name)
147                for hip_data in hip_datas
148                if hip_data.side == Side.ANT and hip_data.get_metric(name) != 0
149            ] or [0]
150
151            graf_value = 0
152            if hip_datas.graf_frame:
153                graf_value = hip_datas.grafs_hip.get_metric(name)
154
155            hip_datas.metrics.append(
156                Metric3D(
157                    name=name,
158                    graf=graf_value,
159                    post=rmean(post_values),
160                    ant=rmean(ant_values),
161                )
162            )
163
164    if all(metric.post == 0 for metric in hip_datas.metrics):
165        hip_datas.recorded_error.append("No Posterior values recorded.")
166        hip_datas.recorded_error.critical = True
167
168    if all(metric.ant == 0 for metric in hip_datas.metrics):
169        hip_datas.recorded_error.append("No Anterior values recorded.")
170        hip_datas.recorded_error.critical = True
171    if len(cr_points) != 0:
172        fem_sph = FemSphere(cr_points[0], radius)
173    else:
174        fem_sph = None
175
176    ulogger.info(
177        f"Time for all 3D Elements: {round(time.time() - start, 2)} s"
178    )
179
180    return (
181        hip_datas,
182        visual_3d,
183        fem_sph,
184        illium_mesh,
185        apex_points,
186        femoral_sphere,
187        avg_normals_data,
188        normals_data,
189    )

Get the 3D metrics and visuals for the hip US module.

Parameters
  • hip_datas: The hip data.
  • results: The segmentation results.
  • config: The configuration object.
Returns

The hip data, the 3D visual, the femoral sphere, the illium mesh, the apex points, the femoral sphere, the average normals data of the ACA Vectors, and the normals data of the ACA Vectors.