This notebook illustrates how to convert Scanco microCT images into open standard file formats while preserving critical image metadata.
import sys
!{sys.executable} -m pip install itk-ioscanco xarray zarr tqdm requests itkwidgets
import requests
from tqdm.notebook import tqdm
import os
from itkwidgets import view
import itk
import numpy as np
import xarray as xr
import zarr
def download_data(url, filename):
if not os.path.exists(filename):
chunk_size = 32 * 1024
r = requests.get(url, stream=True)
total_size = int(r.headers.get('content-length', 0))
pbar = tqdm(unit="B", unit_scale=True, total=int(total_size))
with open(filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=chunk_size):
if chunk: # filter out keep-alive new chunks
pbar.update(len(chunk))
f.write(chunk)
file_name = 'C0004255.ISQ'
file_url = 'https://data.kitware.com/api/v1/file/591e56178d777f16d01e0d20/download'
download_data(file_url, file_name)
imageio = itk.ScancoImageIO.New()
image = itk.imread(file_name, imageio=imageio)
The type of the image is an itk.Image
.
print(isinstance(image, itk.Image))
print(type(image))
True <class 'itk.itkImagePython.itkImageSS3'>
view(image)
Viewer(geometries=[], gradient_opacity=0.22, point_sets=[], rendered_image=<itk.itkImagePython.itkImageSS3; pr…
Get the pixel data as a NumPy ndarray
.
pixel_data = np.asarray(image)
pixel_data
array([[[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], ..., [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]]], dtype=int16)
Get the image metadata as a dict
.
metadata = dict(image)
metadata
{'CalibrationData': '45 kVp, 0.5mm Al, BH: 1200mg HA/ccm, Scaling 4096', 'CreationDate': '5-JUN-2015 11:09:18.880', 'DataRange': (-2813.0, 32767.0), 'Energy': 45.0, 'Intensity': 0.177, 'MeasurementIndex': 4937, 'ModificationDate': '5-JUN-2015 11:09:18.880', 'MuScaling': 4096.0, 'MuWater': 0.7032999992370605, 'NumberOfProjections': 500, 'NumberOfSamples': 1024, 'PatientIndex': 78, 'PatientName': 'COLE-BPBP', 'ReconstructionAlg': 3, 'ReferenceLine': 0.0, 'RescaleIntercept': -1000.0, 'RescaleSlope': 0.34713582434927287, 'RescaleType': 2, 'RescaleUnits': 'mg HA/ccm', 'SampleTime': 400.0, 'ScanDistance': 36.864000000000004, 'ScannerID': 2135, 'ScannerType': 10, 'Site': 5, 'SliceIncrement': 0.036000000000000004, 'SliceThickness': 0.036000000000000004, 'Version': 'CTDATA-HEADER_V1', 'origin': array([0., 0., 0.]), 'spacing': array([0.036, 0.036, 0.036]), 'direction': array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])}
MetaImage is a multi-dimension scientific image file format supported by Kitware-supported open source tools, i.e. ITK and VTK-based tools.
MetaImage:
signed short
.origin
, spacing
, and direction
.itk.imwrite(image, 'image.mha')
image_meta = itk.imread('image.mha')
dict(image_meta)
{'CalibrationData': '45 kVp, 0.5mm Al, BH: 1200mg HA/ccm, Scaling 4096', 'CreationDate': '5-JUN-2015 11:09:18.880', 'DataRange': '32767 32767', 'Energy': '45', 'ITK_InputFilterName': 'MetaImageIO', 'Intensity': '0.177', 'MeasurementIndex': '4937', 'ModificationDate': '5-JUN-2015 11:09:18.880', 'MuScaling': '4096', 'MuWater': '0.7033', 'NumberOfProjections': '500', 'NumberOfSamples': '1024', 'PatientIndex': '78', 'PatientName': 'COLE-BPBP', 'ReconstructionAlg': '3', 'ReferenceLine': '0', 'RescaleIntercept': '-1000', 'RescaleSlope': '0.347136', 'RescaleType': '2', 'RescaleUnits': 'mg HA/ccm', 'SampleTime': '400', 'ScanDistance': '36.864', 'ScannerID': '2135', 'ScannerType': '10', 'Site': '5', 'SliceIncrement': '0.036', 'SliceThickness': '0.036', 'Version': 'CTDATA-HEADER_V1', 'origin': array([0., 0., 0.]), 'spacing': array([0.036, 0.036, 0.036]), 'direction': array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])}
itk.imwrite(image, 'image.nrrd')
image_nrrd = itk.imread('image.nrrd')
dict(image_nrrd)
{'CalibrationData': '45 kVp, 0.5mm Al, BH: 1200mg HA/ccm, Scaling 4096', 'CreationDate': '5-JUN-2015 11:09:18.880', 'DataRange': '', 'Energy': '', 'ITK_InputFilterName': 'NrrdImageIO', 'Intensity': '', 'MeasurementIndex': '', 'ModificationDate': '5-JUN-2015 11:09:18.880', 'MuScaling': '', 'MuWater': '', 'NRRD_kinds[0]': 'domain', 'NRRD_kinds[1]': 'domain', 'NRRD_kinds[2]': 'domain', 'NRRD_space': 'left-posterior-superior', 'NumberOfProjections': '', 'NumberOfSamples': '', 'PatientIndex': '', 'PatientName': 'COLE-BPBP', 'ReconstructionAlg': '', 'ReferenceLine': '', 'RescaleIntercept': '', 'RescaleSlope': '', 'RescaleType': '', 'RescaleUnits': 'mg HA/ccm', 'SampleTime': '', 'ScanDistance': '', 'ScannerID': '', 'ScannerType': '', 'Site': '', 'SliceIncrement': '', 'SliceThickness': '', 'Version': 'CTDATA-HEADER_V1', 'origin': array([0., 0., 0.]), 'spacing': array([0.036, 0.036, 0.036]), 'direction': array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])}
Zarr is a next generation file format popular in the scientific Python community, but also usable across other programming languages.
Zarr has many advantages:
xarray provides an interface for working with label multi-dimensional arrays, and supports serialization to zarr.
image_da = itk.xarray_from_image(image)
image_da
<xarray.DataArray (z: 51, y: 1024, x: 1024)> array([[[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., ... ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]]], dtype=int16) Coordinates: * x (x) float64 0.0 0.036 0.072 0.108 0.144 ... 36.72 36.76 36.79 36.83 * y (y) float64 0.0 0.036 0.072 0.108 0.144 ... 36.72 36.76 36.79 36.83 * z (z) float64 0.0 0.036 0.072 0.108 0.144 ... 1.692 1.728 1.764 1.8 Attributes: (12/28) direction: [[1. 0. 0.]\n [0. 1. 0.]\n [0. 0. 1.]] CalibrationData: 45 kVp, 0.5mm Al, BH: 1200mg HA/ccm, Scaling 4096 CreationDate: 5-JUN-2015 11:09:18.880 DataRange: (-2813.0, 32767.0) Energy: 45.0 Intensity: 0.177 ... ... ScannerID: 2135 ScannerType: 10 Site: 5 SliceIncrement: 0.036000000000000004 SliceThickness: 0.036000000000000004 Version: CTDATA-HEADER_V1
array([[[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., ... ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]], [[-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], ..., [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000], [-1000, -1000, -1000, ..., -1000, -1000, -1000]]], dtype=int16)
array([0.0000e+00, 3.6000e-02, 7.2000e-02, ..., 3.6756e+01, 3.6792e+01, 3.6828e+01])
array([0.0000e+00, 3.6000e-02, 7.2000e-02, ..., 3.6756e+01, 3.6792e+01, 3.6828e+01])
array([0. , 0.036, 0.072, 0.108, 0.144, 0.18 , 0.216, 0.252, 0.288, 0.324, 0.36 , 0.396, 0.432, 0.468, 0.504, 0.54 , 0.576, 0.612, 0.648, 0.684, 0.72 , 0.756, 0.792, 0.828, 0.864, 0.9 , 0.936, 0.972, 1.008, 1.044, 1.08 , 1.116, 1.152, 1.188, 1.224, 1.26 , 1.296, 1.332, 1.368, 1.404, 1.44 , 1.476, 1.512, 1.548, 1.584, 1.62 , 1.656, 1.692, 1.728, 1.764, 1.8 ])
image_ds = image_da.to_dataset(name='image')
with zarr.storage.ZipStore('image.zarr.zip', mode='w', compression=0) as store:
image_ds.to_zarr(store, mode='w', compute=True)
image_ds = xr.open_zarr('image.zarr.zip')
image_zarr = itk.image_from_xarray(image_ds.image)
dict(image_zarr)
{'CalibrationData': '45 kVp, 0.5mm Al, BH: 1200mg HA/ccm, Scaling 4096', 'CreationDate': '5-JUN-2015 11:09:18.880', 'Energy': 45.0, 'Intensity': 0.1770000010728836, 'MeasurementIndex': 4937, 'ModificationDate': '5-JUN-2015 11:09:18.880', 'MuScaling': 4096.0, 'MuWater': 0.7032999992370605, 'NumberOfProjections': 500, 'NumberOfSamples': 1024, 'PatientIndex': 78, 'PatientName': 'COLE-BPBP', 'ReconstructionAlg': 3, 'ReferenceLine': 0.0, 'RescaleIntercept': -1000.0, 'RescaleSlope': 0.3471358120441437, 'RescaleType': 2, 'RescaleUnits': 'mg HA/ccm', 'SampleTime': 400.0, 'ScanDistance': 36.86399841308594, 'ScannerID': 2135, 'ScannerType': 10, 'Site': 5, 'SliceIncrement': 0.035999998450279236, 'SliceThickness': 0.035999998450279236, 'Version': 'CTDATA-HEADER_V1', 'origin': array([0., 0., 0.]), 'spacing': array([0.036, 0.036, 0.036]), 'direction': array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])}