retuve.funcs

Contains the high-level functions that are used to run the Retuve pipeline.

  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"""
 16Contains the high-level functions that are used to run the Retuve pipeline.
 17"""
 18
 19import copy
 20import time
 21from typing import Any, BinaryIO, Callable, Dict, List, Tuple, Union
 22
 23import pydicom
 24from moviepy.video.io.ImageSequenceClip import ImageSequenceClip
 25from PIL import Image
 26from plotly.graph_objs import Figure
 27from radstract.data.dicom import convert_dicom_to_images
 28from radstract.data.nifti import NIFTI, convert_images_to_nifti_labels
 29
 30from retuve.classes.draw import Overlay
 31from retuve.classes.metrics import Metric2D, Metric3D
 32from retuve.classes.seg import SegFrameObjects
 33from retuve.hip_us.classes.dev import DevMetricsUS
 34from retuve.hip_us.classes.general import HipDatasUS, HipDataUS
 35from retuve.hip_us.draw import draw_hips_us, draw_table
 36from retuve.hip_us.handlers.bad_data import handle_bad_frames
 37from retuve.hip_us.handlers.side import set_side
 38from retuve.hip_us.metrics.dev import get_dev_metrics
 39from retuve.hip_us.modes.landmarks import landmarks_2_metrics_us
 40from retuve.hip_us.modes.seg import pre_process_segs_us, segs_2_landmarks_us
 41from retuve.hip_us.multiframe import (find_graf_plane,
 42                                      get_3d_metrics_and_visuals)
 43from retuve.hip_xray.classes import DevMetricsXRay, HipDataXray, LandmarksXRay
 44from retuve.hip_xray.draw import draw_hips_xray
 45from retuve.hip_xray.landmarks import landmarks_2_metrics_xray
 46from retuve.keyphrases.config import Config, OperationType
 47from retuve.keyphrases.enums import HipMode
 48from retuve.logs import ulogger
 49from retuve.typehints import GeneralModeFuncType
 50
 51
 52def get_fps(no_of_frames: int, min_fps=30, min_vid_length=6) -> int:
 53    """
 54    Get the frames per second for the video clip.
 55
 56    Should be min_fps or number of fps to produce 6 min_vid_length of video.
 57
 58    :param no_of_frames: The number of frames.
 59    :param min_fps: The minimum frames per second.
 60    :param min_vid_length: The minimum video length.
 61
 62    :return: The frames per second.
 63    """
 64
 65    fps = (
 66        min_fps
 67        if no_of_frames > (min_fps * min_vid_length)
 68        else no_of_frames // min_vid_length
 69    )
 70
 71    return fps if fps > 0 else 1
 72
 73
 74def process_landmarks_xray(
 75    config: Config,
 76    landmark_results: List[LandmarksXRay],
 77    seg_results: List[SegFrameObjects],
 78) -> Tuple[List[HipDataXray], List[Image.Image]]:
 79    """
 80    Process the landmarks for the xray.
 81
 82    :param config: The configuration.
 83    :param landmark_results: The landmark results.
 84    :param seg_results: The segmentation results.
 85
 86    :return: The hip datas and the image arrays.
 87    """
 88    hip_datas_xray = landmarks_2_metrics_xray(landmark_results, config)
 89    image_arrays = draw_hips_xray(hip_datas_xray, seg_results, config)
 90    return hip_datas_xray, image_arrays
 91
 92
 93def process_segs_us(
 94    config: Config,
 95    file: BinaryIO,
 96    modes_func: Callable[
 97        [BinaryIO, Union[str, Config], Dict[str, Any]],
 98        List[SegFrameObjects],
 99    ],
100    modes_func_kwargs_dict: Dict[str, Any],
101) -> Tuple[HipDatasUS, List[SegFrameObjects], Tuple[int, int, int]]:
102    """
103    Process the segmentation for the 3DUS.
104
105    :param config: The configuration.
106    :param file: The file.
107    :param modes_func: The mode function.
108    :param modes_func_kwargs_dict: The mode function kwargs.
109
110    :return: The hip datas, the results, and the shape.
111    """
112
113    results: List[SegFrameObjects] = modes_func(
114        file, config, **modes_func_kwargs_dict
115    )
116    results, shape = pre_process_segs_us(results, config)
117    pre_edited_results = copy.deepcopy(results)
118    landmarks, all_seg_rejection_reasons = segs_2_landmarks_us(results, config)
119    pre_edited_landmarks = copy.deepcopy(landmarks)
120    hip_datas = landmarks_2_metrics_us(landmarks, shape, config)
121    hip_datas.all_seg_rejection_reasons = all_seg_rejection_reasons
122
123    if config.test_data_passthrough:
124        hip_datas.pre_edited_results = pre_edited_results
125        hip_datas.pre_edited_landmarks = pre_edited_landmarks
126        hip_datas.pre_edited_hip_datas = copy.deepcopy(hip_datas)
127
128    return hip_datas, results, shape
129
130
131def analyse_hip_xray_2D(
132    img: Image.Image,
133    keyphrase: Union[str, Config],
134    modes_func: Callable[
135        [Image.Image, str, Dict[str, Any]],
136        Tuple[List[LandmarksXRay], List[SegFrameObjects]],
137    ],
138    modes_func_kwargs_dict: Dict[str, Any],
139) -> Tuple[HipDataXray, Image.Image, DevMetricsXRay]:
140    """
141    Analyze the hip for the xray.
142
143    :param img: The image.
144    :param keyphrase: The keyphrase.
145    :param modes_func: The mode function.
146    :param modes_func_kwargs_dict: The mode function kwargs.
147
148    :return: The hip, the image, and the dev metrics.
149    """
150    if isinstance(keyphrase, str):
151        config = Config.get_config(keyphrase)
152    else:
153        config = keyphrase
154
155    if config.operation_type in OperationType.LANDMARK:
156        landmark_results, seg_results = modes_func(
157            [img], keyphrase, **modes_func_kwargs_dict
158        )
159        hip_datas, image_arrays = process_landmarks_xray(
160            config, landmark_results, seg_results
161        )
162
163    img = image_arrays[0]
164    img = Image.fromarray(img)
165    hip = hip_datas[0]
166
167    if config.test_data_passthrough:
168        hip.seg_results = seg_results
169
170    return hip, img, DevMetricsXRay()
171
172
173def analyze_synthetic_xray(
174    dcm: pydicom.FileDataset,
175    keyphrase: Union[str, Config],
176    modes_func: Callable[
177        [pydicom.FileDataset, str, Dict[str, Any]],
178        Tuple[List[LandmarksXRay], List[SegFrameObjects]],
179    ],
180    modes_func_kwargs_dict: Dict[str, Any],
181) -> NIFTI:
182    """
183    NOTE: Experimental function.
184
185    Useful if the xray images are stacked in a single DICOM file.
186
187    Analyze the hip for the xray.
188
189    :param dcm: The DICOM file.
190    :param keyphrase: The keyphrase.
191    :param modes_func: The mode function.
192    :param modes_func_kwargs_dict: The mode function kwargs.
193
194    :return: The nifti segmentation file
195    """
196    if isinstance(keyphrase, str):
197        config = Config.get_config(keyphrase)
198    else:
199        config = keyphrase
200
201    images = convert_dicom_to_images(dcm)
202    nifti_frames = []
203
204    try:
205        if config.operation_type in OperationType.LANDMARK:
206            landmark_results, seg_results = modes_func(
207                images, keyphrase, **modes_func_kwargs_dict
208            )
209            hip_datas, image_arrays = process_landmarks_xray(
210                config, landmark_results, seg_results
211            )
212    except Exception as e:
213        ulogger.error(f"Critical Error: {e}")
214        return None
215
216    for hip, seg_frame_objs in zip(hip_datas, seg_results):
217        shape = seg_frame_objs.img.shape
218
219        overlay = Overlay((shape[0], shape[1], 3), config)
220        test = overlay.get_nifti_frame(seg_frame_objs, shape)
221        nifti_frames.append(test)
222
223    # Convert to NIfTI
224    nifti = convert_images_to_nifti_labels(nifti_frames)
225
226    return nifti
227
228
229def analyse_hip_3DUS(
230    dcm: pydicom.FileDataset,
231    keyphrase: Union[str, Config],
232    modes_func: Callable[
233        [pydicom.FileDataset, str, Dict[str, Any]],
234        List[SegFrameObjects],
235    ],
236    modes_func_kwargs_dict: Dict[str, Any],
237) -> Tuple[
238    HipDatasUS,
239    ImageSequenceClip,
240    Figure,
241    Union[DevMetricsXRay, DevMetricsUS],
242]:
243    """
244    Analyze a 3D Ultrasound Hip
245
246    :param dcm: The DICOM file.
247    :param keyphrase: The keyphrase.
248    :param modes_func: The mode function.
249    :param modes_func_kwargs_dict: The mode function kwargs.
250
251    :return: The hip datas, the video clip, the 3D visual, and the dev metrics.
252    """
253    start = time.time()
254
255    config = Config.get_config(keyphrase)
256    hip_datas = HipDatasUS()
257
258    file_id = modes_func_kwargs_dict.get("file_id")
259    if file_id:
260        del modes_func_kwargs_dict["file_id"]
261
262    try:
263        if config.operation_type == OperationType.SEG:
264            hip_datas, results, shape = process_segs_us(
265                config, dcm, modes_func, modes_func_kwargs_dict
266            )
267        elif config.operation_type == OperationType.LANDMARK:
268            raise NotImplementedError(
269                "This is not yet supported. Please use the seg operation type."
270            )
271    except Exception as e:
272        ulogger.error(f"Critical Error: {e}")
273        return None, None, None, None
274
275    hip_datas = handle_bad_frames(hip_datas, config)
276
277    if not any(hip.metrics for hip in hip_datas):
278        dcm_patient = dcm.get("PatientID", "Unknown")
279        ulogger.error(f"No metrics were found in the DICOM {dcm_patient}.")
280
281    hip_datas.file_id = file_id
282    hip_datas = find_graf_plane(hip_datas, results, config=config)
283
284    hip_datas, results = set_side(
285        hip_datas, results, config.hip.allow_flipping
286    )
287
288    (
289        hip_datas,
290        visual_3d,
291        fem_sph,
292        illium_mesh,
293        apex_points,
294        femoral_sphere,
295        avg_normals_data,
296        normals_data,
297    ) = get_3d_metrics_and_visuals(hip_datas, results, config)
298
299    image_arrays, nifti = draw_hips_us(hip_datas, results, fem_sph, config)
300
301    if config.seg_export:
302        hip_datas.nifti = nifti
303
304    hip_datas = get_dev_metrics(hip_datas, results, config)
305
306    data_image = draw_table(shape, hip_datas)
307    image_arrays.append(data_image)
308
309    ulogger.info(f"Total 3DUS time: {time.time() - start:.2f}s")
310
311    fps = get_fps(
312        len(image_arrays),
313        config.visuals.min_vid_fps,
314        config.visuals.min_vid_length,
315    )
316
317    video_clip = ImageSequenceClip(
318        image_arrays,
319        fps=fps,
320    )
321
322    if config.test_data_passthrough:
323        hip_datas.illium_mesh = illium_mesh
324        hip_datas.fem_sph = fem_sph
325        hip_datas.results = results
326        hip_datas.apex_points = apex_points
327        hip_datas.femoral_sphere = femoral_sphere
328        hip_datas.avg_normals_data = avg_normals_data
329        hip_datas.normals_data = normals_data
330
331    return (
332        hip_datas,
333        video_clip,
334        visual_3d,
335        hip_datas.dev_metrics,
336    )
337
338
339def analyse_hip_2DUS(
340    img: Image.Image,
341    keyphrase: Union[str, Config],
342    modes_func: Callable[
343        [Image.Image, str, Dict[str, Any]],
344        List[SegFrameObjects],
345    ],
346    modes_func_kwargs_dict: Dict[str, Any],
347    return_seg_info: bool = False,
348) -> Tuple[HipDataUS, Image.Image, DevMetricsUS]:
349    """
350    Analyze a 2D Ultrasound Hip
351
352    :param img: The image.
353    :param keyphrase: The keyphrase.
354    :param modes_func: The mode function.
355    :param modes_func_kwargs_dict: The mode function kwargs.
356
357    :return: The hip, the image, and the dev metrics.
358    """
359    config = Config.get_config(keyphrase)
360
361    try:
362        if config.operation_type in OperationType.SEG:
363            hip_datas, results, _ = process_segs_us(
364                config, [img], modes_func, modes_func_kwargs_dict
365            )
366    except Exception as e:
367        ulogger.error(f"Critical Error: {e}")
368        return None, None, None
369
370    image_arrays, _ = draw_hips_us(hip_datas, results, None, config)
371
372    hip_datas = get_dev_metrics(hip_datas, results, config)
373
374    image = image_arrays[0]
375    hip = hip_datas[0]
376
377    image = Image.fromarray(image)
378
379    if return_seg_info:
380        hip.seg_info = results
381
382    return hip, image, hip_datas.dev_metrics
383
384
385def analyse_hip_2DUS_sweep(
386    dcm: pydicom.FileDataset,
387    keyphrase: Union[str, Config],
388    modes_func: Callable[
389        [pydicom.FileDataset, str, Dict[str, Any]],
390        List[SegFrameObjects],
391    ],
392    modes_func_kwargs_dict: Dict[str, Any],
393) -> Tuple[
394    HipDataUS,
395    Image.Image,
396    DevMetricsUS,
397    ImageSequenceClip,
398]:
399    """
400    Analyze a 2D Sweep Ultrasound Hip
401
402    :param dcm: The DICOM file.
403    :param keyphrase: The keyphrase.
404    :param modes_func: The mode function.
405    :param modes_func_kwargs_dict: The mode function kwargs.
406
407    :return: The hip, the image, the dev metrics, and the video clip.
408    """
409
410    config = Config.get_config(keyphrase)
411    hip_datas = HipDatasUS()
412
413    try:
414        if config.operation_type == OperationType.SEG:
415            hip_datas, results, shape = process_segs_us(
416                config, dcm, modes_func, modes_func_kwargs_dict
417            )
418        elif config.operation_type == OperationType.LANDMARK:
419            raise NotImplementedError(
420                "This is not yet supported. Please use the seg operation type."
421            )
422    except Exception as e:
423        ulogger.error(f"Critical Error: {e}")
424        return None, None, None, None
425
426    hip_datas = handle_bad_frames(hip_datas, config)
427    hip_datas = find_graf_plane(hip_datas, results, config)
428
429    graf_hip = hip_datas.grafs_hip
430    graf_frame = hip_datas.graf_frame
431
432    image_arrays, _ = draw_hips_us(hip_datas, results, None, config)
433
434    hip_datas = get_dev_metrics(hip_datas, results, config)
435
436    if graf_frame is not None:
437        graf_image = image_arrays[graf_frame]
438        graf_image = Image.fromarray(graf_image)
439
440        image_arrays = (
441            [image_arrays[graf_frame]] * int(len(image_arrays) * 0.1)
442            + image_arrays
443            + [image_arrays[graf_frame]] * int(len(image_arrays) * 0.1)
444        )
445    else:
446        graf_image = None
447
448    video_clip = ImageSequenceClip(
449        image_arrays,
450        fps=get_fps(
451            len(image_arrays),
452            config.visuals.min_vid_fps,
453            config.visuals.min_vid_length,
454        ),
455    )
456
457    return graf_hip, graf_image, hip_datas.dev_metrics, video_clip
458
459
460class RetuveResult:
461    """
462    The standardised result of the Retuve pipeline.
463
464    :attr hip_datas: The hip datas.
465    :attr hip: The hip.
466    :attr image: The saved image, if any.
467    :attr metrics: The metrics.
468    :attr video_clip: The video clip, if any.
469    :attr visual_3d: The 3D visual, if any.
470    """
471
472    def __init__(
473        self,
474        metrics: Union[List[Metric2D], List[Metric3D]],
475        hip_datas: Union[HipDatasUS, List[HipDataXray]] = None,
476        hip: Union[HipDataXray, HipDataUS] = None,
477        image: Image.Image = None,
478        video_clip: ImageSequenceClip = None,
479        visual_3d: Figure = None,
480    ):
481        self.hip_datas = hip_datas
482        self.hip = hip
483        self.image = image
484        self.metrics = metrics
485        self.video_clip = video_clip
486        self.visual_3d = visual_3d
487
488
489def retuve_run(
490    hip_mode: HipMode,
491    config: Config,
492    modes_func: GeneralModeFuncType,
493    modes_func_kwargs_dict: Dict[str, Any],
494    file: str,
495) -> RetuveResult:
496    """
497    Run the Retuve pipeline with standardised inputs and outputs.
498
499    :param hip_mode: The hip mode.
500    :param config: The configuration.
501    :param modes_func: The mode function.
502    :param modes_func_kwargs_dict: The mode function kwargs.
503    :param file: The file.
504
505    :return: The Retuve result standardised output.
506    """
507    always_dcm = (
508        len(config.batch.input_types) == 1
509        and ".dcm" in config.batch.input_types
510    )
511
512    if always_dcm or (
513        file.endswith(".dcm") and ".dcm" in config.batch.input_types
514    ):
515        file = pydicom.dcmread(file)
516
517    if hip_mode == HipMode.XRAY:
518        file = Image.open(file)
519        hip, image, dev_metrics = analyse_hip_xray_2D(
520            file, config, modes_func, modes_func_kwargs_dict
521        )
522        return RetuveResult(
523            hip.json_dump(config, dev_metrics), image=image, hip=hip
524        )
525    elif hip_mode == HipMode.US3D:
526        hip_datas, video_clip, visual_3d, dev_metrics = analyse_hip_3DUS(
527            file, config, modes_func, modes_func_kwargs_dict
528        )
529        return RetuveResult(
530            hip_datas.json_dump(config),
531            hip_datas=hip_datas,
532            video_clip=video_clip,
533            visual_3d=visual_3d,
534        )
535    elif hip_mode == HipMode.US2D:
536        file = Image.open(file).convert("RGB")
537        hip, image, dev_metrics = analyse_hip_2DUS(
538            file, config, modes_func, modes_func_kwargs_dict
539        )
540        return RetuveResult(
541            hip.json_dump(config, dev_metrics), hip=hip, image=image
542        )
543    elif hip_mode == HipMode.US2DSW:
544        hip, image, dev_metrics, video_clip = analyse_hip_2DUS_sweep(
545            file, config, modes_func, modes_func_kwargs_dict
546        )
547        json_dump = None
548        if hip:
549            json_dump = hip.json_dump(config, dev_metrics)
550
551        return RetuveResult(
552            json_dump,
553            hip=hip,
554            image=image,
555            video_clip=video_clip,
556        )
557    else:
558        raise ValueError(f"Invalid hip_mode. {hip_mode}")
def get_fps(no_of_frames: int, min_fps=30, min_vid_length=6) -> int:
53def get_fps(no_of_frames: int, min_fps=30, min_vid_length=6) -> int:
54    """
55    Get the frames per second for the video clip.
56
57    Should be min_fps or number of fps to produce 6 min_vid_length of video.
58
59    :param no_of_frames: The number of frames.
60    :param min_fps: The minimum frames per second.
61    :param min_vid_length: The minimum video length.
62
63    :return: The frames per second.
64    """
65
66    fps = (
67        min_fps
68        if no_of_frames > (min_fps * min_vid_length)
69        else no_of_frames // min_vid_length
70    )
71
72    return fps if fps > 0 else 1

