3. 장기 별 bounding box 좌표 추가

문지우·2024년 5월 15일

[Medical AI] RSNA 2023

목록 보기
4/5

1. extract_organ_3D_bounding_box()

; TotalSegmentator output인 nii 파일에서 3D Bbox 좌표(key - 장기 코드) 추출

def extract_organ_3D_bounding_box(nii_file):

    """
    주어진 .nii 파일에서 장기의 3D bounding box (min, max 좌표)를 추출

    Args:
    - nii_file (str): .nii 파일의 경로.

    Returns:
    - dict: 장기 이름을 키로 하고 해당하는 min, max 3D 좌표를 값
    """
    
    # .nii 파일 불러오기
    nii_image = sitk.ReadImage(nii_file)
    data = sitk.GetArrayFromImage(nii_image)
    
    # 각 장기에 대한 bounding box 좌표를 저장할 사전
    organ_bounding_boxes = {}
    
    organ_labels = {
        'liver': [5],
        'spleen': [1],
        'kidney': [2, 3],
        'bowel': [55, 57, 56]  # small_bowel, colon, duodenum
    }
    
    # 각 장기의 bounding box 좌표 추출
    for organ, labels in organ_labels.items():
        coords = []	  # 좌표 저장할 리스트
        for label in labels:
            coords.extend(np.argwhere(data == label).tolist())
        
        if not coords:
            organ_bounding_boxes[organ] = None
            continue
        
        coords = np.array(coords)

        z_min, y_min, x_min = coords.min(axis=0)
        z_max, y_max, x_max = coords.max(axis=0)

        # 각 차원의 중심을 계산
        z_center, y_center, x_center = (z_min + z_max) // 2, (y_min + y_max) // 2, (x_min + x_max) // 2

        # scale_facor 지정
        scale_factor = 1.2
    
        # 각 차원의 절반 길이를 계산
        z_half, y_half, x_half = (z_max - z_min) * scale_factor / 2, (y_max - y_min) * scale_factor / 2, (x_max - x_min) * scale_factor / 2

        # 중심을 기준으로 bounding box 재조정
        z_min, z_max = int(z_center - z_half), int(z_center + z_half)
        y_min, y_max = int(y_center - y_half), int(y_center + y_half)
        x_min, x_max = int(x_center - x_half), int(x_center + x_half)
        
        organ_bounding_boxes[organ] = {
            'x': (x_min, x_max),
            'y': (y_min, y_max),
            'z': (z_min, z_max)
        }
    
    return organ_bounding_boxes
  • scale factor = 1.2로 잡음 -> bbox 20% 더 넓게 잡음
    (❓ scale factor 왜 더 넓게 잡는건지? 조금 더 보수적으로 roi 추출하려는건가)


2. save_bounding_boxes_to_csv() :

위의 extract_organ_3D_bounding_box() 호출해 모든 파일 내 bbox 좌표 추출(dict 형태)

이를 csv 파일로 작성 (patient_id, series_id, organ, bbox 좌표)

def save_bounding_boxes_to_csv(nii_directory, csv_directory, csv_filename="bounding_boxes.csv"):
    """
    주어진 디렉터리의 모든 .nii 파일에서 3D bounding box를 추출하고
    결과를 지정된 CSV 파일에 저장합니다.

    Args:
    - nii_directory (str): .nii 파일들이 있는 디렉터리 경로.
    - csv_directory (str): 결과를 저장할 CSV 파일의 디렉터리 경로.
    - csv_filename (str): 생성될 CSV 파일의 이름. 기본값은 "bounding_boxes.csv".
    """

    # CSV 파일 경로 생성
    csv_file_path = os.path.join(csv_directory, csv_filename)

    # .nii 파일 리스트 가져오기
    nii_files = [f for f in os.listdir(nii_directory) if f.endswith('.nii')]

    # series_id를 기준으로 정렬
    nii_files = sorted(nii_files, key=lambda x: int(x.split('.')[0]))

    with open(csv_file_path, 'w', newline='') as csvfile:
        csv_writer = csv.writer(csvfile)
        # CSV 헤더 작성
        csv_writer.writerow(['patient_id', 'series_id', 'organ', 'x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max'])

        
        # 각 .nii 파일에 대해 처리
        for nii_file in tqdm(nii_files, desc="Processing NII files"):
            nii_file_path = os.path.join(nii_directory, nii_file)

            patient_id, series_id = nii_file.split('_')[0], nii_file.split('_')[1].split('.')[0]

            bounding_boxes = extract_organ_3D_bounding_box(nii_file_path)
            
            for organ, bbox in bounding_boxes.items():
                if bbox:
                    csv_writer.writerow([patient_id, series_id, organ, bbox['x'][0], bbox['x'][1], bbox['y'][0], bbox['y'][1], bbox['z'][0], bbox['z'][1]])


