Dicom Resample and Save as nii

"""
convert dcm 2 nii
1. read dicom series
2. clarify and sort series
3. skip scout series
4. convert series to 3d
5. resample and save as nii

## usage ##
---------------------------------
-- parent(root)
  |
  -- Folder1(subdir)
  |    |
  |    -- DICOMS
       /  -- 4d-dicom file
  -- Folder2
      |
      -- DICOMS
---------------------------------
input: root
output: subdir, include several nii files determined by series

dependencies:
python 3.9.2 (used for log)
SimpleITK
numpy

logging:
support output to console and file

"""

import SimpleITK as sitk
import numpy as np
import logging
from pathlib import Path


def set_logger(mylogger, file):
    mylogger.setLevel(logging.DEBUG)
    # create file handler which logs even debug messages
    fh = logging.FileHandler(file)
    fh.setLevel(logging.DEBUG)
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    # create formatter and add it to the handlers
    formatter = logging.Formatter('%(asctime)s [%(name)s] [%(levelname)s] %(message)s')
    ch.setFormatter(formatter)
    fh.setFormatter(formatter)
    # add the handlers to logger
    mylogger.addHandler(ch)
    mylogger.addHandler(fh)


# resample image using new spacing
def resample_image(itk_image, out_spacing):
    original_spacing = itk_image.GetSpacing()
    original_size = itk_image.GetSize()

    # image size according to new spacing
    out_size = [
        int(np.round(original_size[0] * original_spacing[0] / out_spacing[0])),
        int(np.round(original_size[1] * original_spacing[1] / out_spacing[1])),
        int(np.round(original_size[2] * original_spacing[2] / out_spacing[2]))
    ]

    resample = sitk.ResampleImageFilter()
    resample.SetOutputSpacing(out_spacing)
    resample.SetSize(out_size)
    resample.SetOutputDirection(itk_image.GetDirection())
    resample.SetOutputOrigin(itk_image.GetOrigin())
    resample.SetTransform(sitk.Transform())
    resample.SetDefaultPixelValue(itk_image.GetPixelIDValue())

    resample.SetInterpolator(sitk.sitkBSpline)
    # print(resample)
    return resample.Execute(itk_image)


# read and save nii file
# def resample_nii(input_path, out_file):
#     original_img = sitk.ReadImage(input_path)
#     resample_nii_image(original_img, out_file)


def resample_nii_image(nii_img, out_file):
    pix_spacing = nii_img.GetSpacing()
    logger.info('Original image Spacing:')
    logger.info(pix_spacing)
    logger.info('Original image Size:')
    logger.info(nii_img.GetSize())

    new_pix_spacing = [pix_spacing[0], pix_spacing[0], pix_spacing[0]]
    resample_img = resample_image(nii_img, new_pix_spacing)
    logger.info('Resampled image Spacing:')
    logger.info(resample_img.GetSpacing())
    logger.info('Resampled image Size:')
    logger.info(resample_img.GetSize())

    sitk.WriteImage(resample_img, out_file)


def convert_dcm2nii_resample(directory_path):
    reader = sitk.ImageSeriesReader()
    reader.MetaDataDictionaryArrayUpdateOn()
    reader.LoadPrivateTagsOn()
    series_ids = sitk.ImageSeriesReader.GetGDCMSeriesIDs(directory_path)
    arr_index = 0
    for series_ID in series_ids:
        arr_index += 1
        logger.info("series id: " + series_ID)
        dicom_names = reader.GetGDCMSeriesFileNames(directory_path, series_ID)
        if len(dicom_names) < 5:
            logger.info('skip series less than 5')
            continue
        # if 3 == arr_index:
        #     dicom_names = dicom_names[1409:2152]
        # else:
        #     print("skip series number: ", str(arr_index))
        #     continue
        reader.SetFileNames(dicom_names)
        image3d = reader.Execute()

        series_number = reader.GetMetaData(0, '0020|0011')
        # series_description = reader.GetMetaData(0, '0008|103e')
        # if series_number == '3 ':
        #     dicom_names = dicom_names[1409:2152]
        #     reader.SetFileNames(dicom_names)
        #     image3d = reader.Execute()
        # else:
        #     print("skip series number: ", str(series_number))
        #     continue
        # determine if it is scout protocol
        slice_number = 0
        # slice_keys = reader.GetMetaDataKeys(slice_number)
        # reader.HasMetaDataKey(slice_number, '0008|0008')
        # image type
        image_type = reader.GetMetaData(slice_number, '0008|0008')

        scout_flag = "LOCALIZER"
        if scout_flag in image_type:
            logger.info("skip scout")
            continue

        # saved path
        save_path = Path(directory_path).parent.as_posix()
        # save_path = save_path + "//" + str(series_number)+ "_" + str(series_description) + ".nii.gz"
        save_path = save_path + "//" + str(series_number) + "_" + ".nii.gz"
        # save_path = re.sub(r"[\n\t\s]*", "", save_path)
        # save_path1 = save_path.strip()
        # resample
        resample_nii_image(image3d, save_path)
        logger.info("saved file: ")
        logger.info(save_path)


def get_paths(input_path, out_paths):
    path = Path(input_path)
    for subdir in path.iterdir():
        # check if has nii file and skip
        if subdir.is_file() and '.nii.gz' in subdir.name:
            return
    # recursive get paths
    for subdir in path.iterdir():
        if subdir.is_dir():
            get_paths(subdir, out_paths)
        else:
            if ".dcm" in subdir.name:
                out_paths.append(subdir.parent)
                break


def main():
    path = Path(r"C:\Data\mydata\SID221153")
    logger.info("--------------------task begin-----------------------")
    input_dir = []
    get_paths(path, input_dir)
    logger.info("folder size: " + str(len(input_dir)))
    logger.info(input_dir)

    for convert_dir in input_dir:
        logger.info("---begin dir---")
        logger.info(convert_dir)
        convert_dcm2nii_resample(Path(convert_dir).as_posix())
        logger.info("---finish dir---")
    logger.info("--------------------task finish-----------------------")


logger = logging.getLogger('dcm2nii')
set_logger(logger, "log/convert_test.log")

if __name__ == '__main__':
    main()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值