Raw Image Reading

Overview

In some, hopefully rare, cases you may only have the image information in raw pixel format, a binary blob file. For example the chest x-ray dataset from the Japanese Society of Radiological Technology (JSRT).

To read such an image you are required to know three things:

  1. Image size (number of pixels per dimenssion).

  2. Pixel type (e.g. sitkUInt16).

  3. The byte order ( or endianness) where little endian is most common.

If the byte order in unknown since it only has two options and we can try both. The one resulting in a visually correct image is the correct byte order.

As a SimpleITK image is a spatial object, we may also need to provide its origin, spacing, and directon cosine matrix. Without explicit information we can make reasonable assumptions that often work, as shown in the code below.

Code


import SimpleITK as sitk
import tempfile
import argparse
import os
import sys


def read_raw(binary_file_name, image_size, sitk_pixel_type, image_spacing=None,
             image_origin=None, big_endian=False):
    """
    Read a raw binary scalar image.

    Parameters
    ----------
    binary_file_name (str): Raw, binary image file content.
    image_size (tuple like): Size of image (e.g. [2048,2048])
    sitk_pixel_type (SimpleITK pixel type: Pixel type of data (e.g. sitk.sitkUInt16).
    image_spacing (tuple like): Optional image spacing, if none given assumed to be [1]*dim.
    image_origin (tuple like): Optional image origin, if none given assumed to be [0]*dim.
    big_endian (bool): Optional byte order indicator, if True big endian, else little endian.

    Returns
    -------
    SimpleITK image or None if fails.
    """

    pixel_dict = {sitk.sitkUInt8: 'MET_UCHAR',
                  sitk.sitkInt8: 'MET_CHAR',
                  sitk.sitkUInt16: 'MET_USHORT',
                  sitk.sitkInt16: 'MET_SHORT',
                  sitk.sitkUInt32: 'MET_UINT',
                  sitk.sitkInt32: 'MET_INT',
                  sitk.sitkUInt64: 'MET_ULONG_LONG',
                  sitk.sitkInt64: 'MET_LONG_LONG',
                  sitk.sitkFloat32: 'MET_FLOAT',
                  sitk.sitkFloat64: 'MET_DOUBLE'}
    direction_cosine = ['1 0 0 1', '1 0 0 0 1 0 0 0 1', '1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1']
    dim = len(image_size)
    header = ['ObjectType = Image\n'.encode(),
              ('NDims = {0}\n'.format(dim)).encode(),
              ('DimSize = ' + ' '.join([str(v) for v in image_size]) + '\n').encode(),
              ('ElementSpacing = ' + (' '.join([str(v) for v in image_spacing]) if image_spacing else ' '.join(
                  ['1'] * dim)) + '\n').encode(),
              ('Offset = ' + (
                  ' '.join([str(v) for v in image_origin]) if image_origin else ' '.join(['0'] * dim) + '\n')).encode(),
              ('TransformMatrix = ' + direction_cosine[dim - 2] + '\n').encode(),
              ('ElementType = ' + pixel_dict[sitk_pixel_type] + '\n').encode(),
              'BinaryData = True\n'.encode(),
              ('BinaryDataByteOrderMSB = ' + str(big_endian) + '\n').encode(),
              ('ElementDataFile = ' + os.path.abspath(
                  binary_file_name) + '\n').encode()]  # ElementDataFile must be the last entry in the header
    fp = tempfile.NamedTemporaryFile(suffix='.mhd', delete=False)

    print(header)

    # Not using the tempfile with a context manager and auto-delete
    # because on windows we can't open the file a second time for ReadImage.
    fp.writelines(header)
    fp.close()
    img = sitk.ReadImage(fp.name)
    os.remove(fp.name)
    return img


parser = argparse.ArgumentParser()
parser.add_argument('raw_file_name', help='path to raw binary image file')
parser.add_argument('out_file_name', help='output file name when image read as little endian')
parser.add_argument("big_endian", type=lambda v : v.lower() in {"1", "true"},
                    help="\'false\' for little ending or \'true\'for big endian")
parser.add_argument('sitk_pixel_type', help="SimpleITK pixel type (e.g. sitk.sitkUInt16)")
parser.add_argument('sz', nargs='+', help="image size, x,y,...", type=int)
args = parser.parse_args()

