Source code for imagedata.formats.matplugin

"""Read/Write Matlab-compatible MAT files
"""

# Copyright (c) 2013-2018 Erling Andersen, Haukeland University Hospital, Bergen, Norway

import os.path
import logging
import numpy as np
import scipy
import scipy.io
import imagedata.formats
import imagedata.axis
from imagedata.formats.abstractplugin import AbstractPlugin

logger = logging.getLogger(__name__)


[docs]class ImageTypeError(Exception): """ Thrown when trying to load or save an image of unknown type. """ pass
[docs]class DependencyError(Exception): """ Thrown when a required module could not be loaded. """ pass
[docs]class MultipleVariablesInMatlabFile(Exception): """ Reading multiple variables from a MAT file is not implemented. """
[docs]class MatrixDimensionNotImplemented(Exception): """ Matrix dimension is not implemented. """
[docs]class MatPlugin(AbstractPlugin): """Read/write MAT files.""" name = "mat" description = "Read and write MAT files." authors = "Erling Andersen" version = "1.0.0" url = "www.helse-bergen.no" def __init__(self): super(MatPlugin, self).__init__(self.name, self.description, self.authors, self.version, self.url) self.slices = None self.spacing = None self.tags = None self.output_sort = None # noinspection PyTypeChecker,PyUnresolvedReferences def _read_image(self, f, opts, hdr): """Read image data from given file handle Args: self: format plugin instance f: file handle or filename (depending on self._need_local_file) opts: Input options (dict) hdr: Header Returns: Tuple of hdr: Header Return values: - info: Internal data for the plugin None if the given file should not be included (e.g. raw file) si: numpy array (multi-dimensional) """ info = {} try: logger.debug('matplugin._read_image: scipy.io.loadmat({})'.format(f)) mdictlist = scipy.io.whosmat(f) if len(mdictlist) != 1: names = [] for name, shape, dtype in mdictlist: names.append(name) logger.debug('matplugin._read_image: scipy.io.loadmat len(mdict) {}'.format(len(mdictlist))) logger.debug('matplugin._read_image: Multiple variables in MAT file {}'.format(f)) raise MultipleVariablesInMatlabFile('Multiple variables in MAT file {}: {}'.format(f, names)) name, shape, dtype = mdictlist[0] logger.debug('matplugin._read_image: name {} shape {} dtype {}'.format(name, shape, dtype)) mdict = scipy.io.loadmat(f, variable_names=(name,)) logger.debug('matplugin._read_image variable {}'.format(name)) si = self._reorder_to_dicom(mdict[name]) logger.info("Data shape _read_image MAT: {} {}".format(si.shape, si.dtype)) except imagedata.formats.NotImageError: raise imagedata.formats.NotImageError('{} does not look like a MAT file'.format(f)) return info, si def _set_tags(self, image_list, hdr, si): """Set header tags. Args: self: format plugin instance image_list: list with (info,img) tuples hdr: Header si: numpy array (multi-dimensional) Returns: hdr: Header """ # Set spacing hdr.spacing = (1.0, 1.0, 1.0) axes = list() axes.append(imagedata.axis.UniformLengthAxis( 'row', 0, si.shape[-2]) ) axes.append(imagedata.axis.UniformLengthAxis( 'column', 0, si.shape[-1]) ) # Set tags nt = nz = 1 if si.ndim > 2: nz = si.shape[-3] axes.insert(0, imagedata.axis.UniformLengthAxis( 'slice', 0, nz) ) if si.ndim > 3: nt = si.shape[-4] axes.insert(0, imagedata.axis.UniformLengthAxis( imagedata.formats.input_order_to_dirname_str(hdr.input_order), 0, nt) ) hdr.axes = axes logger.debug('matplugin._set_tags nt {}, nz {}'.format( nt, nz)) dt = 1 times = np.arange(0, nt * dt, dt) tags = {} for slice in range(nz): tags[slice] = np.array(times) hdr.tags = tags # logger.debug('matplugin._set_tags tags {}'.format(tags)) hdr.photometricInterpretation = 'MONOCHROME2' hdr.color = False
[docs] def write_3d_numpy(self, si, destination, opts): """Write 3D numpy image as MAT file Args: self: MATPlugin instance si: Series array (3D or 4D), including these attributes: - slices, - spacing, - tags destination: dict of archive and filenames opts: Output options (dict) """ if si.color: raise imagedata.formats.WriteNotImplemented( "Writing color MAT images not implemented.") logger.debug('MatPlugin.write_3d_numpy: destination {}'.format(destination)) archive = destination['archive'] filename_template = 'Image_%05d.mat' if len(destination['files']) > 0 and len(destination['files'][0]) > 0: filename_template = destination['files'][0] self.slices = si.slices self.spacing = si.spacing self.tags = si.tags logger.info("Data shape write: {}".format(imagedata.formats.shape_to_str(si.shape))) if si.ndim == 4 and si.shape[0] == 1: si.shape = si.shape[1:] # if si.ndim == 2: # si.shape = (1,) + si.shape assert si.ndim == 2 or si.ndim == 3, "write_3d_series: input dimension %d is not 2D/3D." % si.ndim # slices = si.shape[0] # if slices != si.slices: # raise ValueError("write_3d_series: slices of dicom template ({}) differ from input array ({}).".format(si.slices, slices)) # newshape = tuple(reversed(si.shape)) # logger.info("Data shape matlab write: {}".format(imagedata.formats.shape_to_str(newshape))) # logger.debug('matplugin.write_3d_numpy newshape {}'.format(newshape)) # img = si.reshape(newshape, order='C') # img = si.reshape(newshape, order='F') # img = np.asfortranarray(si) # img = self._dicom_to_mat(si) # img = self._reorder_from_dicom(si) # logger.debug('matplugin.write_3d_numpy si {} {}'.format(si.shape, si.dtype)) # logger.debug('matplugin.write_3d_numpy img {} {}'.format(img.shape, img.dtype)) # if not os.path.isdir(directory_name): # os.makedirs(directory_name) try: filename = filename_template % 0 except TypeError: filename = filename_template # filename = os.path.join(directory_name, filename) if len(os.path.splitext(filename)[1]) == 0: filename = filename + '.mat' with archive.open(filename, 'wb') as f: # scipy.io.savemat(f, {'A': img}) scipy.io.savemat(f, {'A': self._reorder_from_dicom(si)})
[docs] def write_4d_numpy(self, si, destination, opts): """Write 4D numpy image as MAT files Args: self: MATPlugin instance si[tag,slice,rows,columns]: Series array, including these attributes: - slices, - spacing, - tags destination: dict of archive and filenames opts: Output options (dict) """ if si.color: raise imagedata.formats.WriteNotImplemented( "Writing color MAT images not implemented.") logger.debug('MatPlugin.write_4d_numpy: destination {}'.format(destination)) archive = destination['archive'] filename_template = 'Image_%05d.mat' if len(destination['files']) > 0 and len(destination['files'][0]) > 0: filename_template = destination['files'][0] self.slices = si.slices self.spacing = si.spacing self.tags = si.tags # Defaults self.output_sort = imagedata.formats.SORT_ON_SLICE if 'output_sort' in opts: self.output_sort = opts['output_sort'] # Should we allow to write 3D volume? # if si.ndim == 3: # si.shape = (1,) + si.shape if si.ndim != 4: raise ValueError("write_4d_numpy: input dimension {} is not 4D.".format(si.ndim)) logger.debug("write_4d_numpy: si dtype {}, shape {}, sort {}".format( si.dtype, si.shape, imagedata.formats.sort_on_to_str(self.output_sort))) steps = si.shape[0] slices = si.shape[1] if steps != len(si.tags[0]): raise ValueError( "write_4d_series: tags of dicom template ({}) differ from input array ({}).".format(len(si.tags[0]), steps)) if slices != si.slices: raise ValueError( "write_4d_series: slices of dicom template ({}) differ from input array ({}).".format(si.slices, slices)) # if not os.path.isdir(directory_name): # os.makedirs(directory_name) filename = filename_template % 0 # filename = os.path.join(directory_name, filename) if len(os.path.splitext(filename)[1]) == 0: filename = filename + '.mat' with archive.open(filename, 'wb') as f: # scipy.io.savemat(f, {'A': self._reorder_from_dicom(si)}) img = self._reorder_from_dicom(si) logger.debug("write_4d_numpy: Calling savemat") scipy.io.savemat(f, {'A': img})