Sunday, December 25, 2022
HomeData ScienceCoping with DICOM Utilizing ImageIO Python Bundle | by Omar Alkousa |...

Coping with DICOM Utilizing ImageIO Python Bundle | by Omar Alkousa | Dec, 2022


Photograph by Jonathan Borba on Unsplash

On this publish, we are going to use the ImageIO Python package deal to learn DICOM information, extract metadata and attributes, and plot picture slices utilizing interactive slider widgets utilizing Ipywidgets.

The output of the ultimate code beneath. [Image by the author]

DICOM is the bread and butter of medical imaging programs. If you’re a Biomedical Engineer, an IT Specialist within the healthcare area, or a Healthcare Information Scientist/Analyst, you’ve most likely used or at the least heard about DICOM as a result of it’s in every single place associated to medical imaging programs.

DICOM, Digital Imaging and Communications in Medication, is a set of requirements which are created to permit communication throughout a number of modalities, e.g. CT, MRI, Ultrasound, and so on., between a number of producers so that every one medical machines, which are DICOM-compliant after all, can converse the identical language when sending data throughout a community.

DICOM information normally have data different than simply information of pixels. DICOM Requirements present a set of attributes to standardize the info saved in DICOM information. Every attribute has a particular information kind (e.g. string, integer, float, and so on.) with a set worth illustration. These attributes are known as metadata. Examples of DICOM metadata:

  • Affected person-related attributes: title, age, intercourse, and so on.
  • Modality-related attributes: modality (CT, MRI, Ultrasound, and so on.), producer, acquisition date, and so on.
  • Picture-related attributes: form, sampling, facet ratio, pixel information, and so on.

Be aware that the image-related attributes I discussed above are crucial to grasp as they’re helpful to implement real-world calculations on medical photos. We’ll closely depend on them down beneath. It’s most popular that you simply perceive what every represents.

There are dozens of attributes that may characterize DICOM information. So, you can’t simply learn all of them. It is advisable focus solely on the attributes that you simply may counter throughout your work on a DICOM-related factor. For this objective, I extremely advocate this nice DICOM Customary Browser which was constructed by Innolitics, you simply must seek for the attribute you need to find out about.

Now let’s get right down to our enterprise… We’ll use python to simply surf DICOM information. We’ll give attention to utilizing the (ImageIO) package deal to current among the fundamentals about DICOM information. We’ll talk about the next:

  • Learn DICOM information utilizing ImageIO.
  • DICOM attributes, Metadata.
  • Entry a particular DICOM attribute.
  • Picture illustration utilizing Matplotlib package deal.
  • Stack and browse a number of slices.
  • Perceive Pixel Spacing, Form, Slice Thickness, Facet Ratio, and the sphere of view.
  • Construct an interactive picture illustration alongside the axial, coronal, and sagittal planes utilizing Ipywidgets.

First, let’s import the required packages. We’ll use (ImageIO) to take care of DICOM information, (NumPy) because the pixel information are learn as a NumPy-array, and (matplotlib) to visualise the photographs. A further package deal (Ipywidgets) can be used to construct an interactive slider that we will use to scroll amongst a number of picture slices.

I’m utilizing Google Colab for coding in Python

import ipywidgets as widgets
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import imageio

DICOM Dataset

There are alot of DICOM samples on the web. And you should utilize your personal DICOM information. For this weblog, I selected a dataset of 99 slices of chest-CT scan for one affected person. You will discover it on Kaggle. I saved the dataset on my Drive so I can simply have entry by GoogleColab.

Studying DICOM Information

You possibly can simply learn a DICOM file and retailer it in a variable utilizing (.imread) from ImageIO package deal.

#Studying a DICOM Picture
im = imageio.imread('/content material/drive/MyDrive/Datasets/Kaggle/DICOM/dicom_lung/000009.dcm')

DICOM Attributes

Utilizing (.meta) outputs a dictionary that accommodates the attributes, the metadata, of this DICOM file.