Get the frames per second for the video clip.

Should be min_fps or number of fps to produce 6 min_vid_length of video.

Parameters
  • no_of_frames: The number of frames.
  • min_fps: The minimum frames per second.
  • min_vid_length: The minimum video length.
Returns

The frames per second.

def process_landmarks_xray( config: retuve.keyphrases.config.Config, landmark_results: List[retuve.hip_xray.classes.LandmarksXRay], seg_results: List[retuve.classes.seg.SegFrameObjects]) -> Tuple[List[retuve.hip_xray.classes.HipDataXray], List[PIL.Image.Image]]:
75def process_landmarks_xray(
76    config: Config,
77    landmark_results: List[LandmarksXRay],
78    seg_results: List[SegFrameObjects],
79) -> Tuple[List[HipDataXray], List[Image.Image]]:
80    """
81    Process the landmarks for the xray.
82
83    :param config: The configuration.
84    :param landmark_results: The landmark results.
85    :param seg_results: The segmentation results.
86
87    :return: The hip datas and the image arrays.
88    """
89    hip_datas_xray = landmarks_2_metrics_xray(landmark_results, config)
90    image_arrays = draw_hips_xray(hip_datas_xray, seg_results, config)
91    return hip_datas_xray, image_arrays

Process the landmarks for the xray.

