Source code for imagedata.formats.matplugin

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

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

import logging
import numpy as np
import mimetypes
import scipy
import scipy.io
from . import NotImageError, input_order_to_dirname_str, WriteNotImplemented, \
    shape_to_str, sort_on_to_str, SORT_ON_SLICE
from ..axis import UniformLengthAxis
from .abstractplugin import AbstractPlugin
from ..archives.abstractarchive import AbstractArchive

logger = logging.getLogger(__name__)

mimetypes.add_type('application/x-matlab-data', '.mat')


[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" extensions = [".mat"] 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 = {} if hdr.input_order == 'auto': hdr.input_order = 'none' hdr.color = False 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)) raise MultipleVariablesInMatlabFile('Multiple variables in MAT file {}'.format(f)) 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 NotImageError: raise 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(UniformLengthAxis( 'row', 0, si.shape[-2]) ) axes.append(UniformLengthAxis( 'column', 0, si.shape[-1]) ) # Set tags nt = nz = 1 if si.ndim > 2: nz = si.shape[-3] axes.insert(0, UniformLengthAxis( 'slice', 0, nz) ) if si.ndim > 3: nt = si.shape[-4] axes.insert(0, UniformLengthAxis( 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 WriteNotImplemented( "Writing color MAT images not implemented.") logger.debug('MatPlugin.write_3d_numpy: destination {}'.format(destination)) self.slices = si.slices self.spacing = si.spacing self.tags = si.tags logger.info("Data shape write: {}".format(shape_to_str(si.shape))) if si.ndim == 4 and si.shape[0] == 1: si.shape = si.shape[1:] assert si.ndim == 2 or si.ndim == 3, \ "write_3d_series: input dimension %d is not 2D/3D." % si.ndim img = self._reorder_from_dicom(si) self._write_numpy_to_mat(img, destination, opts)
[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 WriteNotImplemented( "Writing color MAT images not implemented.") logger.debug('MatPlugin.write_4d_numpy: destination {}'.format(destination)) self.slices = si.slices self.spacing = si.spacing self.tags = si.tags # Defaults self.output_sort = SORT_ON_SLICE if 'output_sort' in opts: self.output_sort = opts['output_sort'] 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, 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)) img = self._reorder_from_dicom(si) self._write_numpy_to_mat(img, destination, opts)
def _write_numpy_to_mat(self, img, destination, opts): archive: AbstractArchive = destination['archive'] archive.set_member_naming_scheme( fallback='Image.mat', level=0, default_extension='.mat', extensions=self.extensions ) query = None if destination['files'] is not None and len(destination['files']): query = destination['files'][0] filename = archive.construct_filename( tag=None, query=query ) with archive.open(filename, 'wb') as f: logger.debug("_write_numpy_to_mat: Calling savemat") scipy.io.savemat(f, {'A': img})