In [2]:
import vtk
import itk
import SimpleITK as sitk
import pydicom as dicom
import nibabel as nib

import matplotlib.pyplot as plt
import numpy as np
import cv2
import skimage

import os
import sys
In [3]:
def read_dicom_series_pydicom(dir_path):
    """
    Reads dicom series from directory with pydicom

    :param dir_path: string, directory with dicom series
    :return: list of dicom images
    """
    files = os.listdir(dir_path)
    images = []
    for f in files:
        filepath = os.path.join(dir_path, f)
        if os.path.isfile(filepath):
            image = dicom.read_file(filepath)
            images.append(image)

    return images
In [ ]:
def write_dicom_series(images, dir):
    """
    Saves dicom series to directory
    
    :param images: list of pydicom images
    :param dir: path to directory where it should be saved
    :return: 
    """
    for image in images:
        f_name = image.filename.split('/')[-1]
        image.save_as(f'{dir}{f_name}')
In [4]:
# read dicoms from path
dicoms = read_dicom_series_pydicom('./3/')
In [5]:
# informations of photo (tags)
print(dicoms[0])

# get numpy array from dicom image
dcm_arr = dicoms[100].pixel_array

# show image in matplotlib
plt.figure(figsize=(10, 10))
plt.imshow(dcm_arr, cmap='gray')
(0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
(0008, 0008) Image Type                          CS: ['ORIGINAL', 'PRIMARY', 'AXIAL']
(0008, 0016) SOP Class UID                       UI: CT Image Storage
(0008, 0018) SOP Instance UID                    UI: 2.16.840.1.113669.632.16.995.5352.4836.20181009180220405
(0008, 0020) Study Date                          DA: '20181009'
(0008, 0021) Series Date                         DA: '20181009'
(0008, 0022) Acquisition Date                    DA: '20181009'
(0008, 0023) Content Date                        DA: '20181009'
(0008, 0030) Study Time                          TM: '200219'
(0008, 0031) Series Time                         TM: '200219'
(0008, 0032) Acquisition Time                    TM: '200219'
(0008, 0033) Content Time                        TM: '200220'
(0008, 0050) Accession Number                    SH: '7694'
(0008, 0060) Modality                            CS: 'CT'
(0008, 0070) Manufacturer                        LO: 'TOSHIBA'
(0008, 0080) Institution Name                    LO: 'Institution'
(0008, 0090) Referring Physician's Name          PN: ''
(0008, 1010) Station Name                        SH: 'Station Name'
(0008, 1090) Manufacturer's Model Name           LO: 'Aquilion Prime SP'
(0010, 0010) Patient's Name                      PN: 'Anonymous^Patient^2951'
(0010, 0020) Patient ID                          LO: 'Pat9845'
(0010, 0030) Patient's Birth Date                DA: '18900101'
(0010, 0040) Patient's Sex                       CS: 'O'
(0018, 0010) Contrast/Bolus Agent                LO: 'Contrast Bolus Agent'
(0018, 0015) Body Part Examined                  CS: 'BBA'
(0018, 0022) Scan Options                        CS: 'HELICAL_CT'
(0018, 0050) Slice Thickness                     DS: "3.0"
(0018, 0060) KVP                                 DS: "100"
(0018, 0090) Data Collection Diameter            DS: "500.00"
(0018, 1000) Device Serial Number                LO: 'DevID3627'
(0018, 1020) Software Version(s)                 LO: 'V8.41GR005'
(0018, 1030) Protocol Name                       LO: 'Protocol Name'
(0018, 1041) Contrast/Bolus Volume               DS: "150.0"
(0018, 1042) Contrast/Bolus Start Time           TM: '082804.200'
(0018, 1100) Reconstruction Diameter             DS: "500.00"
(0018, 1120) Gantry/Detector Tilt                DS: "+0.0"
(0018, 1130) Table Height                        DS: "+146.00"
(0018, 1140) Rotation Direction                  CS: 'CW'
(0018, 1150) Exposure Time                       IS: "500"
(0018, 1151) X-Ray Tube Current                  IS: "173"
(0018, 1152) Exposure                            IS: "86"
(0018, 1160) Filter Type                         SH: 'LARGE'
(0018, 1170) Generator Power                     IS: "17"
(0018, 1190) Focal Spot(s)                       DS: ['0.9', '0.8']
(0018, 1210) Convolution Kernel                  SH: 'FC08'
(0018, 5100) Patient Position                    CS: 'FFS'
(0018, 9302) Acquisition Type                    CS: 'SPIRAL'
(0018, 9305) Revolution Time                     FD: 0.5
(0018, 9306) Single Collimation Width            FD: 0.5
(0018, 9307) Total Collimation Width             FD: 40.0
(0018, 9310) Table Feed per Rotation             FD: 25.5
(0018, 9311) Spiral Pitch Factor                 FD: 0.638
(0018, 9318) Reconstruction Target Center (Patie FD: [0.0, 0.0, 1509.0]
(0018, 9323) Exposure Modulation Type            CS: '3D'
(0018, 9324) Estimated Dose Saving               FD: 43.62
(0018, 9327) Table Position                      FD: 270.0
(0018, 9334) Fluoroscopy Flag                    CS: 'NO'
(0018, 9345) CTDIvol                             FD: 2.4
(0020, 000d) Study Instance UID                  UI: 2.16.840.1.113669.632.16.984.5352.4836.20181009180219413
(0020, 000e) Series Instance UID                 UI: 2.16.840.1.113669.632.16.983.5352.4836.20181009180219581
(0020, 0010) Study ID                            SH: 'Std4136'
(0020, 0011) Series Number                       IS: "3"
(0020, 0012) Acquisition Number                  IS: "5"
(0020, 0013) Instance Number                     IS: "91"
(0020, 0020) Patient Orientation                 CS: ['L', 'P']
(0020, 0032) Image Position (Patient)            DS: ['-249.5117', '-249.5117', '1509.00']
(0020, 0037) Image Orientation (Patient)         DS: ['1.00000', '0.00000', '0.00000', '0.00000', '1.00000', '0.00000']
(0020, 0052) Frame of Reference UID              UI: 2.16.840.1.113669.632.16.982.5352.4836.20181009180219415
(0020, 1040) Position Reference Indicator        LO: ''
(0020, 1041) Slice Location                      DS: "+270.00"
(0028, 0002) Samples per Pixel                   US: 1
(0028, 0004) Photometric Interpretation          CS: 'MONOCHROME2'
(0028, 0010) Rows                                US: 512
(0028, 0011) Columns                             US: 512
(0028, 0030) Pixel Spacing                       DS: ['0.976', '0.976']
(0028, 0100) Bits Allocated                      US: 16
(0028, 0101) Bits Stored                         US: 16
(0028, 0102) High Bit                            US: 15
(0028, 0103) Pixel Representation                US: 1
(0028, 1050) Window Center                       DS: "70"
(0028, 1051) Window Width                        DS: "500"
(0028, 1052) Rescale Intercept                   DS: "0"
(0028, 1053) Rescale Slope                       DS: "1"
(0040, 2017) Filler Order Number / Imaging Servi LO: ''
(7fe0, 0010) Pixel Data                          OW: Array of 524288 bytes
Out[5]:
<matplotlib.image.AxesImage at 0x19c61066fd0>
In [6]:
# read nifti image from directory 
filename = os.path.join(nib.testing.data_path, 'example4d.nii.gz')
nii_image = nib.load(filename)

# get numpy ndarray from nifti image
nii_image_arr = nii_image.get_data() 
In [7]:
# print information and tags
print(nii_image)

plt.figure(figsize=(10, 10))
plt.imshow(nii_image_arr[:, :, 0, 0], cmap='gray')
<class 'nibabel.nifti1.Nifti1Image'>
data shape (128, 96, 24, 2)
affine: 
[[-2.00000000e+00  6.71471565e-19  9.08102451e-18  1.17855103e+02]
 [-6.71471565e-19  1.97371149e+00 -3.55528235e-01 -3.57229424e+01]
 [ 8.25548089e-18  3.23207617e-01  2.17108178e+00 -7.24879837e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
metadata:
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 57
dim             : [  4 128  96  24   2   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : int16
bitpix          : 16
slice_start     : 0
pixdim          : [-1.000000e+00  2.000000e+00  2.000000e+00  2.199999e+00  2.000000e+03
  1.000000e+00  1.000000e+00  1.000000e+00]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 23
slice_code      : unknown
xyzt_units      : 10
cal_max         : 1162.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'FSL3.3\x00 v2.25 NIfTI-1 Single file format'
aux_file        : b''
qform_code      : scanner
sform_code      : scanner
quatern_b       : -1.9451068e-26
quatern_c       : -0.9967085
quatern_d       : -0.08106874
qoffset_x       : 117.8551
qoffset_y       : -35.722942
qoffset_z       : -7.2487984
srow_x          : [-2.0000000e+00  6.7147157e-19  9.0810245e-18  1.1785510e+02]
srow_y          : [-6.7147157e-19  1.9737115e+00 -3.5552824e-01 -3.5722942e+01]
srow_z          : [ 8.2554809e-18  3.2320762e-01  2.1710818e+00 -7.2487984e+00]
intent_name     : b''
magic           : b'n+1'
Out[7]:
<matplotlib.image.AxesImage at 0x19c6121b278>
In [8]:
def read_dicom(img_path):
    """
    Reads dicom series from directory with SimpleITK
    :param img_path: path to directory with a series
    :return: list of dicom images
    """
    reader = sitk.ImageSeriesReader()
    names = reader.GetGDCMSeriesFileNames(img_path)
    reader.SetFileNames(names)
    reader.MetaDataDictionaryArrayUpdateOn()
    reader.LoadPrivateTagsOn()
    image = reader.Execute()
    return image
In [9]:
# read dicoms and get numpy ndarray from SimpleITK Image
sitk_dicoms = read_dicom('./3/')
sitk_dicom_arr = sitk.GetArrayFromImage(sitk_dicoms)
In [10]:
# print information from SimpleITK Image
print(sitk_dicoms)

plt.figure(figsize=(10, 10))
plt.imshow(sitk_dicom_arr[0], cmap='gray')
Image (0000019C44585A80)
  RTTI typeinfo:   class itk::Image<short,3>
  Reference Count: 1
  Modified Time: 8723
  Debug: Off
  Object Name: 
  Observers: 
    none
  Source: (none)
  Source output name: (none)
  Release Data: Off
  Data Released: False
  Global Release Data: Off
  PipelineMTime: 932
  UpdateMTime: 8722
  RealTimeStamp: 0 seconds 
  LargestPossibleRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [512, 512, 234]
  BufferedRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [512, 512, 234]
  RequestedRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [512, 512, 234]
  Spacing: [0.976, 0.976, 3]
  Origin: [-249.512, -249.512, 810]
  Direction: 
1 0 0
0 1 0
0 0 1

  IndexToPointMatrix: 
0.976 0 0
0 0.976 0
0 0 3

  PointToIndexMatrix: 
1.02459 0 0
0 1.02459 0
0 0 0.333333

  Inverse Direction: 
1 0 0
0 1 0
0 0 1

  PixelContainer: 
    ImportImageContainer (0000019C30004C00)
      RTTI typeinfo:   class itk::ImportImageContainer<unsigned __int64,short>
      Reference Count: 1
      Modified Time: 995
      Debug: Off
      Object Name: 
      Observers: 
        none
      Pointer: 0000019C694F5040
      Container manages memory: true
      Size: 61341696
      Capacity: 61341696

Out[10]:
<matplotlib.image.AxesImage at 0x19c61284358>
In [5]:
hounsfield_table = cv2.imread('hu_scale_table.png')
plt.figure(figsize=(15, 15))
plt.imshow(hounsfield_table)


def apply_hu(dicoms_list):
    """
    Applies Hounsfield units transformation to DICOM images.

    Args:
        dicoms_list (list): list of DICOM objects (pydicom.dataset.FileDataset).
    Returns:
        dicoms_hu (list): list of DICOM objects (pydicom.dataset.FileDataset).
    """

    dicoms_hu = []

    for (i, dcm) in enumerate(dicoms_list):
        
        try:
            slope = dcm.RescaleSlope
        except Exception as ex:
            sys.exit(1)
            
        try:    
            intercept = dcm.RescaleIntercept
        except Exception as ex:
            sys.exit(1)
            
        pixels = dicoms_list[i].pixel_array.copy()
        pixels = slope * pixels.astype(np.float64)

        pixels = pixels.astype(np.int16)
        pixels += np.int16(intercept)

        dicoms_hu.append(pixels)
    return dicoms_hu
In [13]:
# simple operators works on SimpleITK Images as on numpy arrays
s = sitk_dicoms**2
sx = sitk.GetArrayFromImage(s)

x = np.hstack((sx[0], sitk_dicom_arr[0]))

plt.figure(figsize=(20, 10))
plt.imshow(x, cmap='gray')
Out[13]:
<matplotlib.image.AxesImage at 0x19c780836a0>
In [ ]:
# applies hounsfield scale and threshold (bones and vessels on picture)
hu = apply_hu(dicoms)

threshold = hu.copy()
threshold = np.asarray(threshold)
threshold[threshold > 110] = 255
threshold[threshold <= 110] = 0
plt.figure(figsize=(10, 10))
plt.imshow(threshold[100], cmap='gray')
In [16]:
def show_3d_image_slices(image_array):
    """
    Displays 3D image as 2D slices

    :key k: left on trackbar
    :key l: right on trackbar

    :param image_array: 3D image in numpy.ndarray
    """
    
    def nothing(x):
        pass

    w, h = image_array[0].shape
    cv2.namedWindow('name', cv2.WINDOW_NORMAL)
    cv2.resizeWindow('name', w, h)
    cv2.createTrackbar('image_index', 'name', 0, len(image_array) - 1, nothing)

    while 1:
        img_index = cv2.getTrackbarPos('image_index', 'name')
        show_img = image_array[img_index]
        # show_img = show_img.astype(np.uint8)
        cv2.imshow('name', show_img)
        k = cv2.waitKey(1) & 0xFF
        if k == 27:
            break
        elif k == 107:
            if img_index > 0:
                cv2.setTrackbarPos('image_index', 'name', img_index - 1)
        elif k == 108:
            if img_index < len(image_array) - 1:
                cv2.setTrackbarPos('image_index', 'name', img_index + 1)
    cv2.destroyAllWindows()
In [17]:
# print image in slices
threshold = threshold.astype(np.uint8)
show_3d_image_slices(threshold)
In [16]:
from skimage import measure
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from scipy import ndimage


def plot_3d(image, name):
    # p = image.transpose(2, 1, 0)
    verts, faces = measure.marching_cubes_classic(image)
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    mesh = Poly3DCollection(verts[faces], alpha=0.7)
    face_color = [0.45, 0.45, 0.75]
    mesh.set_facecolor(face_color)
    ax.add_collection(mesh)

    ax.set_xlim(0, image.shape[0])
    ax.set_ylim(0, image.shape[1])
    ax.set_zlim(0, image.shape[2])

    plt.savefig(name)


def resample(image, scan, new_spacing=[1, 1, 1]):
    # Determine current pixel spacing
    spacing = np.array([scan[0].SliceThickness] + [float(x) for x in scan[0].PixelSpacing], dtype=np.float32)

    resize_factor = spacing / new_spacing
    new_real_shape = image.shape * resize_factor
    new_shape = np.round(new_real_shape)
    real_resize_factor = new_shape / image.shape
    new_spacing = spacing / real_resize_factor

    image = ndimage.interpolation.zoom(image, real_resize_factor, mode='nearest')

    return image, new_spacing
In [17]:
# plot 3d image in matplotlib (very slow)
resampled, spacing = resample(threshold, dicoms)
plot_3d(resampled, 'matplotlib_3d')
In [8]:
import pclpy


def print_3d_point_cloud(window_name, cloud):
    """
    Shows 3D point cloud

    :param window_name: string
    :param cloud: pclpy.PointCloud.[PointXYZ, PointXYZRGB, ...]
    """
    visual = pclpy.pcl.visualization.CloudViewer(window_name)
    visual.showCloud(cloud)

    v = True
    while v:
        v = not (visual.wasStopped())
In [9]:
# prepare data to point cloud (works on Windows, no pclpy on Linux)
points = np.nonzero(threshold)
points = np.transpose(points)
point_cloud = pclpy.pcl.PointCloud.PointXYZ(points)
print_3d_point_cloud('', point_cloud)
In [ ]:
import vtk

# vtk example

colors = vtk.vtkNamedColors()

fileName = 'FullHead.mhd' #get_program_parameters()

colors.SetColor("SkinColor", [255, 125, 64, 255])
colors.SetColor("BkgColor", [51, 77, 102, 255])

# Create the renderer, the render window, and the interactor. The renderer
# draws into the render window, the interactor enables mouse- and
# keyboard-based interaction with the data within the render window.
#
aRenderer = vtk.vtkRenderer()
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(aRenderer)

iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

reader = vtk.vtkMetaImageReader()
reader.SetFileName(fileName)

# An isosurface, or contour value of 500 is known to correspond to the
# skin of the patient.
skinExtractor = vtk.vtkMarchingCubes()
skinExtractor.SetInputConnection(reader.GetOutputPort())
skinExtractor.SetValue(0, 500)

skinMapper = vtk.vtkPolyDataMapper()
skinMapper.SetInputConnection(skinExtractor.GetOutputPort())
skinMapper.ScalarVisibilityOff()

skin = vtk.vtkActor()
skin.SetMapper(skinMapper)
skin.GetProperty().SetDiffuseColor(colors.GetColor3d("SkinColor"))

# An outline provides context around the data.
#
outlineData = vtk.vtkOutlineFilter()
outlineData.SetInputConnection(reader.GetOutputPort())

mapOutline = vtk.vtkPolyDataMapper()
mapOutline.SetInputConnection(outlineData.GetOutputPort())

outline = vtk.vtkActor()
outline.SetMapper(mapOutline)
outline.GetProperty().SetColor(colors.GetColor3d("Black"))

# It is convenient to create an initial view of the data. The FocalPoint
# and Position form a vector direction. Later on (ResetCamera() method)
# this vector is used to position the camera to look at the data in
# this direction.
aCamera = vtk.vtkCamera()
aCamera.SetViewUp(0, 0, -1)
aCamera.SetPosition(0, -1, 0)
aCamera.SetFocalPoint(0, 0, 0)
aCamera.ComputeViewPlaneNormal()
aCamera.Azimuth(30.0)
aCamera.Elevation(30.0)

# Actors are added to the renderer. An initial camera view is created.
# The Dolly() method moves the camera towards the FocalPoint,
# thereby enlarging the image.
aRenderer.AddActor(outline)
aRenderer.AddActor(skin)
aRenderer.SetActiveCamera(aCamera)
aRenderer.ResetCamera()
aCamera.Dolly(1.5)

# Set a background color for the renderer and set the size of the
# render window (expressed in pixels).
aRenderer.SetBackground(colors.GetColor3d("BkgColor"))
renWin.SetSize(640, 480)

# Note that when camera movement occurs (as it does in the Dolly()
# method), the clipping planes often need adjusting. Clipping planes
# consist of two planes: near and far along the view direction. The
# near plane clips out objects in front of the plane the far plane
# clips out objects behind the plane. This way only what is drawn
# between the planes is actually rendered.
aRenderer.ResetCameraClippingRange()

# Initialize the event loop and then start it.
iren.Initialize()
iren.Start()