Parameters
  • config: The configuration.
  • landmark_results: The landmark results.
  • seg_results: The segmentation results.
Returns

The hip datas and the image arrays.

def process_segs_us( config: retuve.keyphrases.config.Config, file: <class 'BinaryIO'>, modes_func: Callable[[BinaryIO, Union[str, retuve.keyphrases.config.Config], Dict[str, Any]], List[retuve.classes.seg.SegFrameObjects]], modes_func_kwargs_dict: Dict[str, Any]) -> Tuple[retuve.hip_us.classes.general.HipDatasUS, List[retuve.classes.seg.SegFrameObjects], Tuple[int, int, int]]:
 94def process_segs_us(
 95    config: Config,
 96    file: BinaryIO,
 97    modes_func: Callable[
 98        [BinaryIO, Union[str, Config], Dict[str, Any]],
 99        List[SegFrameObjects],
100    ],
101    modes_func_kwargs_dict: Dict[str, Any],
102) -> Tuple[HipDatasUS, List[SegFrameObjects], Tuple[int, int, int]]:
103    """
104    Process the segmentation for the 3DUS.
105
106    :param config: The configuration.
107    :param file: The file.
108    :param modes_func: The mode function.
109    :param modes_func_kwargs_dict: The mode function kwargs.
110
111    :return: The hip datas, the results, and the shape.
112    """
113
114    results: List[SegFrameObjects] = modes_func(
115        file, config, **modes_func_kwargs_dict
116    )
117    results, shape = pre_process_segs_us(results, config)
118    pre_edited_results = copy.deepcopy(results)
119    landmarks, all_seg_rejection_reasons = segs_2_landmarks_us(results, config)
120    pre_edited_landmarks = copy.deepcopy(landmarks)
121    hip_datas = landmarks_2_metrics_us(landmarks, shape, config)
122    hip_datas.all_seg_rejection_reasons = all_seg_rejection_reasons
123
124    if config.test_data_passthrough:
125        hip_datas.pre_edited_results = pre_edited_results
126        hip_datas.pre_edited_landmarks = pre_edited_landmarks
127        hip_datas.pre_edited_hip_datas = copy.deepcopy(hip_datas)
128
129    return hip_datas, results, shape

