Source code for sfftk.readers.modreader

"""
``sfftk.readers.modreader``
===========================

Ad hoc reader for IMOD (`.mod`) files.

`.mod` files are chunk files and loosely follow the Interchange File
Format (IFF). In summary, IFF files consist of a four-byte header
(all caps chunk name e.g. 'IMOD') followed by an integer of
the number of bytes in the chunk. The chunk is then structured
according to the author's design requirements. Not all .mod
chunks follow this convention (e.g. 'OBJT' chunks do not include
the size of the chunk immediately after the chunk ID.

A description of the structure of `.mod` files can be found at
the following URL: https://bio3d.colorado.edu/imod/betaDoc/binspec.html.
This module consists of a set of classes each identified by the respective chunk names. The following patterns are
observed in the design of these classes:

-   The name of the class is the name of the chunk e.g. OBJT class refers to OBJT chunks.

-   All classes have one public method: read(f), which takes a file handle and returns a file handle at the current
    unread position.

-   Some chunks are nested (despite the serial nature of IFF files). Contained chunks are read with public methods
    defined as ``add_<chunk>`` e.g. OBJT objects are containers of CONT objects and therefore have a ``add_cont()``
    method which takes a CONT object as argument. Internally, container objects use (ordered) dictionaries to store
    contained objects.

-   All chunk classes inherit from :py:class:`object` class and have the :py:meth:`object.__repr__()` method
    implemented to print objects of that class.

In addition, there are several useful dictionary constants and functions and classes (flags) that interpret
several fields within chunks.

.. note::

    The order of classes is based on their position in the module. This can be changed if needed.
    The most important classes are :py:class:`.modreader.IMOD`, :py:class:`.modreader.OBJT`,
    :py:class:`.modreader.CONT` and :py:class:`.modreader.MESH`
"""
import os
import struct
import sys

import numpy
from bitarray import bitarray
from sfftkrw.core import _decode, _xrange, _dict_iter_items, _dict_iter_keys, _dict
from sfftkrw.core.print_tools import get_printable_ascii_string

__author__ = 'Paul K. Korir, PhD'
__email__ = 'pkorir@ebi.ac.uk'
__date__ = '2015-10-12'
__updated__ = '2018-02-14'

"""
:TODO: unravel VIEW chunk (email from 3dmod authors unclear)
:TODO: list fiels in MESH chunk with -24 markers
:TODO: empty (no field) implementation of OGRP, SKLI and SLAN (class exists but unclear how to nest it)
"""

KEY_WORDS = [
    b'CLIP',
    b'CONT',
    b'COST',
    b'IMAT',
    b'IMOD',
    b'LABL',
    b'MCLP',
    b'MEPA',
    b'MESH',
    b'MEST',
    b'MINX',
    b'MOST',
    b'OBJT',
    b'OBST',
    b'OGRP',
    b'OLBL',
    b'SIZE',
    b'SKLI',
    b'SLAN',
    b'VIEW',
]

# 0 = circle, 1 = none, 2 = square, 3 = triangle, 4 = star
OBJT_SYMBOLS = {
    0: 'circle',
    1: 'none',
    2: 'square',
    3: 'triangle',
    4: 'star',
}

UNITS = _dict({
    - 12: 'pm',
    - 10: 'ångström',
    - 9: 'nm',
    - 6: 'microns',
    - 3: 'mm',
    - 2: 'cm',
    0: 'pixels',
    1: 'm',
    3: 'km',
})

UPPER_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'


[docs] def angstrom_multiplier(units): """ Determine a multiplier to convert units to angstrom .. math:: 1\\textrm{Å} = 10^{-10}\\textrm{m} \\Rightarrow 1\\textrm{m} = 10^{10}\\textrm{Å} Consider some generic unit *U* with a power of 10 .. math:: 1\\textrm{U} = 10^x \\textrm{m} \\Rightarrow 1\\textrm{m} = 10^{-x}\\textrm{U} We need a unit factor that relates Å to *U*. Dividing both expressions for :math:`1\\textrm{m}` .. math:: \\begin{aligned} 1 = \\frac{10^{10}}{10^{-x}} \\textrm{Å per U (Å/U)} = 10^{10 + x} \\textrm{Å/U} \\end{aligned} To convert *U* to Å we multiply by :math:`10^{10 + x}` Å/U Example: To convert 3 nm to Å we consider that :math:`x = -9` for nm. So: .. math:: \\begin{align} 3\\textrm{nm} & = 3\\textrm{nm} \\times 10^{10 + (-9)}\\textrm{Å/nm} \\\\ & = 3\\textrm{nm} \\times 10^{10 - 9}\\textrm{Å/nm} \\\\ & = 3\\textrm{nm} \\times 10\\textrm{Å/nm} \\\\ & = 30\\textrm{Å} \\end{align} :param int units: the power of ten for the unit e.g. for nm ``units=-9`` :return: the correct multiplier to convert the given units to angstrom :rtype: int """ try: assert units in list(_dict_iter_keys(UNITS)) except AssertionError: raise ValueError("invalid units value '{units}'".format(units=units)) return 10 ** (10 + units)
[docs] def find_chunk_length(f): """ Determine the size (in bytes) of the current chunk. Also, return the name of the next chunk. Assumes that current position in the file is immediately after the chunk header. :param file f: file handle :return: the length of the chunk, the next chunk name, the file handle at the next read position :rtype: tuple(int, str, file) """ chunk_length = 0 next_chunk = struct.unpack('>4s', f.read(4))[0] while ((next_chunk[0] not in UPPER_ALPHA) or (next_chunk[1] not in UPPER_ALPHA) or ( next_chunk[2] not in UPPER_ALPHA) or (next_chunk[3] not in UPPER_ALPHA)): chunk_length += 1 f.seek(-3, os.SEEK_CUR) next_chunk = struct.unpack('>4s', f.read(4))[0] return chunk_length, next_chunk, f
[docs] class FLAGS(object): """Base class of bit flags""" def __init__(self, int_value, num_bytes, endian='little'): """Initialiser of ``FLAG`` class :param int int_value: the value in base 10 :param int bytes: the number of bytes to store :param str endian: one of `little` or `big` Example usage: .. code-block:: python >>> from sfftk.readers.modreader import FLAGS >>> flag = FLAGS(10, 2) >>> flag 0000000000001010 >>> flag[0] False >>> flag[1] False >>> flag[-1] False >>> flag[-2] True """ try: assert endian in ['little', 'big'] except AssertionError: raise ValueError("Unknown endianess '%s'" % endian) try: assert isinstance(int_value, int) except AssertionError: raise ValueError("Not an int: '%s'" % int_value) try: assert num_bytes > 0 except AssertionError: raise ValueError("Bytes (%s) should be a positive (>0) integer" % num_bytes) self.endian = endian self.bytes = num_bytes self.bits = self.bytes * 8 format_string = '{0:0' + str(self.bits) + 'b}' try: self.bitarray = bitarray(format_string.format(int_value), endian=self.endian) except Exception as e: raise ValueError(e) def __getitem__(self, item): return self.bitarray[item] def __repr__(self): return self.bitarray.to01()
[docs] class MODEL_FLAGS(FLAGS): """Flags in the MODEL chunk""" def __init__(self, *args, **kwargs): super(MODEL_FLAGS, self).__init__(*args, **kwargs)
[docs] class OBJECT_FLAGS(FLAGS): """Flags in the OBJT chunk""" def __init__(self, *args, **kwargs): super(OBJECT_FLAGS, self).__init__(*args, **kwargs)
[docs] class OBJECT_SYM_FLAGS(FLAGS): """Additional flags in the OBJT chunk""" def __init__(self, *args, **kwargs): super(OBJECT_SYM_FLAGS, self).__init__(*args, **kwargs)
[docs] class CONTOUR_FLAGS(FLAGS): """Flags in the CONT chunk""" def __init__(self, *args, **kwargs): super(CONTOUR_FLAGS, self).__init__(*args, **kwargs)
[docs] class COST_FLAGS(FLAGS): """Flags in the COST chunk""" def __init__(self, *args, **kwargs): super(COST_FLAGS, self).__init__(*args, **kwargs)
[docs] class MESH_FLAGS(FLAGS): """Flags in the MESH chunk""" def __init__(self, *args, **kwargs): super(MESH_FLAGS, self).__init__(*args, **kwargs)
[docs] class CLIP_FLAGS(FLAGS): """Flags in the CLIP chunk""" def __init__(self, *args, **kwargs): super(CLIP_FLAGS, self).__init__(*args, **kwargs)
[docs] class MCLP_FLAGS(FLAGS): """Flags in the MCLP chunk""" def __init__(self, *args, **kwargs): super(MCLP_FLAGS, self).__init__(*args, **kwargs)
[docs] class IMAT_FLAGS(FLAGS): """ Flags in the IMAT chunk. """ def __init__(self, *args, **kwargs): super(IMAT_FLAGS, self).__init__(*args, **kwargs)
[docs] class VIEW_FLAGS(FLAGS): """Flags in the VIEW chunk""" def __init__(self, *args, **kwargs): super(VIEW_FLAGS, self).__init__(*args, **kwargs)
[docs] class MEPA_FLAGS(FLAGS): """Flags in the MEPA chunk""" def __init__(self, *args, **kwargs): super(MEPA_FLAGS, self).__init__(*args, **kwargs)
[docs] class STORE(object): """Generic storage class for models (MOST), objects (OBST), contours (COST), and meshes (MEST) :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f
[docs] def read(self): """Read the contents of this chunk""" f = self.f self.type = struct.unpack('>h', f.read(2))[0] self.flags = COST_FLAGS(struct.unpack('>h', f.read(2))[0], 2) # bit 1 and 0 (in order) if not self.flags[-2] and not self.flags[-1]: # 0 self.index = struct.unpack('>i', f.read(4))[0] elif not self.flags[-2] and self.flags[-1]: # 1 self.index = struct.unpack('>f', f.read(4))[0] elif self.flags[-2] and not self.flags[-1]: # 2 self.index = struct.unpack('>hh', f.read(4)) elif self.flags[-2] and self.flags[-1]: # 3 self.index = struct.unpack('>bbbb', f.read(4)) # bit 3 and 2 (in order) if not self.flags[-4] and not self.flags[-3]: # 0 self.value = struct.unpack('>i', f.read(4))[0] elif not self.flags[-4] and self.flags[-3]: # 1 self.value = struct.unpack('>f', f.read(4))[0] elif self.flags[-4] and not self.flags[-3]: # 2 self.value = struct.unpack('>hh', f.read(4)) elif self.flags[-4] and self.flags[-3]: # 3 self.value = struct.unpack('>bbbb', f.read(4)) return f
def __repr__(self): return "type: %s; flags: %s; index: %s; value: %s\n" % (self.type, self.flags, str(self.index), str(self.value))
[docs] class IMOD(object): """Class encapsulating the data in an IMOD file The top-level of an IMOD file is an IMOD chunk specifying various data members. """ def __init__(self, f): self.f = f self.isset = False self.objts = _dict() self.objt_count = 0 self.current_objt = None self.views = _dict() self.view_count = 0 self.mclp = None self.minx = None self.most = None
[docs] def read(self): """Read the IMOD file into an IMOD object :FIXME: use zscale to fix sizes """ f = self.f self.version = _decode(struct.unpack('>4s', f.read(4))[0], 'utf-8') self.name = _decode(get_printable_ascii_string(struct.unpack('>128s', f.read(128))[0]), 'utf-8') self.xmax, self.ymax, self.zmax = struct.unpack('>iii', f.read(12)) self.objsize = struct.unpack('>i', f.read(4))[0] self.flags = MODEL_FLAGS(struct.unpack('>I', f.read(4))[0], 4) self.drawmode, self.mousemode, self.blacklevel, self.whitelevel = struct.unpack('>iiii', f.read(16)) self.xoffset, self.yoffset, self.zoffset = struct.unpack('>fff', f.read(12)) self.xscale, self.yscale, self.zscale = struct.unpack('>fff', f.read(12)) self.object, self.contour, self.point, self.res, self.thresh = struct.unpack('>iiiii', f.read(20)) self.pixsize = struct.unpack('>f', f.read(4))[0] self.units = struct.unpack('>i', f.read(4))[0] self.named_units = UNITS[self.units] self.csum = struct.unpack('>i', f.read(4))[0] self.alpha, self.beta, self.gamma = struct.unpack('>fff', f.read(12)) self.isset = True return f
[docs] def add_objt(self, objt): """Add an OBJT chunk object to this IMOD object""" self.objts[self.objt_count] = objt self.objt_count += 1 self.current_objt = self.objts[self.objt_count - 1]
[docs] def add_view(self, view): """Add a VIEW chunk object to this IMOD object""" self.views[self.view_count] = view self.view_count += 1
@property def ijk_to_xyz_transform(self): return numpy.array([ self.pixsize * self.xscale * angstrom_multiplier(self.units), 0., 0., 0., 0., self.pixsize * self.yscale * angstrom_multiplier(self.units), 0., 0., 0., 0., self.pixsize * self.zscale * angstrom_multiplier(self.units), 0., ]).reshape(3, 4) @property def x_length(self): """The length of X side of the image in angstrom""" return self.xmax * self.pixsize * self.xscale * angstrom_multiplier(self.units) @property def y_length(self): """The length of Y side of the image in angstrom""" return self.ymax * self.pixsize * self.yscale * angstrom_multiplier(self.units) @property def z_length(self): """The length of Z side of the image in angstrom""" return self.zmax * self.pixsize * self.zscale * angstrom_multiplier(self.units) def __str__(self): return repr(self) def __repr__(self): string = """\ version: %s name: %s xmax: %s ymax: %s zmax: %s objsize: %s flags: %s drawmode: %s mousemode: %s blacklevel: %s whitelevel: %s xoffset: %s yoffset: %s zoffset: %s xscale: %s yscale: %s zscale: %s object: %s contour: %s point: %s res: %s thresh: %s pixsize: %s units: %s (%s) csum: %s alpha: %s beta: %s gamma: %s stored data: %s""" % ( self.version, self.name, self.xmax, self.ymax, self.zmax, self.objsize, self.flags, self.drawmode, self.mousemode, self.blacklevel, self.whitelevel, self.xoffset, self.yoffset, self.zoffset, self.xscale, self.yscale, self.zscale, self.object, self.contour, self.point, self.res, self.thresh, self.pixsize, self.units, self.named_units, self.csum, self.alpha, self.beta, self.gamma, self.most ) return string
[docs] class MOST(object): """MOST chunk class Class encapsulating storage parameters for the top-level :py:class:`sfftk.readers.modreader.IMOD` chunk. :param file f: file handle of the IMOD segmentation """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the MOST chunk""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] self.store = _dict() for i in _xrange(self.bytes // 12): store = STORE(f) f = store.read() self.store[i] = store self.isset = True return f
def __repr__(self): string = '' for i in sorted(self.store.keys()): string += self.store[i].__repr__() return string
[docs] class OBJT(object): """OBJT chunk class An IMOD file has several :py:class:`sfftk.readers.modreader.OBJT` chunks, each of which contain the data either as contours (:py:class:`sfftk.readers.modreader.CONT`) or meshes (:py:class:`sfftk.readers.modreader.MESH`). OBJT chunks also contain :py:class:`sfftk.readers.modreader.CLIP`, :py:class:`sfftk.readers.modreader.IMAT`, :py:class:`sfftk.readers.modreader.MEPA` and a :py:class:`sfftk.readers.modreader.OBST` storage chunk. """ def __init__(self, f): self.f = f self.isset = False self.conts = _dict() self.cont_count = 0 self.current_cont = None self.meshes = _dict() self.mesh_count = 0 self.current_mesh = None self.clip = None self.imat = None self.mepa = None self.obst = None
[docs] def read(self): """Read data from the file to the chunk""" f = self.f self.name = _decode(get_printable_ascii_string(struct.unpack('>64s', f.read(64))[0]), 'utf-8') self.extra = struct.unpack('>16I', f.read(64))[0] # keep an eye on this self.contsize = struct.unpack('>i', f.read(4))[0] self.flags = OBJECT_FLAGS(struct.unpack('>I', f.read(4))[0], 4) self.axis = struct.unpack('>i', f.read(4))[0] self.drawmode = struct.unpack('>i', f.read(4))[0] self.red, self.green, self.blue = struct.unpack('>fff', f.read(12)) self.pdrawsize = struct.unpack('>i', f.read(4))[0] self.symbol, self.symsize, self.linewidth2, self.linewidth, self.linesty = struct.unpack('>BBBBB', f.read(5)) self.symflags = OBJECT_SYM_FLAGS(struct.unpack('>B', f.read(1))[0], 1) self.sympad, self.trans = struct.unpack('>BB', f.read(2)) self.meshsize, self.surfsize = struct.unpack('>ii', f.read(8)) self.isset = True return f
[docs] def add_cont(self, cont): """Add a CONT chunk object to this OBJT object""" self.conts[self.cont_count] = cont self.cont_count += 1 self.current_cont = self.conts[self.cont_count - 1]
[docs] def add_mesh(self, mesh): """Add a MESH chunk object to this OBJT object""" self.meshes[self.mesh_count] = mesh self.mesh_count += 1 self.current_mesh = self.meshes[self.mesh_count - 1]
def __repr__(self): string = """\ name: %s extra: %s contsize: %s flags: %s axis: %s drawmode: %s RGB: (%s, %s, %s) pdrawsize: %s symbol: %s symsize: %s linewidth2: %s linewidth: %s linesty: %s symflags: %s sympad: %s trans: %s meshsize: %s surfsize: %s stored data: %s""" % ( self.name, self.extra, self.contsize, self.flags, self.axis, self.drawmode, self.red, self.green, self.blue, self.pdrawsize, self.symbol, self.symsize, self.linewidth2, self.linewidth, self.linesty, self.symflags, self.sympad, self.trans, self.meshsize, self.surfsize, self.obst ) return string
[docs] class OBST(object): """OBST chunk class""" def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read data from file to this object""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] self.store = _dict() for i in _xrange(self.bytes // 12): store = STORE(f) f = store.read() self.store[i] = store self.isset = True return f
def __repr__(self): string = '' for i in sorted(self.store.keys()): string += self.store[i].__repr__() return string
[docs] class CONT(object): """CONT chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False self.size = None self.cost = None
[docs] def read(self): """Read the contents of the CONT chunk""" f = self.f self.psize = struct.unpack('>i', f.read(4))[0] self.flags = CONTOUR_FLAGS(struct.unpack('>I', f.read(4))[0], 4) self.time, self.surf = struct.unpack('>ii', f.read(8)) # self.pt is a array of triples # first get the array of floats # then isolate each frame (0, 1, 2) # then zip them all together pt = struct.unpack('>' + 'fff' * self.psize, f.read(12 * self.psize)) pt_x = pt[0::3] pt_y = pt[1::3] pt_z = pt[2::3] self.pt = list(zip(pt_x, pt_y, pt_z)) self.isset = True return f
[docs] def add_size(self, size): """Modify the size attribute :param size: the size value """ self.size = size
def __repr__(self): string = """\ psize: %s flags: %s time: %s surf: %s pt: %s size: %s stored data: %s""" % (self.psize, self.flags, self.time, self.surf, self.pt, self.size, self.cost) return string
[docs] class COST(object): """COST chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the COST chunk""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] self.store = _dict() for i in _xrange(self.bytes // 12): store = STORE(f) f = store.read() self.store[i] = store self.isset = True return f
def __repr__(self): string = '' for i in sorted(self.store.keys()): string += self.store[i].__repr__() return string
[docs] class MESH(object): """MESH chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.mest = None self.isset = False
[docs] def read(self): """Read the contents of the MESH chunk""" f = self.f self.vsize, self.lsize = struct.unpack('>ii', f.read(8)) self.flag = MESH_FLAGS(struct.unpack('>I', f.read(4))[0], 4) self.time, self.surf = struct.unpack('>hh', f.read(4)) # self.vert is an array of triples # first get the array of floats # then isolate each frame (0, 1, 2) # then zip them all together vert = struct.unpack('>' + 'fff' * self.vsize, f.read(12 * self.vsize)) vert_x = vert[0::3] vert_y = vert[1::3] vert_z = vert[2::3] self.vert = tuple(zip(vert_x, vert_y, vert_z)) self.list = struct.unpack('>' + 'i' * self.lsize, f.read(4 * self.lsize)) self.isset = True return f
def __repr__(self): string = """\ vsize: %s lsize: %s flag: %s time: %s surf: %s vert: %s list: %s stored data: %s""" % (self.vsize, self.lsize, self.flag, self.time, self.surf, self.vert, self.list, self.mest) return string
[docs] class MEST(object): """MEST chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the MEST chunk""" f = self.f self.store = _dict() for i in _xrange(self.bytes // 12): store = STORE(f) f = store.read() self.store[i] = store self.isset = True return f
def __repr__(self): string = '' for i in sorted(self.store.keys()): string += self.store[i].__repr__() return string
[docs] class IMAT(object): """IMAT chunk class""" def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the IMAT chunk""" f = self.f self.bytes = struct.unpack('>I', f.read(4))[0] self.ambient, self.diffuse, self.specular, self.shininess, self.fillred, self.fillgreen, self.fillblue, \ self.quality = struct.unpack('>BBBBBBBB', f.read(8)) self.mat2 = struct.unpack('>I', f.read(4))[0] self.valblack, self.valwhite, self.matflags2, self.mat3b3 = struct.unpack('>BBBB', f.read(4)) self.isset = True return f
def __repr__(self): string = """\ bytes: %s ambient: %s diffuse: %s specular: %s shininess: %s fillred: %s fillgreen: %s fillblue: %s quality: %s mat2: %s valblack: %s valwhite: %s matflags2: %s mat3b3: %s""" % ( self.bytes, self.ambient, self.diffuse, self.specular, self.shininess, self.fillred, self.fillgreen, self.fillblue, self.quality, self.mat2, self.valblack, self.valwhite, self.matflags2, self.mat3b3 ) return string
[docs] class SLAN(object): """SLAN chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the SLAN chunk""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] self.time = struct.unpack('>i', f.read(4))[0] self.angles = struct.unpack('>fff', f.read(12)) self.center = struct.unpack('>fff', f.read(12)) self.label = get_printable_ascii_string(struct.unpack('>32s', f.read(32))[0]) return f
[docs] class VIEW(object): """VIEW chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f, first_view=False): self.f = f self.isset = False self.first_view = first_view
[docs] def read(self): """Read the contents of the VIEW chunk""" f = self.f self.objvsize = struct.unpack('>i', f.read(4))[0] if self.first_view: self.Objv = struct.unpack('>i', f.read(4))[0] else: self.Objv = struct.unpack('>%ss' % (self.objvsize), f.read(self.objvsize))[0] self.isset = True return f
def __repr__(self): if self.first_view: string = """\ objvsize: %s View: %s""" % (self.objvsize, self.Objv) else: string = """\ objvsize: %s Objv: %s""" % (self.objvsize, self.Objv) return string
[docs] class MINX(object): """MINX chunk class Model to image transformation Documented as 72 bytes but works with 76 bytes :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the MINX chunk""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] # unknown byte self.oscale = struct.unpack('>fff', f.read(12)) self.otrans = struct.unpack('>fff', f.read(12)) self.orot = struct.unpack('>fff', f.read(12)) self.cscale = struct.unpack('>fff', f.read(12)) self.ctrans = struct.unpack('>fff', f.read(12)) self.crot = struct.unpack('>fff', f.read(12)) self.isset = True return f
def __repr__(self): string = """\ bytes: %s oscale: %s otrans: %s orot: %s cscale: %s ctrans: %s crot: %s""" % ( self.bytes, self.oscale, self.otrans, self.orot, self.cscale, self.ctrans, self.crot ) return string
[docs] class CLIP(object): """CLIP chunk class :param file f: file handler for the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the CLIP chunk""" f = self.f self.count = struct.unpack('>B', f.read(1))[0] self.flags = CLIP_FLAGS(struct.unpack('>B', f.read(1))[0], 1) self.trans, self.plane = struct.unpack('>BB', f.read(2)) if self.count == 0: count = 1 else: count = self.count self.normal = struct.unpack('>fff', f.read(12 * count)) self.point = struct.unpack('>fff', f.read(12 * count)) self.something = struct.unpack('>i', f.read(4))[0] self.isset = True return f
def __repr__(self): string = """\ count: %s flags: %s trans: %s plane: %s normal: %s point: %s something: %s""" % (self.count, self.flags, self.trans, self.plane, self.normal, self.point, self.something) return string
[docs] class MCLP(object): """MCLP chunk class Model clipping plane parameters :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the MCLP chunk""" f = self.f self.count = struct.unpack('>B', f.read(1))[0] self.flags = MCLP_FLAGS(struct.unpack('>B', f.read(1))[0], 1) self.trans, self.plane = struct.unpack('>BB', f.read(2)) if self.count == 0: count = 1 else: count = self.count self.normal = struct.unpack('>fff', f.read(12 * count)) self.point = struct.unpack('>fff', f.read(12 * count)) self.something = struct.unpack('>i', f.read(4))[0] self.isset = True return f
def __repr__(self): string = """\ count: %s flags: %s trans: %s plane: %s normal: %s point: %s something: %s""" % (self.count, self.flags, self.trans, self.plane, self.normal, self.point, self.something) return string
[docs] class MEPA(object): """MEPA chunk class :param file f: file handle of the IMOD segmentation file """ def __init__(self, f): self.f = f self.isset = False
[docs] def read(self): """Read the contents of the MEPA chunk""" f = self.f self.bytes = struct.unpack('>i', f.read(4))[0] self.flags = MEPA_FLAGS(struct.unpack('>I', f.read(4))[0], 4) self.cap, self.passes, self.capSkipNz, self.inczLowRes, self.inczHighRes, self.minz, self.maxz, \ self.reserved_int = struct.unpack('>8i', f.read(32)) self.overlaps, self.tubeDiameter, self.xmin, self.xmax, self.ymin, self.ymax, self.tolLowRes, \ self.tolHighRes, self.flatCrit, self.reserved_float = struct.unpack('>10f', f.read(40)) self.isset = True return f
def __repr__(self): string = """\ bytes: %s flags: %s cap: %s passes: %s capSkipNz: %s inczLowRes: %s inczHighRes: %s minz: %s maxz: %s reserved: %s overlaps: %s tubeDiameter: %s xmin: %s xmax: %s ymin: %s ymax: %s tolLowRes: %s tolHighRes: %s flatCrit: %s reserved: %s""" % ( self.bytes, self.flags, self.cap, self.passes, self.capSkipNz, self.inczLowRes, self.inczHighRes, self.minz, self.maxz, self.reserved_int, self.overlaps, self.tubeDiameter, self.xmin, self.xmax, self.ymin, self.ymax, self.tolLowRes, self.tolHighRes, self.flatCrit, self.reserved_float) return string
[docs] def get_data(fn): """ Extract chunks from IMOD model file pointed to by the handle f :param str fn: name of IMOD file :raises ValueError: if it doesn't start with an IMOD chunk :raises ValueError: if the file lacks an IEOF chunk """ with open(fn, 'r+b') as f: # make sure the file has a 'IEOF' terminal type ID # absence of this will mean non-termination f.seek(-4, os.SEEK_END) chunk_name = _decode(struct.unpack('>4s', f.read(4))[0], 'utf-8') if chunk_name == 'IEOF': f.seek(0) else: raise ValueError("Invalid file: missing terminal IEOF; has %s instead" % chunk_name) chunk_name = _decode(struct.unpack('>4s', f.read(4))[0], 'utf-8') if chunk_name == 'IMOD': imod = None first_view = True else: raise ValueError("Invalid file: %s" % chunk_name) while chunk_name != 'IEOF': if chunk_name == 'IMOD': imod = IMOD(f) f = imod.read() elif chunk_name == 'MOST': most = MOST(f) f = most.read() imod.most = most elif chunk_name == 'OBJT': objt = OBJT(f) f = objt.read() imod.add_objt(objt) elif chunk_name == 'OGRP': # fixme: use print_date print("OGRP: skipping data...", file=sys.stderr) num_bytes = struct.unpack('>i', f.read(4))[0] f.read(num_bytes) elif chunk_name == 'OBST': obst = OBST(f) f = obst.read() imod.current_objt.obst = obst elif chunk_name == 'CONT': cont = CONT(f) f = cont.read() imod.current_objt.add_cont(cont) elif chunk_name == 'COST': cost = COST(f) f = cost.read() imod.current_objt.current_cont.cost = cost elif chunk_name == 'SIZE': size = struct.unpack('>i' + 'f' * imod.current_objt.current_cont.psize, f.read(4 * (imod.current_objt.current_cont.psize + 1))) imod.current_objt.current_cont.size = size elif chunk_name == 'MESH': mesh = MESH(f) f = mesh.read() imod.current_objt.add_mesh(mesh) elif chunk_name == 'SKLI': # fixme: use print_date print("SKLI: skipping data...", file=sys.stderr) num_bytes = struct.unpack('>i', f.read(4))[0] f.read(num_bytes) elif chunk_name == 'MEST': mest = MEST(f) f = mest.read() imod.current_objt.current_mesh.mest = mest elif chunk_name == 'CLIP': clip = CLIP(f) f = clip.read() imod.current_objt.clip = clip elif chunk_name == 'MCLP': mclp = MCLP(f) f = mclp.read() imod.mclp = mclp elif chunk_name == 'IMAT': imat = IMAT(f) f = imat.read() imod.current_objt.imat = imat elif chunk_name == 'SLAN': # the class exists but has not been integrated; need an example of where it occurs # fixme: use print_date print("SLAN: skipping data...", file=sys.stderr) num_bytes = struct.unpack('>i', f.read(4))[0] f.read(num_bytes) elif chunk_name == 'MEPA': mepa = MEPA(f) f = mepa.read() imod.current_objt.mepa = mepa elif chunk_name == 'VIEW': if first_view: view = VIEW(f, first_view=first_view) first_view = False else: view = VIEW(f) f = view.read() imod.add_view(view) elif chunk_name == 'MINX': minx = MINX(f) f = minx.read() imod.minx = minx elif chunk_name == 'OLBL': chunk_length, next_chunk = find_chunk_length(f) # for diagnostics only # fixme: use print_date print("The chunk {} has a length of {} bytes. The next chunk is {}.".format( chunk_name, chunk_length, next_chunk )) else: # fixme: use print_date print("This chunk is %(chunk_name)s." % {'chunk_name': chunk_name}) chunk_length, next_chunk = find_chunk_length(f) # for diagnostics only # fixme: use print_date print("It has a length of %(chunk_length)s bytes." % {'chunk_length': chunk_length}) # fixme: use print_date print("The next chunk is %(next_chunk)s." % {'next_chunk': next_chunk}) raise ValueError("Unknown chunk named '%s'" % chunk_name) # next chunk chunk_name = _decode(struct.unpack('>4s', f.read(4))[0], 'utf-8') # if isinstance(_chunk_name, _bytes): # chunk_name = _chunk_name.decode('utf-8') # else: # chunk_name = _chunk_name return imod
[docs] def show_chunks(fn): """ Show the sequence and number of chunks pointed to the by file handle f. :param str fn: name of IMOD file """ marker_sequence = list() seen_view = False view_size = 0 byte = b'' with open(fn, 'rb') as f: while byte != b'IEOF': byte = struct.unpack('>4s', f.read(4))[0] if byte in KEY_WORDS: # or (byte[0] in ALPHA and byte[1] in ALPHA and byte[2] in ALPHA and byte[3] in ALPHA): marker_sequence.append(byte) if byte == b'VIEW': seen_view = True # print "view size = %s" % view_size if seen_view: view_size += 1 f.seek(-3, os.SEEK_CUR) # advance -3 positions back marker_sequence.append(byte) # IEOF marker_count = dict(zip( KEY_WORDS, [0] * len(KEY_WORDS) )) for i in _xrange(len(marker_sequence) - 1): current_marker = marker_sequence[i] next_marker = marker_sequence[i + 1] if next_marker == current_marker: marker_count[current_marker] += 1 elif next_marker != current_marker: marker_count[current_marker] += 1 # fixme: use print_date print(current_marker, marker_count[current_marker]) # waiter = raw_input('') marker_count[current_marker] = 0 # print the last one # fixme: use print_date print(next_marker) return