#DICOM Metadata
im.meta
Dict([('TransferSyntaxUID', '1.2.840.10008.1.2.1'),
('SOPClassUID', '1.2.840.10008.5.1.4.1.1.2'),
('SOPInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.397047382026069586801778973091'),
('StudyDate', '20100227'),
('SeriesDate', '20100227'),
('AcquisitionDate', '20100227'),
('ContentDate', '20100227'),
('StudyTime', '161937.171'),
('SeriesTime', '162536.14 '),
('AcquisitionTime', '162208.162527 '),
('ContentTime', '162208.162527 '),
('Modality', 'CT'),
('Manufacturer', 'SIEMENS'),
('StudyDescription', 'CT CHEST W IV CONTRAST'),
('SeriesDescription', 'LUNG 3.0 B70f'),
('PatientName', 'C3N-00247'),
('PatientID', 'C3N-00247'),
('PatientBirthDate', ''),
('PatientSex', 'F '),
('PatientAge', '077Y'),
('StudyInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.258626612405225511766549337110'),
('SeriesInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.242193282649561899185427104083'),
('SeriesNumber', 2),
('AcquisitionNumber', 2),
('InstanceNumber', 1),
('ImagePositionPatient', (-143.2177734375, -287.2177734375, 6.0)),
('ImageOrientationPatient', (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)),
('SamplesPerPixel', 1),
('Rows', 512),
('Columns', 512),
('PixelSpacing', (0.564453125, 0.564453125)),
('BitsAllocated', 16),
('BitsStored', 12),
('HighBit', 11),
('PixelRepresentation', 0),
('RescaleIntercept', -1024.0),
('RescaleSlope', 1.0),
('PixelData',
b'Data converted to numpy array, raw data removed to preserve memory'),
('shape', (512, 512)),
('sampling', (0.564453125, 0.564453125))])

Because the metadata is saved as a dictionary, you may see the keys, the attributes’ names, of the DICOM file:

# The Attributes of the DICOM File
im.meta.keys()
odict_keys(['TransferSyntaxUID', 'SOPClassUID', 'SOPInstanceUID',
'StudyDate', 'SeriesDate', 'AcquisitionDate', 'ContentDate',
'StudyTime', 'SeriesTime', 'AcquisitionTime', 'ContentTime',
'Modality', 'Manufacturer', 'StudyDescription', 'SeriesDescription',
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex',
'PatientAge', 'StudyInstanceUID', 'SeriesInstanceUID',
'SeriesNumber', 'AcquisitionNumber', 'InstanceNumber',
'ImagePositionPatient', 'ImageOrientationPatient',
'SamplesPerPixel', 'Rows', 'Columns', 'PixelSpacing',
'BitsAllocated', 'BitsStored', 'HighBit', 'PixelRepresentation',
'RescaleIntercept', 'RescaleSlope', 'PixelData', 'shape',
'sampling'])

Be aware: The attributes we see right here won’t be the entire attributes contained on this DICOM file. It is because ImageIO offers with DICOM photos as gentle as potential. An non-compulsory studying is to test this hyperlink to see the dictionary of attributes that ImageIO helps.

Entry a Particular DICOM Attribute

Accessing a particular attribute, “Modality” for instance, could be completed like the next:

#The Modality Attribute
im.meta['Modality']
'CT'

Picture Illustration Utilizing Matplotlib Bundle

Normally, the “Pixel Information” attribute has the values of pixels. Let’s entry this attribute identical to earlier than:

# Entry the Pixel Information attribute
im.meta['PixelData']
b'Information transformed to numpy array, uncooked information eliminated to protect reminiscence'

Because it says, the pixel values are saved as a NumPy array. And that is helpful as a result of NumPy is appropriate for coping with arrays and their calculation fastly. Now let’s see the pixel values:

#print the picture Numpy-array
im
Array([[ -985,  -990,  -999, ..., -1017, -1008,  -971],
[-1016, -984, -963, ..., -1000, -1009, -999],
[-1024, -1008, -996, ..., -979, -1021, -987],
...,
[ -920, -942, -944, ..., -893, -917, -955],
[ -871, -879, -905, ..., -895, -869, -867],
[ -876, -855, -873, ..., -933, -982, -936]], dtype=int16)

We solely see a bunch of numbers the place every quantity represents the pixel worth within the Hounsfield Unit (HU) type. You possibly can slice the picture array utilizing NumPy-array indices:

#Slicing the primary 5 rows and first 5 columns
im[0:5, 0:5]
Array([[ -985,  -990,  -999,  -982, -1011],
[-1016, -984, -963, -978, -1005],
[-1024, -1008, -996, -969, -992],
[-1006, -984, -997, -963, -1002],
[ -970, -988, -1006, -992, -999]], dtype=int16)

After understanding what Pixel Information represents, let’s present the picture of those pixels.

#Present the picture with grey color-map
plt.imshow(im, cmap='grey')
#Do not present tha axes
plt.axis('off')
#Add a title to the plot
plt.title('Axial Slice')
plt.present()
Picture illustration utilizing Matplotlib [Image by the author]

Stack and Learn A number of Slices

A DICOM file can have a number of frames that is likely to be discovered stacked within the Pixel Information attribute. You possibly can see that in DICOM information which have movies or coloured photos, RGB channels. Additionally, generally you discover a number of DICOM information in a single folder during which every DICOM file accommodates one body, or slice, of the identical affected person. On this case, we’ve to stack these DICOM information ourselves. Thankfully, that is simply completed utilizing (.volread) from ImageIO.

#Stacking 99 slices
vol = imageio.volread('/content material/drive/MyDrive/Datasets/Kaggle/DICOM/dicom_lung')
Studying DICOM (analyzing information): 99/99 information (100.0%)
Discovered 1 appropriate sequence.
Studying DICOM (loading information): 99/99 (100.0%)

You possibly can entry a particular slice utilizing array indices:

#Entry the primary slice
vol[0,:,:]
Array([[ -985,  -990,  -999, ..., -1017, -1008,  -971],
[-1016, -984, -963, ..., -1000, -1009, -999],
[-1024, -1008, -996, ..., -979, -1021, -987],
...,
[ -920, -942, -944, ..., -893, -917, -955],
[ -871, -879, -905, ..., -895, -869, -867],
[ -876, -855, -873, ..., -933, -982, -936]], dtype=int16)

Additionally, picture illustration for a single particular slice could be completed identical to earlier than:

#Present the primary slice.
plt.imshow(vol[0,:,:], cmap='grey')
#Do not present the axis
plt.axis('off')
#Add a title
plt.title('Axial Slice')
plt.present()
That is the primary slice of the 99 stack photos. [Image by the author]

Coping with stacked slices may be very helpful for extracting different planes (Sagittal, and Coronal). Additionally, it’s useful for radiologists to scroll over a number of slices whereas diagnosing. However to point out the three planes and scroll upon slices accurately, we, engineers, have to grasp the which means of the particular attributes. These attributes are Sampling, Form, and Facet Ratio.