Process the segmentation for the 3DUS.

Parameters
  • config: The configuration.
  • file: The file.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The hip datas, the results, and the shape.

def analyse_hip_xray_2D( img: PIL.Image.Image, keyphrase: Union[str, retuve.keyphrases.config.Config], modes_func: Callable[[PIL.Image.Image, str, Dict[str, Any]], Tuple[List[retuve.hip_xray.classes.LandmarksXRay], List[retuve.classes.seg.SegFrameObjects]]], modes_func_kwargs_dict: Dict[str, Any]) -> Tuple[retuve.hip_xray.classes.HipDataXray, PIL.Image.Image, retuve.hip_xray.classes.DevMetricsXRay]:
132def analyse_hip_xray_2D(
133    img: Image.Image,
134    keyphrase: Union[str, Config],
135    modes_func: Callable[
136        [Image.Image, str, Dict[str, Any]],
137        Tuple[List[LandmarksXRay], List[SegFrameObjects]],
138    ],
139    modes_func_kwargs_dict: Dict[str, Any],
140) -> Tuple[HipDataXray, Image.Image, DevMetricsXRay]:
141    """
142    Analyze the hip for the xray.
143
144    :param img: The image.
145    :param keyphrase: The keyphrase.
146    :param modes_func: The mode function.
147    :param modes_func_kwargs_dict: The mode function kwargs.
148
149    :return: The hip, the image, and the dev metrics.
150    """
151    if isinstance(keyphrase, str):
152        config = Config.get_config(keyphrase)
153    else:
154        config = keyphrase
155
156    if config.operation_type in OperationType.LANDMARK:
157        landmark_results, seg_results = modes_func(
158            [img], keyphrase, **modes_func_kwargs_dict
159        )
160        hip_datas, image_arrays = process_landmarks_xray(
161            config, landmark_results, seg_results
162        )
163
164    img = image_arrays[0]
165    img = Image.fromarray(img)
166    hip = hip_datas[0]
167
168    if config.test_data_passthrough:
169        hip.seg_results = seg_results
170
171    return hip, img, DevMetricsXRay()