string_to_pixelType = {"sitkUInt8": sitk.sitkUInt8,
                       "sitkInt8": sitk.sitkInt8,
                       "sitkUInt16": sitk.sitkUInt16,
                       "sitkInt16": sitk.sitkInt16,
                       "sitkUInt32": sitk.sitkUInt32,
                       "sitkInt32": sitk.sitkInt32,
                       "sitkUInt64": sitk.sitkUInt64,
                       "sitkInt64": sitk.sitkInt64,
                       "sitkFloat32": sitk.sitkFloat32,
                       "sitkFloat64": sitk.sitkFloat64}

# Read the image using both big and little endian
image = read_raw(binary_file_name=args.raw_file_name,
                 image_size=args.sz,
                 sitk_pixel_type=string_to_pixelType[args.sitk_pixel_type],
                 big_endian=args.big_endian)

sitk.WriteImage(image, args.out_file_name)

if "SITK_NOSHOW" not in os.environ:
    sitk.Show(image, 'raw converted')
library(SimpleITK)

#' Read a raw binary scalar image.
#'
#' @param binary_file_name (str): Raw, binary image file content.
#' @param image_size (tuple like): Size of image (e.g. [2048,2048])
#' @param sitk_pixel_type (SimpleITK pixel type: Pixel type of data (e.g. sitk.sitkUInt16).
#' @param image_spacing (tuple like): Optional image spacing, if none given assumed to be [1]*dim.
#' @param image_origin (tuple like): Optional image origin, if none given assumed to be [0]*dim.
#' @param big_endian (bool): Optional byte order indicator, if True big endian, else little endian.
#'
#' @return SimpleITK image.
read_raw<-function(binary_file_name, image_size, sitk_pixel_type, image_spacing=NULL,
                   image_origin=NULL, big_endian=FALSE)
{
    PIXEL_TYPE <- list("sitkUInt8" = "MET_UCHAR",
                       "sitkInt8" = "MET_CHAR",
                       "sitkUInt16" = "MET_USHORT",
                       "sitkInt16" = "MET_SHORT",
                       "sitkUInt32" = "MET_UINT",
                       "sitkInt32" = "MET_INT",
                       "sitkUInt64" = "MET_ULONG_LONG",
                       "sitkInt64" = "MET_LONG_LONG",
                       "sitkFloat32" = "MET_FLOAT",
                       "sitkFloat64" = "MET_DOUBLE")
    direction_cosine <- list("1 0 0 1", "1 0 0 0 1 0 0 0 1", "1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1")
    dim <- length(image_size)
    header <- c("ObjectType = Image\n",
                paste0("NDims = ",toString(dim),"\n"),
                paste0("DimSize = ", paste(image_size, collapse = " "), "\n"),
                paste0("ElementSpacing = ", paste((if (!is.null(image_spacing)) image_spacing else rep(1,dim)), collapse = " "), "\n"),
                paste0("Offset = ", paste(if (!is.null(image_origin)) image_origin else rep(0,dim), collapse = " "), "\n"),
                paste0("TransformMatrix = ", direction_cosine[dim-1], "\n"),
                paste0("ElementType = ", PIXEL_TYPE[sitk_pixel_type], "\n"),
                "BinaryData = True\n",
                paste0("BinaryDataByteOrderMSB = ", if (big_endian==FALSE) "False" else "True", "\n"),
                paste0("ElementDataFile = ", normalizePath(binary_file_name), "\n")) # ElementDataFile must be the last entry in the header

    fname <- tempfile(fileext = ".mhd") #generate temp file name
    writeLines(header, con=fname) #create and write to file
    img <- ReadImage(fname)
    file.remove(fname) #immediatly remove tempfile, otherwise it is automatically removed at the end of the R session
    return(img)
}

args <- commandArgs( TRUE )

if (length(args) < 6) {
   write("Usage arguments: <raw_file_name> <out_file_name> <big_endian false|true> <sitk_pixel_type> <image_size>", stderr())
   quit(save="no", status=1)
}

# Read the image using both big and little endian
image <- tryCatch(read_raw(binary_file_name=args[[1]], image_size=tail(args,-4), sitk_pixel_type=args[[4]], big_endian=as.logical(args[3])),
                                warning = function(err) {
                                    message(err)
                                    quit(save="no", status=1)
                                }
                               )

WriteImage(image, args[[2]])

if (exists("SITK_NOSHOW", mode="environment")) {
   Show(image, "little endian")
}

quit(save="no", status=0)