Human anatomy planes, transverse is the axial aircraft. [Image by Wikimedia Commons]
#Introduce the metadata of the stacked photos.
vol.meta
Dict([('TransferSyntaxUID', '1.2.840.10008.1.2.1'),
('SOPClassUID', '1.2.840.10008.5.1.4.1.1.2'),
('SOPInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.397047382026069586801778973091'),
('StudyDate', '20100227'),
('SeriesDate', '20100227'),
('AcquisitionDate', '20100227'),
('ContentDate', '20100227'),
('StudyTime', '161937.171'),
('SeriesTime', '162536.14 '),
('AcquisitionTime', '162208.162527 '),
('ContentTime', '162208.162527 '),
('Modality', 'CT'),
('Manufacturer', 'SIEMENS'),
('StudyDescription', 'CT CHEST W IV CONTRAST'),
('SeriesDescription', 'LUNG 3.0 B70f'),
('PatientName', 'C3N-00247'),
('PatientID', 'C3N-00247'),
('PatientBirthDate', ''),
('PatientSex', 'F '),
('PatientAge', '077Y'),
('StudyInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.258626612405225511766549337110'),
('SeriesInstanceUID',
'1.3.6.1.4.1.14519.5.2.1.7085.2626.242193282649561899185427104083'),
('SeriesNumber', 2),
('AcquisitionNumber', 2),
('InstanceNumber', 1),
('ImagePositionPatient', (-143.2177734375, -287.2177734375, 6.0)),
('ImageOrientationPatient', (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)),
('SamplesPerPixel', 1),
('Rows', 512),
('Columns', 512),
('PixelSpacing', (0.564453125, 0.564453125)),
('BitsAllocated', 16),
('BitsStored', 12),
('HighBit', 11),
('PixelRepresentation', 0),
('RescaleIntercept', -1024.0),
('RescaleSlope', 1.0),
('PixelData', b'Deferred loading of pixel data'),
('shape', (99, 512, 512)),
('sampling', (3.0, 0.564453125, 0.564453125))])

Discover the variations in “form” and “sampling” attributes for the stacked photos. That is completed by ImageIO studying when utilizing (.volread).

Perceive “Form”, “Sampling” and “PixelAspectRatio” attributes

  • Form: It’s merely the variety of rows and columns in every slice. Since we’re coping with a number of slices, there might be a 3rd dimension equal to the variety of slices which are stacked collectively. In our instance, the form of the stacked photos is 99 slices, 512 rows, and 512 columns.
# The form of the stacked photos in every aircraft
# (Axial, Coronal, and Sagittal, respectively)
n0, n1, n2 = vol.form
# Print the ouput
print("Variety of Slices:nt", "Axial=", n0, "Slicesnt",
"Coronal=", n1, "Slicesnt",
"Sagittal=", n2, "Slices")
Variety of Slices:
Axial= 99 Slices
Coronal= 512 Slices
Sagittal= 512 Slices
  • Sampling: In the event you seek for “Sampling” at DICOM Customary, you gained’t get a direct reply. As a result of it’s a particular variable in-built ImageIO. This Sampling mixture of two attributes, SliceThickness, and PixelSpacing. SliceThickness is just the nominal slice thickness in mm. As for the PixelSpacing attribute, it’s the bodily distance, within the affected person. It’s specified by a numeric pair: The primary worth is the row spacing in mm, that’s the spacing between the facilities of adjoining rows, or vertical spacing. The second worth is the column spacing in mm, that’s the spacing between the facilities of adjoining columns, or horizontal spacing.
PixelSapcing attribute. [Image by DICOM]
# The sampling of the stacked photos in every aircraft
# (Axial, Coronal, and Sagittal, respectively)
d0, d1, d2 = vol.meta['sampling'] # in mm
# Print the output
print("Sampling:nt", "Axial=", d0, "mmnt",
"Coronal=", d1, "mmnt",
"Sagittal=", d2, "mm")
Sampling:
Axial= 3.0 mm
Coronal= 0.564453125 mm
Sagittal= 0.564453125 mm
  • Pixel Facet Ratio: Ratio of the vertical measurement and horizontal measurement of the pixels within the picture alongside a particular axis. Discover that “PixelAspectRatio” shouldn’t be within the metadata of the stacked photos. However that’s okay as a result of we will calculate the facet ratio alongside every axis through the use of the “sampling” parameters. The picture beneath represents the PixelAspectRatio attribute.