Analyze the hip for the xray.

Parameters
  • img: The image.
  • keyphrase: The keyphrase.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The hip, the image, and the dev metrics.

def analyze_synthetic_xray( dcm: pydicom.dataset.FileDataset, keyphrase: Union[str, retuve.keyphrases.config.Config], modes_func: Callable[[pydicom.dataset.FileDataset, str, Dict[str, Any]], Tuple[List[retuve.hip_xray.classes.LandmarksXRay], List[retuve.classes.seg.SegFrameObjects]]], modes_func_kwargs_dict: Dict[str, Any]) -> radstract.data.nifti.main.NIFTI:
174def analyze_synthetic_xray(
175    dcm: pydicom.FileDataset,
176    keyphrase: Union[str, Config],
177    modes_func: Callable[
178        [pydicom.FileDataset, str, Dict[str, Any]],
179        Tuple[List[LandmarksXRay], List[SegFrameObjects]],
180    ],
181    modes_func_kwargs_dict: Dict[str, Any],
182) -> NIFTI:
183    """
184    NOTE: Experimental function.
185
186    Useful if the xray images are stacked in a single DICOM file.
187
188    Analyze the hip for the xray.
189
190    :param dcm: The DICOM file.
191    :param keyphrase: The keyphrase.
192    :param modes_func: The mode function.
193    :param modes_func_kwargs_dict: The mode function kwargs.
194
195    :return: The nifti segmentation file
196    """
197    if isinstance(keyphrase, str):
198        config = Config.get_config(keyphrase)
199    else:
200        config = keyphrase
201
202    images = convert_dicom_to_images(dcm)
203    nifti_frames = []
204
205    try:
206        if config.operation_type in OperationType.LANDMARK:
207            landmark_results, seg_results = modes_func(
208                images, keyphrase, **modes_func_kwargs_dict
209            )
210            hip_datas, image_arrays = process_landmarks_xray(
211                config, landmark_results, seg_results
212            )
213    except Exception as e:
214        ulogger.error(f"Critical Error: {e}")
215        return None
216
217    for hip, seg_frame_objs in zip(hip_datas, seg_results):
218        shape = seg_frame_objs.img.shape
219
220        overlay = Overlay((shape[0], shape[1], 3), config)
221        test = overlay.get_nifti_frame(seg_frame_objs, shape)
222        nifti_frames.append(test)
223
224    # Convert to NIfTI
225    nifti = convert_images_to_nifti_labels(nifti_frames)
226
227    return nifti

