retuve.defaults.manual_seg

The Default Segmentaition Method for Hip Ultrasound and Xray.

Currently, all the defaults are manual methods.

AI methods will be added in the future.

  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"""
 16The Default Segmentaition Method for Hip Ultrasound and Xray.
 17
 18Currently, all the defaults are manual methods.
 19
 20AI methods will be added in the future.
 21"""
 22
 23import time
 24from typing import Dict, List, Tuple, Union
 25
 26import cv2
 27import numpy as np
 28import pydicom
 29from nibabel.nifti1 import Nifti1Image
 30from PIL import Image, ImageOps
 31from radstract.data.colors import LabelColours, get_unique_colours
 32from radstract.data.dicom import convert_dicom_to_images
 33from radstract.data.nifti import convert_nifti_to_image_labels
 34
 35from retuve.classes.seg import SegFrameObjects, SegObject
 36from retuve.hip_us.classes.enums import HipLabelsUS
 37from retuve.hip_xray.classes import HipLabelsXray, LandmarksXRay
 38from retuve.keyphrases.config import Config
 39from retuve.logs import log_timings
 40
 41
 42def manual_predict_us_dcm(
 43    dcm: pydicom.FileDataset,
 44    keyphrase: Union[str, Config],
 45    config: Config = None,
 46    seg: Union[str, Nifti1Image] = None,
 47) -> List[SegFrameObjects]:
 48    """
 49    Manual Segmentation for Hip Ultrasound.
 50
 51    :param dcm: The DICOM File
 52    :param keyphrase: The Keyphrase or Config
 53    :param config: The Config
 54    :param seg: The Segmentation File
 55
 56    :return: The Segmentation Results
 57    """
 58    if not config:
 59        config = Config.get_config(keyphrase)
 60
 61    dicom_images = convert_dicom_to_images(
 62        dcm,
 63        crop_coordinates=config.crop_coordinates,
 64        dicom_type=config.dicom_type,
 65    )
 66
 67    return manual_predict_us(dicom_images, keyphrase, config=config, seg=seg)
 68
 69
 70def manual_predict_us(
 71    images: List[Image.Image],
 72    keyphrase: Union[str, Config],
 73    config: Config = None,
 74    seg: Union[str, Nifti1Image] = None,
 75    seg_idx: int = 0,
 76) -> List[SegFrameObjects]:
 77    """
 78    Manual Segmentation for Hip Ultrasound.
 79
 80    :param images: The Images
 81    :param keyphrase: The Keyphrase or Config
 82    :param config: The Config
 83    :param seg: The Segmentation File
 84    :param seg_idx: The Segmentation Index
 85
 86    :return: The Segmentation Results
 87    """
 88
 89    if not config:
 90        config = Config.get_config(keyphrase)
 91
 92    # seg is a nifti file, open it
 93    if not seg:
 94        raise ValueError("seg file is required")
 95
 96    results, _ = convert_nifti_to_image_labels(
 97        seg, crop_coordinates=config.crop_coordinates
 98    )
 99
