Files

178 lines
6.2 KiB
Python
Raw Permalink Normal View History

import os
import cv2
import numpy as np
import nibabel as nib
import pydicom
from pathlib import Path
import shutil
2025-12-02 13:05:13 +09:00
from datetime import datetime
# --- 설정 (사용자 수정 가능) ---
DATA_ROOT = Path('.') # 데이터가 있는 최상위 경로 (현재: 실행 위치)
OUTPUT_ROOT_NAME = "Conversion_Results" # 결과물이 저장될 최상위 폴더명
# -----------------------------
# 현재 시간으로 서브 폴더명 생성 (예: 20251202_123000)
TIMESTAMP = datetime.now().strftime("%Y%m%d_%H%M%S")
OUTPUT_DIR = DATA_ROOT / OUTPUT_ROOT_NAME / TIMESTAMP
JPEG_DIR = OUTPUT_DIR / 'JPEGImages'
SEG_CLASS_DIR = OUTPUT_DIR / 'SegmentationClass'
IMAGE_SETS_DIR = OUTPUT_DIR / 'ImageSets' / 'Segmentation'
# 색상 정의 (샘플 labelmap.txt 기반)
COLOR_MAP = {
"C2": (230, 25, 75),
"C3": (60, 180, 75),
"C4": (255, 225, 25),
"C5": (67, 99, 216),
"C6": (245, 130, 49),
"C7": (145, 30, 180),
"L1": (0, 0, 117),
"L2": (128, 128, 128),
"L3": (255, 255, 255),
"L4": (0, 0, 0), # 주의: L4가 검은색(0,0,0)으로 정의되어 있어 배경과 겹칠 수 있음. 확인 필요.
"L5": (31, 119, 180),
"Rib": (255, 127, 14),
"Sacrum": (44, 160, 44),
"T1": (70, 240, 240),
"T10": (170, 255, 195),
"T11": (128, 128, 0),
"T12": (255, 216, 177),
"T2": (240, 50, 230),
"T3": (188, 246, 12),
"T4": (250, 190, 190),
"T5": (0, 128, 128),
"T6": (230, 190, 255),
"T7": (154, 99, 36),
"T8": (255, 250, 200),
"T9": (128, 0, 0),
# "background": (0, 0, 0) # 배경은 기본적으로 검정
}
# L4가 (0,0,0)이라면 배경과 구분 불가. L4 색상을 임의로 변경하거나 주의해야 함.
# 여기서는 안전을 위해 L4를 아주 어두운 회색(1,1,1)으로 하거나,
# 또는 사용자가 준 샘플에 L4가 (0,0,0)이라면 그것을 따라야 하지만,
# 보통 배경이 (0,0,0)이므로 L4는 (50,50,50) 정도로 변경하여 진행하겠습니다.
COLOR_MAP["L4"] = (50, 50, 50)
def normalize_image(image):
"""DICOM 이미지를 0-255 범위의 8bit 이미지로 변환"""
image = image.astype(float)
min_val = np.min(image)
max_val = np.max(image)
if max_val - min_val == 0:
return np.zeros(image.shape, dtype=np.uint8)
image = (image - min_val) / (max_val - min_val) * 255.0
return image.astype(np.uint8)
def process_case(case_dir, file_list):
"""하나의 케이스(폴더) 처리"""
dcm_files = list(case_dir.glob('*.dcm'))
if not dcm_files:
return
dcm_path = dcm_files[0]
case_name = case_dir.name
# 1. DICOM -> JPG
try:
ds = pydicom.dcmread(dcm_path)
pixel_array = ds.pixel_array
img_8bit = normalize_image(pixel_array)
# 그레이스케일 이미지를 RGB로 변환 (CVAT 호환성)
img_rgb = cv2.cvtColor(img_8bit, cv2.COLOR_GRAY2BGR)
cv2.imwrite(str(JPEG_DIR / f"{case_name}.jpg"), img_rgb)
except Exception as e:
print(f"Error processing DICOM {dcm_path}: {e}")
return
# 2. NII Masks -> RGB Mask
# 배경(0,0,0)으로 초기화된 RGB 이미지 생성
mask_rgb = np.zeros((pixel_array.shape[0], pixel_array.shape[1], 3), dtype=np.uint8)
nii_files = list(case_dir.glob('*.nii.gz'))
for nii_path in nii_files:
class_name = nii_path.name.split('.')[0]
if class_name not in COLOR_MAP:
continue
color = COLOR_MAP[class_name] # (R, G, B)
# OpenCV는 BGR 사용하므로 순서 변경
color_bgr = (color[2], color[1], color[0])
try:
img_nii = nib.load(str(nii_path))
data = img_nii.get_fdata()
# 3D -> 2D Projection
proj = np.max(data, axis=2).T
# 리사이즈
if proj.shape != (mask_rgb.shape[0], mask_rgb.shape[1]):
proj = cv2.resize(proj, (mask_rgb.shape[1], mask_rgb.shape[0]), interpolation=cv2.INTER_NEAREST)
# 마스크 그리기
# 해당 클래스 영역을 해당 색상으로 칠함
mask_rgb[proj > 0] = color_bgr
except Exception as e:
print(f"Error processing NII {nii_path}: {e}")
# 마스크 저장 (PNG)
cv2.imwrite(str(SEG_CLASS_DIR / f"{case_name}.png"), mask_rgb)
# 파일 목록에 추가
file_list.append(case_name)
print(f"Processed: {case_name}")
def create_labelmap():
"""labelmap.txt 생성"""
with open(OUTPUT_DIR / 'labelmap.txt', 'w') as f:
f.write("# label:color_rgb:parts:actions\n")
# 정의된 순서대로 작성 (이름 정렬)
for name in sorted(COLOR_MAP.keys()):
r, g, b = COLOR_MAP[name]
f.write(f"{name}:{r},{g},{b}::\n")
# 배경 추가
f.write("background:0,0,0::\n")
def main():
2025-12-02 13:05:13 +09:00
# 디렉토리 생성 (타임스탬프 폴더이므로 삭제 불필요)
JPEG_DIR.mkdir(parents=True, exist_ok=True)
SEG_CLASS_DIR.mkdir(parents=True, exist_ok=True)
IMAGE_SETS_DIR.mkdir(parents=True, exist_ok=True)
2025-12-02 13:05:13 +09:00
print(f"Converting to CVAT Segmentation Mask 1.1 format...")
print(f"Output Directory: {OUTPUT_DIR}")
file_list = []
# 데이터 탐색
2025-12-02 13:05:13 +09:00
for d in DATA_ROOT.iterdir():
# 결과 폴더, 숨김 폴더, cvat 관련 폴더 제외
if d.is_dir() and not d.name.startswith('.') and OUTPUT_ROOT_NAME not in d.name and 'cvat' not in d.name and 'segmask' not in d.name:
if list(d.glob('*.dcm')):
process_case(d, file_list)
# default.txt 생성
with open(IMAGE_SETS_DIR / 'default.txt', 'w') as f:
for name in file_list:
f.write(f"{name}\n")
# labelmap.txt 생성
create_labelmap()
print("\nDone! Dataset created at:", OUTPUT_DIR.absolute())
print(f"Total cases: {len(file_list)}")
print("\n[중요] 업로드 시 주의사항:")
2025-12-02 13:05:13 +09:00
print(f"1. 생성된 폴더 '{OUTPUT_DIR.name}' 안으로 들어가서")
print("2. 모든 파일/폴더(JPEGImages, SegmentationClass, ImageSets, labelmap.txt)를 선택하고")
print("3. '압축하기(ZIP)'를 실행하세요.")
print("4. CVAT 포맷 선택: 'Segmentation Mask 1.1'")
if __name__ == '__main__':
main()