NOTE: Experimental function.

Useful if the xray images are stacked in a single DICOM file.

Analyze the hip for the xray.

Parameters
  • dcm: The DICOM file.
  • keyphrase: The keyphrase.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The nifti segmentation file

def analyse_hip_3DUS( dcm: pydicom.dataset.FileDataset, keyphrase: Union[str, retuve.keyphrases.config.Config], modes_func: Callable[[pydicom.dataset.FileDataset, str, Dict[str, Any]], List[retuve.classes.seg.SegFrameObjects]], modes_func_kwargs_dict: Dict[str, Any]) -> Tuple[retuve.hip_us.classes.general.HipDatasUS, moviepy.video.io.ImageSequenceClip.ImageSequenceClip, plotly.graph_objs._figure.Figure, Union[retuve.hip_xray.classes.DevMetricsXRay, retuve.hip_us.classes.dev.DevMetricsUS]]:
230def analyse_hip_3DUS(
231    dcm: pydicom.FileDataset,
232    keyphrase: Union[str, Config],
233    modes_func: Callable[
234        [pydicom.FileDataset, str, Dict[str, Any]],
235        List[SegFrameObjects],
236    ],
237    modes_func_kwargs_dict: Dict[str, Any],
238) -> Tuple[
239    HipDatasUS,
240    ImageSequenceClip,
241    Figure,
242    Union[DevMetricsXRay, DevMetricsUS],
243]:
244    """
245    Analyze a 3D Ultrasound Hip
246
247    :param dcm: The DICOM file.
248    :param keyphrase: The keyphrase.
249    :param modes_func: The mode function.
250    :param modes_func_kwargs_dict: The mode function kwargs.
251
252    :return: The hip datas, the video clip, the 3D visual, and the dev metrics.
253    """
254    start = time.time()
255
256    config = Config.get_config(keyphrase)
257    hip_datas = HipDatasUS()
258
259    file_id = modes_func_kwargs_dict.get("file_id")
260    if file_id:
261        del modes_func_kwargs_dict["file_id"]
262
263    try:
264        if config.operation_type == OperationType.SEG:
265            hip_datas, results, shape = process_segs_us(
266                config, dcm, modes_func, modes_func_kwargs_dict
267            )
268        elif config.operation_type == OperationType.LANDMARK:
269            raise NotImplementedError(
270                "This is not yet supported. Please use the seg operation type."
271            )
272    except Exception as e:
273        ulogger.error(f"Critical Error: {e}")
274        return None, None, None, None
275
276    hip_datas = handle_bad_frames(hip_datas, config)
277
278    if not any(hip.metrics for hip in hip_datas):
279        dcm_patient = dcm.get("PatientID", "Unknown")
280        ulogger.error(f"No metrics were found in the DICOM {dcm_patient}.")
281
282    hip_datas.file_id = file_id
283    hip_datas = find_graf_plane(hip_datas, results, config=config)
284
285    hip_datas, results = set_side(
286        hip_datas, results, config.hip.allow_flipping
287    )
288
289    (
290        hip_datas,
291        visual_3d,
292        fem_sph,
293        illium_mesh,
294        apex_points,
295        femoral_sphere,
296        avg_normals_data,
297        normals_data,
298    ) = get_3d_metrics_and_visuals(hip_datas, results, config)
299
300    image_arrays, nifti = draw_hips_us(hip_datas, results, fem_sph, config)
301
302    if config.seg_export:
303        hip_datas.nifti = nifti
304
305    hip_datas = get_dev_metrics(hip_datas, results, config)
306
307    data_image = draw_table(shape, hip_datas)
308    image_arrays.append(data_image)
309
310    ulogger.info(f"Total 3DUS time: {time.time() - start:.2f}s")
311
312    fps = get_fps(
313        len(image_arrays),
314        config.visuals.min_vid_fps,
315        config.visuals.min_vid_length,
316    )
317
318    video_clip = ImageSequenceClip(
319        image_arrays,
320        fps=fps,
321    )
322
323    if config.test_data_passthrough:
324        hip_datas.illium_mesh = illium_mesh
325        hip_datas.fem_sph = fem_sph
326        hip_datas.results = results
327        hip_datas.apex_points = apex_points
328        hip_datas.femoral_sphere = femoral_sphere
329        hip_datas.avg_normals_data = avg_normals_data
330        hip_datas.normals_data = normals_data
331
332    return (
333        hip_datas,
334        video_clip,
335        visual_3d,
336        hip_datas.dev_metrics,
337    )

Analyze a 3D Ultrasound Hip

Parameters
  • dcm: The DICOM file.
  • keyphrase: The keyphrase.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The hip datas, the video clip, the 3D visual, and the dev metrics.

def analyse_hip_2DUS( img: PIL.Image.Image, keyphrase: Union[str, retuve.keyphrases.config.Config], modes_func: Callable[[PIL.Image.Image, str, Dict[str, Any]], List[retuve.classes.seg.SegFrameObjects]], modes_func_kwargs_dict: Dict[str, Any], return_seg_info: bool = False) -> Tuple[retuve.hip_us.classes.general.HipDataUS, PIL.Image.Image, retuve.hip_us.classes.dev.DevMetricsUS]:
340def analyse_hip_2DUS(
341    img: Image.Image,
342    keyphrase: Union[str, Config],
343    modes_func: Callable[
344        [Image.Image, str, Dict[str, Any]],
345        List[SegFrameObjects],
346    ],
347    modes_func_kwargs_dict: Dict[str, Any],
348    return_seg_info: bool = False,
349) -> Tuple[HipDataUS, Image.Image, DevMetricsUS]:
350    """
351    Analyze a 2D Ultrasound Hip
352
353    :param img: The image.
354    :param keyphrase: The keyphrase.
355    :param modes_func: The mode function.
356    :param modes_func_kwargs_dict: The mode function kwargs.
357
358    :return: The hip, the image, and the dev metrics.
359    """
360    config = Config.get_config(keyphrase)
361
362    try:
363        if config.operation_type in OperationType.SEG:
364            hip_datas, results, _ = process_segs_us(
365                config, [img], modes_func, modes_func_kwargs_dict
366            )
367    except Exception as e:
368        ulogger.error(f"Critical Error: {e}")
369        return None, None, None
370
371    image_arrays, _ = draw_hips_us(hip_datas, results, None, config)
372
373    hip_datas = get_dev_metrics(hip_datas, results, config)
374
375    image = image_arrays[0]
376    hip = hip_datas[0]
377
378    image = Image.fromarray(image)
379
380    if return_seg_info:
381        hip.seg_info = results
382
383    return hip, image, hip_datas.dev_metrics

Analyze a 2D Ultrasound Hip

Parameters
  • img: The image.
  • keyphrase: The keyphrase.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The hip, the image, and the dev metrics.

def analyse_hip_2DUS_sweep( dcm: pydicom.dataset.FileDataset, keyphrase: Union[str, retuve.keyphrases.config.Config], modes_func: Callable[[pydicom.dataset.FileDataset, str, Dict[str, Any]], List[retuve.classes.seg.SegFrameObjects]], modes_func_kwargs_dict: Dict[str, Any]) -> Tuple[retuve.hip_us.classes.general.HipDataUS, PIL.Image.Image, retuve.hip_us.classes.dev.DevMetricsUS, moviepy.video.io.ImageSequenceClip.ImageSequenceClip]:
386def analyse_hip_2DUS_sweep(
387    dcm: pydicom.FileDataset,
388    keyphrase: Union[str, Config],
389    modes_func: Callable[
390        [pydicom.FileDataset, str, Dict[str, Any]],
391        List[SegFrameObjects],
392    ],
393    modes_func_kwargs_dict: Dict[str, Any],
394) -> Tuple[
395    HipDataUS,
396    Image.Image,
397    DevMetricsUS,
398    ImageSequenceClip,
399]:
400    """
401    Analyze a 2D Sweep Ultrasound Hip
402
403    :param dcm: The DICOM file.
404    :param keyphrase: The keyphrase.
405    :param modes_func: The mode function.
406    :param modes_func_kwargs_dict: The mode function kwargs.
407
408    :return: The hip, the image, the dev metrics, and the video clip.
409    """
410
411    config = Config.get_config(keyphrase)
412    hip_datas = HipDatasUS()
413
414    try:
415        if config.operation_type == OperationType.SEG:
416            hip_datas, results, shape = process_segs_us(
417                config, dcm, modes_func, modes_func_kwargs_dict
418            )
419        elif config.operation_type == OperationType.LANDMARK:
420            raise NotImplementedError(
421                "This is not yet supported. Please use the seg operation type."
422            )
423    except Exception as e:
424        ulogger.error(f"Critical Error: {e}")
425        return None, None, None, None
426
427    hip_datas = handle_bad_frames(hip_datas, config)
428    hip_datas = find_graf_plane(hip_datas, results, config)
429
430    graf_hip = hip_datas.grafs_hip
431    graf_frame = hip_datas.graf_frame
432
433    image_arrays, _ = draw_hips_us(hip_datas, results, None, config)
434
435    hip_datas = get_dev_metrics(hip_datas, results, config)
436
437    if graf_frame is not None:
438        graf_image = image_arrays[graf_frame]
439        graf_image = Image.fromarray(graf_image)
440
441        image_arrays = (
442            [image_arrays[graf_frame]] * int(len(image_arrays) * 0.1)
443            + image_arrays
444            + [image_arrays[graf_frame]] * int(len(image_arrays) * 0.1)
445        )
446    else:
447        graf_image = None
448
449    video_clip = ImageSequenceClip(
450        image_arrays,
451        fps=get_fps(
452            len(image_arrays),
453            config.visuals.min_vid_fps,
454            config.visuals.min_vid_length,
455        ),
456    )
457
458    return graf_hip, graf_image, hip_datas.dev_metrics, video_clip

Analyze a 2D Sweep Ultrasound Hip

Parameters
  • dcm: The DICOM file.
  • keyphrase: The keyphrase.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
Returns

The hip, the image, the dev metrics, and the video clip.

class RetuveResult:
461class RetuveResult:
462    """
463    The standardised result of the Retuve pipeline.
464
465    :attr hip_datas: The hip datas.
466    :attr hip: The hip.
467    :attr image: The saved image, if any.
468    :attr metrics: The metrics.
469    :attr video_clip: The video clip, if any.
470    :attr visual_3d: The 3D visual, if any.
471    """
472
473    def __init__(
474        self,
475        metrics: Union[List[Metric2D], List[Metric3D]],
476        hip_datas: Union[HipDatasUS, List[HipDataXray]] = None,
477        hip: Union[HipDataXray, HipDataUS] = None,
478        image: Image.Image = None,
479        video_clip: ImageSequenceClip = None,
480        visual_3d: Figure = None,
481    ):
482        self.hip_datas = hip_datas
483        self.hip = hip
484        self.image = image
485        self.metrics = metrics
486        self.video_clip = video_clip
487        self.visual_3d = visual_3d

The standardised result of the Retuve pipeline.

:attr hip_datas: The hip datas. :attr hip: The hip. :attr image: The saved image, if any. :attr metrics: The metrics. :attr video_clip: The video clip, if any. :attr visual_3d: The 3D visual, if any.

RetuveResult( metrics: Union[List[retuve.classes.metrics.Metric2D], List[retuve.classes.metrics.Metric3D]], hip_datas: Union[retuve.hip_us.classes.general.HipDatasUS, List[retuve.hip_xray.classes.HipDataXray]] = None, hip: Union[retuve.hip_xray.classes.HipDataXray, retuve.hip_us.classes.general.HipDataUS] = None, image: PIL.Image.Image = None, video_clip: moviepy.video.io.ImageSequenceClip.ImageSequenceClip = None, visual_3d: plotly.graph_objs._figure.Figure = None)
473    def __init__(
474        self,
475        metrics: Union[List[Metric2D], List[Metric3D]],
476        hip_datas: Union[HipDatasUS, List[HipDataXray]] = None,
477        hip: Union[HipDataXray, HipDataUS] = None,
478        image: Image.Image = None,
479        video_clip: ImageSequenceClip = None,
480        visual_3d: Figure = None,
481    ):
482        self.hip_datas = hip_datas
483        self.hip = hip
484        self.image = image
485        self.metrics = metrics
486        self.video_clip = video_clip
487        self.visual_3d = visual_3d
hip_datas
hip
image
metrics
video_clip
visual_3d
def retuve_run( hip_mode: retuve.keyphrases.enums.HipMode, config: retuve.keyphrases.config.Config, modes_func: Callable[[Union[BinaryIO, PIL.Image.Image, pydicom.dataset.FileDataset], Union[str, List[str]], Dict[str, Any]], Union[Tuple[List[retuve.hip_xray.classes.LandmarksXRay], List[retuve.classes.seg.SegFrameObjects]], List[retuve.classes.seg.SegFrameObjects]]], modes_func_kwargs_dict: Dict[str, Any], file: str) -> RetuveResult:
490def retuve_run(
491    hip_mode: HipMode,
492    config: Config,
493    modes_func: GeneralModeFuncType,
494    modes_func_kwargs_dict: Dict[str, Any],
495    file: str,
496) -> RetuveResult:
497    """
498    Run the Retuve pipeline with standardised inputs and outputs.
499
500    :param hip_mode: The hip mode.
501    :param config: The configuration.
502    :param modes_func: The mode function.
503    :param modes_func_kwargs_dict: The mode function kwargs.
504    :param file: The file.
505
506    :return: The Retuve result standardised output.
507    """
508    always_dcm = (
509        len(config.batch.input_types) == 1
510        and ".dcm" in config.batch.input_types
511    )
512
513    if always_dcm or (
514        file.endswith(".dcm") and ".dcm" in config.batch.input_types
515    ):
516        file = pydicom.dcmread(file)
517
518    if hip_mode == HipMode.XRAY:
519        file = Image.open(file)
520        hip, image, dev_metrics = analyse_hip_xray_2D(
521            file, config, modes_func, modes_func_kwargs_dict
522        )
523        return RetuveResult(
524            hip.json_dump(config, dev_metrics), image=image, hip=hip
525        )
526    elif hip_mode == HipMode.US3D:
527        hip_datas, video_clip, visual_3d, dev_metrics = analyse_hip_3DUS(
528            file, config, modes_func, modes_func_kwargs_dict
529        )
530        return RetuveResult(
531            hip_datas.json_dump(config),
532            hip_datas=hip_datas,
533            video_clip=video_clip,
534            visual_3d=visual_3d,
535        )
536    elif hip_mode == HipMode.US2D:
537        file = Image.open(file).convert("RGB")
538        hip, image, dev_metrics = analyse_hip_2DUS(
539            file, config, modes_func, modes_func_kwargs_dict
540        )
541        return RetuveResult(
542            hip.json_dump(config, dev_metrics), hip=hip, image=image
543        )
544    elif hip_mode == HipMode.US2DSW:
545        hip, image, dev_metrics, video_clip = analyse_hip_2DUS_sweep(
546            file, config, modes_func, modes_func_kwargs_dict
547        )
548        json_dump = None
549        if hip:
550            json_dump = hip.json_dump(config, dev_metrics)
551
552        return RetuveResult(
553            json_dump,
554            hip=hip,
555            image=image,
556            video_clip=video_clip,
557        )
558    else:
559        raise ValueError(f"Invalid hip_mode. {hip_mode}")

Run the Retuve pipeline with standardised inputs and outputs.

Parameters
  • hip_mode: The hip mode.
  • config: The configuration.
  • modes_func: The mode function.
  • modes_func_kwargs_dict: The mode function kwargs.
  • file: The file.
Returns

The Retuve result standardised output.