PixelAspectRatio attribute. [Image by DICOM]
# The facet ratio alongside the axial aircraft
axial_asp = d1/d2
# The facet ratio alongside the sagittal aircraft
sagittal_asp = d0/d1
# The facet ratio alongside the coronal aircraft
coronal_asp = d0/d2
# Print the output
print("Pixel Facet Ratio:nt", "Axial=", axial_asp, "nt",
"Coronal=", coronal_asp, "nt",
"Sagittal=", sagittal_asp)
Pixel Facet Ratio:
Axial= 1.0
Coronal= 5.314878892733564
Sagittal= 5.314878892733564

Discipline of View

By multiplying the “form” parameters by the “sampling” parameters, the bodily area in mm, alongside every axis, we get the Discipline of View alongside every axis.

print("Discipline of View:nt", "Axial=", n0*d0, "mmnt",
"Coronal=", n1*d1, "mmnt",
"Sagittal=", n2*d2, "mm")
Discipline of View:
Axial= 297.0 mm
Coronal= 289.0 mm
Sagittal= 289.0 mm

Construct an interactive picture illustration utilizing Ipywidgets

Now, after understanding what sampling and form imply, let’s use them to do an accurate illustration of the photographs with the right facet ratio alongside every axis. We’ll use Ipywidgets to construct an integer slider which we will scroll between completely different slices of the particular planes (Axial, Sagittal, or Coronal).

# Add a slider that begins with 0 and ends on the variety of
# slices alongside the axial aircraft, n0=99.
@widgets.work together(axial_slice=(0,n0-1))
# Outline the perform that exhibits the photographs of the required slice quantity.
# It begins with the tenth slice. And you'll scroll over any slice
# utilizing the slider.
def axial_slicer(axial_slice=50):
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
# Present the picture of the required slice quantity in 'grey' color-map
# and axial facet ratio
ax.imshow(vol[axial_slice,:,:], cmap='grey', facet=axial_asp)
# Do not present the axis
ax.axis('off')
Scrolling upon a number of axial slices utilizing interactive widgets. [Image by the author]

Now let’s present the photographs of three slices every alongside a particular axis. I deliberately confirmed the axes to simply perceive the form of the slices alongside every axis.

# Outline a determine with 1 row and three columns of plots to point out
# the photographs alongside the three planes
fig, ax = plt.subplots(1, 3, figsize=(8, 8))
# Axial Aircraft: present the tenth slice
ax[0].imshow(vol[10,:,:], cmap='grey', facet= axial_asp)
#ax[0].axis('off')
ax[0].set_title('Axial')

# Coronal Aircraft: present the slice 100
ax[1].imshow(vol[:,100,:],cmap='grey', facet= coronal_asp)
#ax[1].axis('off')
ax[1].set_title('Coronal')

# Sagittal Aircraft: present the slice 100
ax[2].imshow(vol[:,:,100], cmap='grey', facet= sagittal_asp)
#ax[2].axis('off')
ax[2].set_title('Sagittal')
plt.present()

Slices(10 Axial, 100 Coronal, 100 Sagittal) with the right facet ratio of every aircraft. [Image by the author]

Let’s attempt the flawed manner… let’s present the photographs ignoring the facet ratio of every axis.

# Outline a determine with 1 row and three columns of plots to point out the
# photos alongside the three planes
fig, ax = plt.subplots(1, 3, figsize=(8, 8))
# Axial Aircraft: present the tenth slice
ax[0].imshow(vol[10,:,:], cmap='grey')
#ax[0].axis('off')
ax[0].set_title('Axial')

# Coronal Aircraft: present the slice 100
ax[1].imshow(vol[:,100,:],cmap='grey')
#ax[1].axis('off')
ax[1].set_title('Coronal')

# Sagittal Aircraft: present the slice 100
ax[2].imshow(vol[:,:,100], cmap='grey')
#ax[2].axis('off')
ax[2].set_title('Sagittal')
plt.present()

Slices(10 Axial, 100 Coronal, 100 Sagittal) ignoring the facet ratio of every aircraft. [Image by the author]