100    if seg_idx:
101        results = results[seg_idx:]
102
103    classes = {
104        LabelColours.LABEL1: HipLabelsUS.IlliumAndAcetabulum,
105        LabelColours.LABEL2: HipLabelsUS.FemoralHead,
106        LabelColours.LABEL3: HipLabelsUS.OsIchium,
107    }
108
109    timings = []
110    seg_results = []
111
112    for img, result in zip(images, results):
113        start = time.time()
114        img = np.array(img)
115
116        seg_frame_objects = SegFrameObjects(img)
117
118        # get each colour from the seg
119        unique_colours = get_unique_colours(result)
120
121        # convert image to np array
122        for colour in unique_colours:
123            if colour not in classes:
124                continue
125
126            # convert image to np array
127            result = np.array(result)
128
129            mask = np.all(result == colour, axis=-1)
130            mask = mask.astype(np.uint8) * 255
131
132            # get the bounding box
133            contours, _ = cv2.findContours(
134                mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
135            )
136
137            if not contours:
138                continue
139
140            box = cv2.boundingRect(mask)
141            # just get the bottom left and top right coordinates
142            box = (box[0], box[1], box[0] + box[2], box[1] + box[3])
143
144            # get the points on the outside of the mask
145            points = np.concatenate(contours[0], axis=0)
146
147            # drop 80% of the points
148            points = [
149                (int(x), int(y))
150                for i, (x, y) in enumerate(points)
151                if i % 10 == 0
152            ]
153
154            # convert mask to rgb
155            mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB)
156
157            seg_obj = SegObject(
158                points, classes[colour], mask, box=box, conf=1.0
159            )
160
161            seg_frame_objects.append(seg_obj)
162
163        timings.append(time.time() - start)
164
165        if len(seg_frame_objects) == 0:
166            seg_frame_objects = SegFrameObjects.empty(img=img)
167
168        seg_results.append(seg_frame_objects)
169
170    log_timings(timings, title="Manual Segmentation")
171
172    return seg_results
173
174
175def manual_predict_xray(
176    images: List[Image.Image],
177    keyphrase: Union[str, Nifti1Image],
178    config: Config = None,
179    landmark_list: List[Dict[str, List[int]]] = None,
180    scale_landmarks: bool = False,
181) -> Tuple[List[LandmarksXRay], List[SegFrameObjects]]:
182    """
183    Manual Segmentation for Hip Xray.
184
185    :param images: The Images
186    :param keyphrase: The Keyphrase or Config
187    :param config: The Config
188    :param landmark_list: The Landmark List
189    :param scale_landmarks: Whether to scale the landmarks.
190                            This shoudl be set False if your coordinates
191                            are calculated to the 1024x1024 image resize.
192
193    :return: The Landmarks and Segmentation Results
194    """
195    if not config:
196        config = Config.get_config(keyphrase)
197
198    original_wh = [images[0].width, images[0].height]
199
200    if not landmark_list:
201        raise ValueError("landmark_list is required")
202
203    landmark_results = []
204    seg_results = []
205
206    for image, landmarks in zip(images, landmark_list):
207        A, B = image.size
208
209        if scale_landmarks:
210            for key, (x, y) in landmarks.items():
211                landmarks[key] = (
212                    int(x * A / original_wh[0]),
213                    int(y * B / original_wh[1]),
214                )
215
216        landmarks = LandmarksXRay(**landmarks)
217        landmarks_l = [landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l]
218        landmarks_r = [landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_r]
219
220        # construct masks for the triangles
221        mask_l = np.zeros((A, B, 3), dtype=np.uint8)
222        mask_r = np.zeros((A, B, 3), dtype=np.uint8)
223
224        cv2.fillPoly(mask_l, [np.array(landmarks_l)], (255, 255, 255))
225        cv2.fillPoly(mask_r, [np.array(landmarks_r)], (255, 255, 255))
226
227        # and bounding boxes
228        box_l = cv2.boundingRect(np.array(landmarks_l))
229        box_r = cv2.boundingRect(np.array(landmarks_r))
230
231        # get the bottom left and top right coordinates
232        box_l = (box_l[0], box_l[1], box_l[0] + box_l[2], box_l[1] + box_l[3])
233        box_r = (box_r[0], box_r[1], box_r[0] + box_r[2], box_r[1] + box_r[3])
234
235        image = np.array(image)
236
237        seg_objects = [
238            SegObject(
239                landmarks_l,
240                HipLabelsXray.AceTriangle,
241                mask_l,
242                box=box_l,
243                conf=1.0,
244            ),
245            SegObject(
246                landmarks_r,
247                HipLabelsXray.AceTriangle,
248                mask_r,
249                box=box_r,
250                conf=1.0,
251            ),
252        ]
253        seg_result = SegFrameObjects(img=image, seg_objects=seg_objects)
254
255        seg_results.append(seg_result)
256        landmark_results.append(landmarks)
257
258    return landmark_results, seg_results
def manual_predict_us_dcm( dcm: pydicom.dataset.FileDataset, keyphrase: Union[str, retuve.keyphrases.config.Config], config: retuve.keyphrases.config.Config = None, seg: Union[str, nibabel.nifti1.Nifti1Image] = None) -> List[retuve.classes.seg.SegFrameObjects]:
43def manual_predict_us_dcm(
44    dcm: pydicom.FileDataset,
45    keyphrase: Union[str, Config],
46    config: Config = None,
47    seg: Union[str, Nifti1Image] = None,
48) -> List[SegFrameObjects]:
49    """
50    Manual Segmentation for Hip Ultrasound.
51
52    :param dcm: The DICOM File
53    :param keyphrase: The Keyphrase or Config
54    :param config: The Config
55    :param seg: The Segmentation File
56
57    :return: The Segmentation Results
58    """
59    if not config:
60        config = Config.get_config(keyphrase)
61
62    dicom_images = convert_dicom_to_images(
63        dcm,
64        crop_coordinates=config.crop_coordinates,
65        dicom_type=config.dicom_type,
66    )
67
68    return manual_predict_us(dicom_images, keyphrase, config=config, seg=seg)

Manual Segmentation for Hip Ultrasound.

Parameters
  • dcm: The DICOM File
  • keyphrase: The Keyphrase or Config
  • config: The Config
  • seg: The Segmentation File
Returns

The Segmentation Results

def manual_predict_us( images: List[PIL.Image.Image], keyphrase: Union[str, retuve.keyphrases.config.Config], config: retuve.keyphrases.config.Config = None, seg: Union[str, nibabel.nifti1.Nifti1Image] = None, seg_idx: int = 0) -> List[retuve.classes.seg.SegFrameObjects]:
 71def manual_predict_us(
 72    images: List[Image.Image],
 73    keyphrase: Union[str, Config],
 74    config: Config = None,
 75    seg: Union[str, Nifti1Image] = None,
 76    seg_idx: int = 0,
 77) -> List[SegFrameObjects]:
 78    """
 79    Manual Segmentation for Hip Ultrasound.
 80
 81    :param images: The Images
 82    :param keyphrase: The Keyphrase or Config
 83    :param config: The Config
 84    :param seg: The Segmentation File
 85    :param seg_idx: The Segmentation Index
 86
 87    :return: The Segmentation Results
 88    """
 89
 90    if not config:
 91        config = Config.get_config(keyphrase)
 92
 93    # seg is a nifti file, open it
 94    if not seg:
 95        raise ValueError("seg file is required")
 96
 97    results, _ = convert_nifti_to_image_labels(
 98        seg, crop_coordinates=config.crop_coordinates
 99    )
100
101    if seg_idx:
102        results = results[seg_idx:]
103
104    classes = {
105        LabelColours.LABEL1: HipLabelsUS.IlliumAndAcetabulum,
106        LabelColours.LABEL2: HipLabelsUS.FemoralHead,
107        LabelColours.LABEL3: HipLabelsUS.OsIchium,
108    }
109
110    timings = []
111    seg_results = []
112
113    for img, result in zip(images, results):
114        start = time.time()
115        img = np.array(img)
116
117        seg_frame_objects = SegFrameObjects(img)
118
119        # get each colour from the seg
120        unique_colours = get_unique_colours(result)
121
122        # convert image to np array
123        for colour in unique_colours:
124            if colour not in classes:
125                continue
126
127            # convert image to np array
128            result = np.array(result)
129
130            mask = np.all(result == colour, axis=-1)
131            mask = mask.astype(np.uint8) * 255
132
133            # get the bounding box
134            contours, _ = cv2.findContours(
135                mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
136            )
137
138            if not contours:
139                continue
140
141            box = cv2.boundingRect(mask)
142            # just get the bottom left and top right coordinates
143            box = (box[0], box[1], box[0] + box[2], box[1] + box[3])
144
145            # get the points on the outside of the mask
146            points = np.concatenate(contours[0], axis=0)
147
148            # drop 80% of the points
149            points = [
150                (int(x), int(y))
151                for i, (x, y) in enumerate(points)
152                if i % 10 == 0
153            ]
154
155            # convert mask to rgb
156            mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB)
157
158            seg_obj = SegObject(
159                points, classes[colour], mask, box=box, conf=1.0
160            )
161
162            seg_frame_objects.append(seg_obj)
163
164        timings.append(time.time() - start)
165
166        if len(seg_frame_objects) == 0:
167            seg_frame_objects = SegFrameObjects.empty(img=img)
168
169        seg_results.append(seg_frame_objects)
170
171    log_timings(timings, title="Manual Segmentation")
172
173    return seg_results