3. SimpleITK ; 의료영상 전처리 오픈 소스

( 티스토리 참고 ; https://bo-10000.tistory.com/61 )
( method 참고 ; https://simpleitk.org/SimpleITK-Notebooks/01_Image_Basics.html )

nda = sitk.GetArrayFromImage(image_RGB)
img = sitk.GetImageFromArray(nda)

*numpy array & sitk.Image 객체 상호 변환 가능

< 아래 속성들에 접근 가능 >

  1. image.GetSize()

    • 설명: 이 메소드는 영상의 크기를 반환합니다. 더 구체적으로는 영상의 각 차원(너비, 높이, 깊이 등)에 대한 픽셀 수를 나타내는 튜플을 제공합니다.
    • 의미: 의료 영상에서는 주로 2D(예: MRI, CT 슬라이스) 또는 3D(예: 전체 MRI, CT 볼륨) 영상을 다룹니다. GetSize()는 이러한 영상의 각 차원을 통해 픽셀(2D) 또는 복셀(3D)의 전체 개수를 알려줍니다. 예를 들어, (512, 512)는 512x512 픽셀의 2D 영상을, (512, 512, 150)는 각각 512x512 픽셀 크기의 150개 슬라이스를 가진 3D 영상을 의미합니다.
  2. image.GetOrigin()

    • 설명: 이 메소드는 영상의 원점(origin)을 반환합니다. 이는 영상의 각 축에 대한 원점의 좌표를 나타내는 튜플로, 공간 상에서 영상의 시작 위치를 알려줍니다.
    • 의미: 의료 영상에서 원점은 영상이 어디서 시작하는지(공간 좌표에서)를 나타내며, 주로 재구성, 정렬, 혹은 영상 간 비교를 할 때 중요합니다. 예를 들어, (0.0, 0.0, 0.0)은 가장 기본적인 원점 위치를, (-250.0, -250.0, -180.0)은 x, y, z 축에서 각각 -250mm, -250mm, -180mm에 해당하는 원점 위치를 의미할 수 있습니다.
  3. image.GetSpacing()

    • 설명: 이 메소드는 영상의 픽셀 간격(spacing)을 반환합니다. 이는 영상에서 인접 픽셀(2D) 또는 복셀(3D) 간의 실제 물리적 거리를 나타내는 튜플입니다.
    • 의미: 픽셀 간격은 픽셀의 크기를 의미하며, 의료 영상의 정량적 분석에 필수적입니다. 예를 들어, (0.5, 0.5)는 각 픽셀이 0.5mm x 0.5mm의 크기를 가짐을, (0.5, 0.5, 1.0)는 3D 영상에서 각 복셀이 0.5mm x 0.5mm x 1.0mm 크기임을 의미합니다.
  4. image.GetDirection()

    • 설명: 이 메소드는 영상의 방향 코사인(direction cosines)을 반환합니다. 이는 영상의 각 축이 공간 상에서 어떻게 배치되어 있는지를 나타내는 행렬입니다.
    • 의미: 방향 코사인은 주로 영상의 방향성을 나타내며, 영상이 어떻게 회전되어 있는지에 대한 정보를 제공합니다. 예를 들어, 단위 행렬 (1, 0, 0, 0, 1, 0, 0, 0, 1)은 영상이 공간 상에서 표준 방향(축에 정렬)으로 배치되어 있음을, 비틀어진 방향을 가진 경우는 다른 값들을 포함할 수 있습니다.
  5. image.GetNumberOfComponentsPerPixel()

    • 설명: 이 메소드는 각 픽셀이 가지고 있는 컴포넌트(예: 색상 채널)의 수를 반환합니다.
    • 의미: 의료 영상에서 이 값은 대부분 1을 가지며, 이는 각 픽셀이 하나의 스칼라 값을 가진다는 것을 의미합니다(예: 그레이스케일 이미지). 그러나 다채널 데이터의 경우, 예를 들어, RGB 영상이나 멀티 파라미터 영상에서는 이 값이 3 또는 그 이상이 될 수 있습니다.

  • 이전에 살펴보았던 DICOM 형식의 이미지에도 접근해 meta data 추출 가능
for key in image.GetMetaDataKeys():
        print "\"{0}\":\"{1}\"".format(key, image.GetMetaData(key))
  • numpy array & sitk.Image 합칠 때 주의할 점
    ; sitk.Image array order (x,y,z)
    numpy arrary order (z,y,x)
    → 역순으로 만들어주는 작업 필요
    print img.GetSize() # (128, 128)
    print nda.shape # (128, 128, 3)
    print nda.shape[::-1] # (3, 128, 128)
profile
대왕 감자의 성장 일기,,,👩‍🌾

0개의 댓글