├── data └── dot.dcm ├── README.md ├── examples ├── dicom_wrapper.py ├── dcm_to_nrrd.py ├── apply_lut.py ├── resample_isotropically.py └── resample_tests.py ├── .gitignore └── notebooks └── ReadImage.ipynb /data/dot.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasteuwen/SimpleITK-examples/HEAD/data/dot.dcm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SimpleITK examples in Python 2 | ============================ 3 | 4 | In this repository you will find a couple of examples on how to use SimpleITK with Python. If you want any specific example, please open an issue. 5 | An open source viewer (and more) for medical images is [Slicer 3D](http://slicer.org). 6 | 7 | 8 | Currently we have the Python examples: 9 | - *dcm_to_nrrd.py*: reads a complete dicom series from a folder and converts this to a [nrrd](http://teem.sourceforge.net/nrrd/) file. Nrrd is a very convenient format to store medical images. The script reads the window and level tags of the DICOM series, and windows the intensity range to these. 10 | - *resample_isotropically.py*: An example to read in a image file, and resample the image to a new grid. 11 | - *resample_tests.py*: Several ways to downsample an image. 12 | - *apply_lut.py*: in some DICOM files there is a tag VOILUTFunction. This is an example on how to apply this. 13 | 14 | 15 | Jupyter notebooks: 16 | - *ReadImage.ipynb*: An example on how to read an image using SimpleITK. 17 | - *ReadDicomBrain.ipynb*: An example how to read a DICOM series using SimpleITK. 18 | -------------------------------------------------------------------------------- /examples/dicom_wrapper.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import SimpleITK as sitk 3 | import logging 4 | import dicom 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | def read_dicom_series(folder): 9 | """Read a folder with DICOM files and outputs a SimpleITK image. 10 | Assumes that there is only one DICOM series in the folder. 11 | 12 | Parameters 13 | ---------- 14 | folder : string 15 | Full path to folder with dicom files. 16 | 17 | Returns 18 | ------- 19 | SimpleITK image. 20 | """ 21 | reader = sitk.ImageSeriesReader() 22 | series_ids = reader.GetGDCMSeriesIDs(folder.encode('ascii')) 23 | 24 | assert len(series_ids) == 1, 'Assuming only one series per folder.' 25 | 26 | filenames = reader.GetGDCMSeriesFileNames(folder, series_ids[0], 27 | False, # useSeriesDetails 28 | False, # recursive 29 | True) # load sequences 30 | reader.SetFileNames(filenames) 31 | image = reader.Execute() 32 | 33 | logger.info('Read DICOM series from {} ({} files).\nSize: {}\n' 34 | 'Spacing: {}'.format(folder, len(filenames), 35 | image.GetSize(), image.GetSpacing())) 36 | 37 | return image 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # emacs 2 | .#* 3 | 4 | # Jupyter 5 | .ipynb_checkpoints 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | -------------------------------------------------------------------------------- /examples/dcm_to_nrrd.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import SimpleITK as sitk 3 | import dicom 4 | from tqdm import tqdm 5 | 6 | 7 | def dcm_to_nrrd(folder, to_path, intensity_windowing=True, compression=False): 8 | """Read a folder with DICOM files and convert to a nrrd file. 9 | Assumes that there is only one DICOM series in the folder. 10 | 11 | Parameters 12 | ---------- 13 | folder : string 14 | Full path to folder with dicom files. 15 | to_path : string 16 | Full path to output file (with .nrrd extension). As the file is 17 | outputted through SimpleITK, any supported format can be selected. 18 | intensity_windowing: bool 19 | If True, the dicom tags 'WindowCenter' and 'WindowWidth' are used 20 | to clip the image, and the resulting image will be rescaled to [0,255] 21 | and cast as uint8. 22 | compression : bool 23 | If True, the output will be compressed. 24 | """ 25 | reader = sitk.ImageSeriesReader() 26 | series_ids = reader.GetGDCMSeriesIDs(folder) 27 | 28 | assert len(series_ids) == 1, 'Assuming only one series per folder.' 29 | 30 | filenames = reader.GetGDCMSeriesFileNames(folder, series_ids[0]) 31 | reader.SetFileNames(filenames) 32 | image = reader.Execute() 33 | 34 | if intensity_windowing: 35 | dcm = dicom.read_file(filenames[0]) 36 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 37 | 'when `intensity_windowing=True`, dicom needs to have the `WindowCenter` and `WindowWidth` tags.' 38 | center = dcm.WindowCenter 39 | width = dcm.WindowWidth 40 | 41 | lower_bound = center - (width - 1)/2 42 | upper_bound = center + (width - 1)/2 43 | 44 | image = sitk.IntensityWindowing(image, 45 | lower_bound, upper_bound, 0, 255) 46 | image = sitk.Cast(image, sitk.sitkUInt8) # after intensity windowing, not necessarily uint8. 47 | 48 | writer = sitk.ImageFileWriter() 49 | if compression: 50 | writer.UseCompressionOn() 51 | 52 | writer.SetFileName(to_path) 53 | writer.Execute(image) 54 | 55 | 56 | def main(): 57 | folders = ['/data/folder1/', '/data/folder2'] 58 | for folder in tqdm(folders): 59 | dcm_to_nrrd(folder, '~', intensity_windowing=True) 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /examples/apply_lut.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """Some dicom images require a VOILUTFunction to be applied before display. 3 | This example script reads in the image, apply the function and writes out 4 | back to dicom. 5 | 6 | Reads: 7 | - http://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.11.2.html 8 | - http://dicom.nema.org/medical/dicom/2014c/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 9 | """ 10 | import dicom 11 | import numpy as np 12 | import SimpleITK as sitk 13 | 14 | 15 | def display_metadata(dcm_file, expl): 16 | """Reads a dicom file, and takes an explanation variable 17 | 18 | Parameters 19 | ---------- 20 | dcm_file : str 21 | path to dicom file 22 | expl : str 23 | name of corresponding Window Center Width Explanation. 24 | 25 | Returns 26 | ------- 27 | center, width, VOILUTFunction, total_bits 28 | """ 29 | dcm = dicom.read_file(dcm_file) 30 | explanation = dcm.WindowCenterWidthExplanation 31 | idx = explanation.index(expl) 32 | center = dcm.WindowCenter[idx] 33 | width = dcm.WindowWidth[idx] 34 | 35 | if hasattr(dcm, 'VOILUTFunction'): 36 | lut_func = dcm.VOILUTFunction.strip() 37 | else: 38 | lut_func = '' 39 | 40 | if dcm.SamplesPerPixel == 1: 41 | if dcm.PhotometricInterpretation == 'MONOCHROME1': 42 | raise NotImplementedError('The image needs to be inverted. Not implemented.') 43 | 44 | return center, width, lut_func, dcm.BitsStored 45 | 46 | 47 | def apply_window_level(dcm_file): 48 | image = sitk.ReadImage(dcm_file) 49 | center, width, lut_func, bits_stored = display_metadata(dcm_file, 'SOFTER') 50 | 51 | if lut_func == 'SIGMOID': 52 | print('Applying `SIGMOID`.') 53 | image = sigmoid_lut(image, bits_stored, center, width) 54 | elif lut_func == '': 55 | print('Applying `LINEAR`.') 56 | image = linear_lut(image, bits_stored, center, width) 57 | else: 58 | raise NotImplementedError('`VOILUTFunction` can only be `SIGMOID`, `LINEAR` or empty.') 59 | 60 | return image 61 | 62 | 63 | def sigmoid_lut(image, out_bits, center, width): 64 | """If VOILUTFunction in the dicom header is equal to SIGMOID, 65 | apply this function for visualisation. 66 | """ 67 | array = sitk.GetArrayFromImage(image).astype(np.float) 68 | array = np.round((2**out_bits - 1)/(1 + np.exp(-4*(array - center)/width))).astype(np.int) 69 | ret_image = sitk.GetImageFromArray(array) 70 | ret_image.SetSpacing(image.GetSpacing()) 71 | ret_image.SetOrigin(image.GetOrigin()) 72 | ret_image.SetDirection(image.GetDirection()) 73 | 74 | return ret_image 75 | 76 | 77 | def linear_lut(image, out_bits, center, width): 78 | """If VOILUTFunction in the dicom header is equal to LINEAR_EXACT, 79 | apply this function for visualisation. 80 | """ 81 | lower_bound = center - (width - 1)/2 82 | upper_bound = center + (width - 1)/2 83 | 84 | min_max = sitk.MinimumMaximumImageFilter() 85 | min_max.Execute(image) 86 | 87 | image = sitk.IntensityWindowing(image, 88 | lower_bound, upper_bound, 89 | min_max.GetMinimum(), 90 | min_max.GetMaximum()) 91 | 92 | return image 93 | 94 | 95 | def normalize_dicom(dcm_file, dcm_out): 96 | image = apply_window_level(dcm_file) 97 | sitk.WriteImage(image, dcm_out) 98 | -------------------------------------------------------------------------------- /examples/resample_isotropically.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """An example to read in a image file, and resample the image to a new grid. 3 | """ 4 | 5 | import SimpleITK as sitk 6 | import os 7 | from glob import glob 8 | from tqdm import tqdm 9 | import numpy as np 10 | 11 | 12 | # https://github.com/SimpleITK/SlicerSimpleFilters/blob/master/SimpleFilters/SimpleFilters.py 13 | _SITK_INTERPOLATOR_DICT = { 14 | 'nearest': sitk.sitkNearestNeighbor, 15 | 'linear': sitk.sitkLinear, 16 | 'gaussian': sitk.sitkGaussian, 17 | 'label_gaussian': sitk.sitkLabelGaussian, 18 | 'bspline': sitk.sitkBSpline, 19 | 'hamming_sinc': sitk.sitkHammingWindowedSinc, 20 | 'cosine_windowed_sinc': sitk.sitkCosineWindowedSinc, 21 | 'welch_windowed_sinc': sitk.sitkWelchWindowedSinc, 22 | 'lanczos_windowed_sinc': sitk.sitkLanczosWindowedSinc 23 | } 24 | 25 | 26 | def resample_sitk_image(sitk_image, spacing=None, interpolator=None, 27 | fill_value=0): 28 | """Resamples an ITK image to a new grid. If no spacing is given, 29 | the resampling is done isotropically to the smallest value in the current 30 | spacing. This is usually the in-plane resolution. If not given, the 31 | interpolation is derived from the input data type. Binary input 32 | (e.g., masks) are resampled with nearest neighbors, otherwise linear 33 | interpolation is chosen. 34 | 35 | Parameters 36 | ---------- 37 | sitk_image : SimpleITK image or str 38 | Either a SimpleITK image or a path to a SimpleITK readable file. 39 | spacing : tuple 40 | Tuple of integers 41 | interpolator : str 42 | Either `nearest`, `linear` or None. 43 | fill_value : int 44 | 45 | Returns 46 | ------- 47 | SimpleITK image. 48 | """ 49 | 50 | if isinstance(sitk_image, str): 51 | sitk_image = sitk.ReadImage(sitk_image) 52 | num_dim = sitk_image.GetDimension() 53 | 54 | if not interpolator: 55 | interpolator = 'linear' 56 | pixelid = sitk_image.GetPixelIDValue() 57 | 58 | if pixelid not in [1, 2, 4]: 59 | raise NotImplementedError( 60 | 'Set `interpolator` manually, ' 61 | 'can only infer for 8-bit unsigned or 16, 32-bit signed integers') 62 | if pixelid == 1: # 8-bit unsigned int 63 | interpolator = 'nearest' 64 | 65 | orig_pixelid = sitk_image.GetPixelIDValue() 66 | orig_origin = sitk_image.GetOrigin() 67 | orig_direction = sitk_image.GetDirection() 68 | orig_spacing = np.array(sitk_image.GetSpacing()) 69 | orig_size = np.array(sitk_image.GetSize(), dtype=np.int) 70 | 71 | if not spacing: 72 | min_spacing = orig_spacing.min() 73 | new_spacing = [min_spacing]*num_dim 74 | else: 75 | new_spacing = [float(s) for s in spacing] 76 | 77 | assert interpolator in _SITK_INTERPOLATOR_DICT.keys(),\ 78 | '`interpolator` should be one of {}'.format(_SITK_INTERPOLATOR_DICT.keys()) 79 | 80 | sitk_interpolator = _SITK_INTERPOLATOR_DICT[interpolator] 81 | 82 | new_size = orig_size*(orig_spacing/new_spacing) 83 | new_size = np.ceil(new_size).astype(np.int) # Image dimensions are in integers 84 | new_size = [int(s) for s in new_size] # SimpleITK expects lists, not ndarrays 85 | 86 | resample_filter = sitk.ResampleImageFilter() 87 | 88 | resampled_sitk_image = resample_filter.Execute(sitk_image, 89 | new_size, 90 | sitk.Transform(), 91 | sitk_interpolator, 92 | orig_origin, 93 | new_spacing, 94 | orig_direction, 95 | fill_value, 96 | orig_pixelid) 97 | 98 | return resampled_sitk_image 99 | 100 | def main(REGEX_TO_IMAGES, WRITE_TO): 101 | for image in tqdm(glob(REGEX_TO_IMAGES)): 102 | tqdm.write('Resampling {}'.format(image)) 103 | resampled_image = resample_sitk_image( 104 | image, spacing=[1, 1, 1], 105 | interpolator=None, fill_value=-1000 106 | ) 107 | base_name = 'resampled_' + os.path.basename(image) 108 | write_to = os.path.join(WRITE_TO, base_name) 109 | tqdm.write('Writing resampled image to {}'.format(write_to)) 110 | sitk.WriteImage(resampled_image, write_to, True) # True = compress image. 111 | 112 | 113 | if __name__ == '__main__': 114 | base_path = './datasets/' 115 | write_path = './resampled/' 116 | 117 | subpaths = ['path1/mhd/*.mhd', 'path2/*.nii.gz'] 118 | write_paths = ['path1/', 'path2/'] 119 | 120 | for i, sub in enumerate(subpaths): 121 | main(base_path + sub, write_path + write_paths[i]) 122 | 123 | -------------------------------------------------------------------------------- /examples/resample_tests.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """In this example we generate a couple of different ways to downsample 3 | a medical image. 4 | 5 | 1) Crop to window and level, with rescaling to uint8. 6 | 2) Crop to window and level. 7 | 3) Resample linearly without filter. 8 | 4) Resample with Gaussian filter, and then linearly. 9 | 5) Resample to a integer multiplier. 10 | 11 | 1) and 2) are meant to compare if the noise and level of detail in 12 | the image gets destroyed because of the quantization of the intensity values. 13 | whereas 3,4 and 5) are meant to compare the different resampling modes. 14 | 15 | The images in this example have a spacing of (0.085, 0.085, 1.0). 16 | 17 | Reads: 18 | - http://dicom.nema.org/MEDICAL/dicom/2014c/output/chtml/part03/sect_C.11.2.html 19 | """ 20 | import SimpleITK as sitk 21 | import os 22 | from glob import glob 23 | import dicom 24 | from dicom_wrapper import read_dicom_series 25 | from resample_isotropically import resample_sitk_image 26 | 27 | import logging 28 | logger = logging.getLogger(__name__) 29 | logger.setLevel(logging.DEBUG) 30 | 31 | 32 | def resample_type1(image_folder): 33 | image = read_dicom_series(image_folder) 34 | dcm_file = glob(os.path.join(image_folder, '*.dcm'))[0] 35 | dcm = dicom.read_file(dcm_file) 36 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 37 | 'Needs window and level for this resampling.' 38 | center = dcm.WindowCenter 39 | width = dcm.WindowWidth 40 | lower_bound = center - (width - 1)/2 41 | upper_bound = center + (width - 1)/2 42 | 43 | min_max = sitk.MinimumMaximumImageFilter() 44 | min_max.Execute(image) 45 | 46 | image = sitk.IntensityWindowing(image, 47 | lower_bound, upper_bound, 48 | min_max.GetMinimum(), 49 | min_max.GetMaximum()) 50 | 51 | writer = sitk.ImageFileWriter() 52 | writer.SetFileName('im_resampled_window_level_uint8.nrrd') 53 | writer.Execute(image) 54 | 55 | 56 | def resample_type2(image_folder): 57 | image = read_dicom_series(image_folder) 58 | dcm_file = glob(os.path.join(image_folder, '*.dcm'))[0] 59 | dcm = dicom.read_file(dcm_file) 60 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 61 | 'Needs window and level for this resampling.' 62 | center = dcm.WindowCenter 63 | width = dcm.WindowWidth 64 | lower_bound = center - (width - 1)/2 65 | upper_bound = center + (width - 1)/2 66 | 67 | image = sitk.IntensityWindowing(image, 68 | lower_bound, upper_bound, 0, 255) 69 | image = sitk.Cast(image, sitk.sitkUInt8) 70 | 71 | writer = sitk.ImageFileWriter() 72 | writer.SetFileName('im_resampled_window_level_full_range.nrrd') 73 | writer.Execute(image) 74 | 75 | 76 | def resample_type3(image_folder): 77 | image = read_dicom_series(image_folder) 78 | dcm_file = glob(os.path.join(image_folder, '*.dcm'))[0] 79 | dcm = dicom.read_file(dcm_file) 80 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 81 | 'Needs window and level for this resampling.' 82 | center = dcm.WindowCenter 83 | width = dcm.WindowWidth 84 | lower_bound = center - (width - 1)/2 85 | upper_bound = center + (width - 1)/2 86 | 87 | image = sitk.IntensityWindowing(image, 88 | lower_bound, upper_bound, 0, 255) 89 | 90 | image = sitk.Cast(image, sitk.sitkUInt8) 91 | resampled_image = resample_sitk_image( 92 | image, spacing=(0.2, 0.2, 1), 93 | interpolator='linear', fill_value=0 94 | ) 95 | sitk.WriteImage(resampled_image, 'im_resampled_linear.nrrd') 96 | 97 | 98 | def resample_type4(image_folder, sigma): 99 | """Apply a Gaussian filter before downsampling. 100 | By the Nyquist-Shannon theorem we can only reliably reproduce 101 | information up to the Nyquist frequency. As this frequency 102 | is scaled by the same factor as the downsampling, we cannot have 103 | frequencies above alpha*Nyquist frequence in the image. 104 | We use a Gaussian filter as a low-pass filter. 105 | 106 | As the Gaussian is the same in the frequency domain, 107 | we can reduce the amount of information in a 85um grid, 108 | to one of 200um. 109 | Approximately, sigma = 85/200 *1/(2*sqrt(pi)), where the normalization 110 | is from the choice of Gaussian. Hence sigma ~ 0.11, or ~1.412 image units. 111 | """ 112 | image = read_dicom_series(image_folder) 113 | # 0,1,2 <-> (x,y,z) 114 | image = sitk.RecursiveGaussian(image, sigma=sigma*0.2, direction=0) 115 | image = sitk.RecursiveGaussian(image, sigma=sigma*0.2, direction=1) 116 | 117 | dcm_file = glob(os.path.join(image_folder, '*.dcm'))[0] 118 | dcm = dicom.read_file(dcm_file) 119 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 120 | 'Needs window and level for this resampling.' 121 | center = dcm.WindowCenter 122 | width = dcm.WindowWidth 123 | lower_bound = center - (width - 1)/2 124 | upper_bound = center + (width - 1)/2 125 | 126 | image = sitk.IntensityWindowing(image, 127 | lower_bound, upper_bound, 0, 255) 128 | image = sitk.Cast(image, sitk.sitkUInt8) 129 | 130 | resampled_image = resample_sitk_image( 131 | image, spacing=(0.2, 0.2, 1), 132 | interpolator='linear', fill_value=0) 133 | sitk.WriteImage(resampled_image, 'im_resampled_gaussian_sigma_{}.nrrd'.format(sigma)) 134 | 135 | 136 | def resample_type5(image_folder): 137 | image = read_dicom_series(image_folder) 138 | dcm_file = glob(os.path.join(image_folder, '*.dcm'))[0] 139 | dcm = dicom.read_file(dcm_file) 140 | assert hasattr(dcm, 'WindowCenter') and hasattr(dcm, 'WindowWidth'),\ 141 | 'Needs window and level for this resampling.' 142 | center = dcm.WindowCenter 143 | width = dcm.WindowWidth 144 | lower_bound = center - (width - 1)/2 145 | upper_bound = center + (width - 1)/2 146 | 147 | image = sitk.IntensityWindowing(image, 148 | lower_bound, upper_bound, 0, 255) 149 | image = sitk.Cast(image, sitk.sitkUInt8) 150 | resampled_image = resample_sitk_image( 151 | image, spacing=(0.255, 0.255, 1), # 0.085*3 152 | interpolator='linear', fill_value=0) 153 | sitk.WriteImage(resampled_image, 'im_resampled_integer_multiple.nrrd') 154 | 155 | 156 | if __name__ == '__main__': 157 | dcm_folder = 'path_to_folder' 158 | resample_type1(dcm_folder) 159 | resample_type2(dcm_folder) 160 | resample_type3(dcm_folder) 161 | resample_type4(dcm_folder, 0.1) 162 | resample_type4(dcm_folder, 0.2) 163 | resample_type4(dcm_folder, 0.5) 164 | resample_type4(dcm_folder, 0.9) 165 | resample_type4(dcm_folder, 1) 166 | resample_type5(dcm_folder) 167 | -------------------------------------------------------------------------------- /notebooks/ReadImage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import SimpleITK as sitk\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "%matplotlib inline" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "The SimpleITK function ReadImage() can read single file images. Usually SimpleITK can correctly determine the file type from the extension and file itself." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "image = sitk.ReadImage('../data/dot.dcm')" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "The type of `image` is an instance of the SimpleITK image. We can request several of its properties. Try tab completion or `help(image)` for more examples." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 5, 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "text/plain": [ 45 | "(21, 21, 1)" 46 | ] 47 | }, 48 | "execution_count": 5, 49 | "metadata": {}, 50 | "output_type": "execute_result" 51 | } 52 | ], 53 | "source": [ 54 | "image.GetSize()" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "SimpleITK is a 'simple' procedural wrapper around ITK which can be called from Python and other scripting languages. SimpleITK provides a convenient interface to numpy arrays." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 8, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stdout", 71 | "output_type": "stream", 72 | "text": [ 73 | "`array` has type: \n", 74 | "(1, 21, 21, 3)\n" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "array = sitk.GetArrayFromImage(image)\n", 80 | "print('`array` has type: {}'.format(type(array)))" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "Let us check out the array size." 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 9, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "data": { 97 | "text/plain": [ 98 | "(1, 21, 21, 3)" 99 | ] 100 | }, 101 | "execution_count": 9, 102 | "metadata": {}, 103 | "output_type": "execute_result" 104 | } 105 | ], 106 | "source": [ 107 | "array.shape" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "The first axis is the number of slices, in this case 1. Many medical image formats are 3D and would have the number of slices on the first axis. The last axis is the number of channels. The image `dot.dcm` is an artificial example of an RGB image saved in the DICOM standard." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 14, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "data": { 124 | "text/plain": [ 125 | "array([[255, 255, 255, 255, 255, 255, 255, 255, 255, 255],\n", 126 | " [255, 255, 255, 255, 255, 255, 255, 255, 255, 0],\n", 127 | " [255, 255, 255, 255, 255, 255, 0, 0, 0, 0],\n", 128 | " [255, 255, 255, 255, 255, 0, 0, 0, 0, 0],\n", 129 | " [255, 255, 255, 255, 0, 0, 0, 0, 0, 0],\n", 130 | " [255, 255, 255, 0, 0, 0, 0, 0, 0, 0],\n", 131 | " [255, 255, 0, 0, 0, 0, 0, 0, 0, 0],\n", 132 | " [255, 255, 0, 0, 0, 0, 0, 0, 0, 0],\n", 133 | " [255, 255, 0, 0, 0, 0, 0, 0, 0, 0],\n", 134 | " [255, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)" 135 | ] 136 | }, 137 | "execution_count": 14, 138 | "metadata": {}, 139 | "output_type": "execute_result" 140 | } 141 | ], 142 | "source": [ 143 | "array[0, 0:10, 0:10, 1]" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "As a `numpy` array we can easily plot the image." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 18, 156 | "metadata": {}, 157 | "outputs": [ 158 | { 159 | "data": { 160 | "image/png": "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\n", 161 | "text/plain": [ 162 | "" 163 | ] 164 | }, 165 | "metadata": {}, 166 | "output_type": "display_data" 167 | } 168 | ], 169 | "source": [ 170 | "fig = plt.figure(figsize=(6, 6))\n", 171 | "plt.imshow(array[0]);" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": { 178 | "collapsed": true 179 | }, 180 | "outputs": [], 181 | "source": [] 182 | } 183 | ], 184 | "metadata": { 185 | "kernelspec": { 186 | "display_name": "Python 2", 187 | "language": "python", 188 | "name": "python2" 189 | }, 190 | "language_info": { 191 | "codemirror_mode": { 192 | "name": "ipython", 193 | "version": 2 194 | }, 195 | "file_extension": ".py", 196 | "mimetype": "text/x-python", 197 | "name": "python", 198 | "nbconvert_exporter": "python", 199 | "pygments_lexer": "ipython2", 200 | "version": "2.7.12+" 201 | } 202 | }, 203 | "nbformat": 4, 204 | "nbformat_minor": 2 205 | } 206 | --------------------------------------------------------------------------------