Manual Segmentation for Hip Ultrasound.

Parameters
  • images: The Images
  • keyphrase: The Keyphrase or Config
  • config: The Config
  • seg: The Segmentation File
  • seg_idx: The Segmentation Index
Returns

The Segmentation Results

def manual_predict_xray( images: List[PIL.Image.Image], keyphrase: Union[str, nibabel.nifti1.Nifti1Image], config: retuve.keyphrases.config.Config = None, landmark_list: List[Dict[str, List[int]]] = None, scale_landmarks: bool = False) -> Tuple[List[retuve.hip_xray.classes.LandmarksXRay], List[retuve.classes.seg.SegFrameObjects]]:
176def manual_predict_xray(
177    images: List[Image.Image],
178    keyphrase: Union[str, Nifti1Image],
179    config: Config = None,
180    landmark_list: List[Dict[str, List[int]]] = None,
181    scale_landmarks: bool = False,
182) -> Tuple[List[LandmarksXRay], List[SegFrameObjects]]:
183    """
184    Manual Segmentation for Hip Xray.
185
186    :param images: The Images
187    :param keyphrase: The Keyphrase or Config
188    :param config: The Config
189    :param landmark_list: The Landmark List
190    :param scale_landmarks: Whether to scale the landmarks.
191                            This shoudl be set False if your coordinates
192                            are calculated to the 1024x1024 image resize.
193
194    :return: The Landmarks and Segmentation Results
195    """
196    if not config:
197        config = Config.get_config(keyphrase)
198
199    original_wh = [images[0].width, images[0].height]
200
201    if not landmark_list:
202        raise ValueError("landmark_list is required")
203
204    landmark_results = []
205    seg_results = []
206
207    for image, landmarks in zip(images, landmark_list):
208        A, B = image.size
209
210        if scale_landmarks:
211            for key, (x, y) in landmarks.items():
212                landmarks[key] = (
213                    int(x * A / original_wh[0]),
214                    int(y * B / original_wh[1]),
215                )
216
217        landmarks = LandmarksXRay(**landmarks)
218        landmarks_l = [landmarks.pel_l_o, landmarks.pel_l_i, landmarks.fem_l]
219        landmarks_r = [landmarks.pel_r_o, landmarks.pel_r_i, landmarks.fem_r]
220
221        # construct masks for the triangles
222        mask_l = np.zeros((A, B, 3), dtype=np.uint8)
223        mask_r = np.zeros((A, B, 3), dtype=np.uint8)
224
225        cv2.fillPoly(mask_l, [np.array(landmarks_l)], (255, 255, 255))
226        cv2.fillPoly(mask_r, [np.array(landmarks_r)], (255, 255, 255))
227
228        # and bounding boxes
229        box_l = cv2.boundingRect(np.array(landmarks_l))
230        box_r = cv2.boundingRect(np.array(landmarks_r))
231
232        # get the bottom left and top right coordinates
233        box_l = (box_l[0], box_l[1], box_l[0] + box_l[2], box_l[1] + box_l[3])
234        box_r = (box_r[0], box_r[1], box_r[0] + box_r[2], box_r[1] + box_r[3])
235
236        image = np.array(image)
237
238        seg_objects = [
239            SegObject(
240                landmarks_l,
241                HipLabelsXray.AceTriangle,
242                mask_l,
243                box=box_l,
244                conf=1.0,
245            ),
246            SegObject(
247                landmarks_r,
248                HipLabelsXray.AceTriangle,
249                mask_r,
250                box=box_r,
251                conf=1.0,
252            ),
253        ]
254        seg_result = SegFrameObjects(img=image, seg_objects=seg_objects)
255
256        seg_results.append(seg_result)
257        landmark_results.append(landmarks)
258
259    return landmark_results, seg_results

Manual Segmentation for Hip Xray.

Parameters
  • images: The Images
  • keyphrase: The Keyphrase or Config
  • config: The Config
  • landmark_list: The Landmark List
  • scale_landmarks: Whether to scale the landmarks. This shoudl be set False if your coordinates are calculated to the 1024x1024 image resize.
Returns

The Landmarks and Segmentation Results