We will see that solely the axial aircraft shouldn’t be messy. It is because the argument “facet” is ready to 1 by default, which is the facet ratio of the axial aircraft in our case (d1/d2 = 0.564453125 / 0.564453125 = 1).

Our Last Code

Let’s transcend only one slider with one picture aircraft. We’ll construct a determine of three plots that comprise the three planes (Axial, Coronal, and Sagittal, respectively). We will specify the slice quantity utilizing the particular slider for every aircraft. Don’t neglect so as to add the right facet ratio so it gained’t get messy identical to above.

# Add three sliders that begin with 0 and ends on the variety of slices
# alongside every aircraft.
# Axial: n0=99 slice
# Corornal: n1=512 slice
# Sagittal: n2=512 slice
@widgets.work together(axial_slice=(0,n0-1), coronal_slice=(0,n1-1),
sagittal_slice=(0,n2-1))
def slicer(axial_slice, coronal_slice, sagittal_slice=100):
fig, ax = plt.subplots(1, 3, figsize=(12, 12))

# Present the specfied slice on the axial aircraft with 'grey' color-map
# and axial facet ratio.
ax[0].imshow(vol[axial_slice,:,:], cmap='grey', facet= axial_asp)
ax[0].axis('off')
ax[0].set_title('Axial')

# Present the required slice on the coronal aircraft with 'grey' color-map
# and coronal facet ratio.
ax[1].imshow(vol[:,coronal_slice,:],cmap='grey', facet= coronal_asp)
ax[1].axis('off')
ax[1].set_title('Coronal')

# Present the required slice on the sagittal aircraft with 'grey' color-map
# and sagittal facet ratio.
ax[2].imshow(vol[:,:,sagittal_slice], cmap='grey', facet= sagittal_asp)
ax[2].axis('off')
ax[2].set_title('Sagittal')

Scrolling upon a number of axial, coronal, and sagittal slices utilizing interactive widgets. [Image by the author]
  • We’ve seen the best way to use the ImageIO package deal to learn DICOM information. And the ability of utilizing (.volread) to stack the photographs and browse them extra conveniently.
  • We’ve realized {that a} DICOM file has metadata, a set of attributes, together with Pixel Information. And we’ve launched among the fundamentals of particular image-related attributes, similar to PixelSapcing, SliceThickness, Form, and PixelAspectRatio.
  • We’ve identified the significance of the facet ratio parameter when plotting the photographs alongside the axes and the way the picture will get messy if it’s not accurately specified.
  • We’ve constructed a determine that exhibits the required slice quantity alongside the axial, coronal, and sagittal planes utilizing the slider of the Ipywidgets package deal.

Thanks For Studying…

  • For extra particulars about DICOM: discuss with the earlier weblog, What’s DICOM?
  • DICOM Metadata — A Helpful Useful resource for Massive Information Analytics:
    This article offers an summary of latest methods to characterize information by combining affected person entry and DICOM data, superior use of medical imaging metadata, evaluation of radiation dose and picture segmentation, and deep studying for characteristic engineering to complement information.
  • Biomedical Picture Evaluation in Python Course on DataCamp. This course was my first dive into DICOM photos and medical picture processing utilizing Python. The data on this weblog closely is dependent upon the primary chapter of this course, which is the one free chapter.
  • Ipywidget Documentation is an effective begin to studying about constructing interactive widgets.

[1] DataCamp, First Chapter of Biomedical Picture Evaluation in Python Course, First Chapter, Exploration.

[2] C. Rossant, IPython Interactive Computing and Visualization Cookbook, (2018), Mastering widgets within the Jupyter Pocket book.

[3] ImageIO, GitHub, ImageIO-Plugin-DICOM, (2022), DICOM Attributes, MINIDICT.

[4] Innolitics, DICOM Customary Browser, (2022), Slice Thickness Attribute.

[5] Innolitics, DICOM Customary Browser, (2022), Pixel Spacing Attribute.

[6] Innolitics, DICOM Customary Browser, (2022), Facet Ratio Attribute.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments