# -*- coding: utf-8 -*- # imageio is distributed under the terms of the (new) BSD License. """ SPE file reader """ import os import logging import numpy as np from .. import formats from ..core import Format logger = logging.getLogger(__name__) class Spec: """SPE file specification data Tuples of (offset, datatype, count), where offset is the offset in the SPE file and datatype is the datatype as used in `numpy.fromfile`() `data_start` is the offset of actual image data. `dtypes` translates SPE datatypes (0...4) to numpy ones, e. g. dtypes[0] is dtype("=3. data_end = ( info["xml_footer_offset"] if info["file_header_ver"] >= 3 else os.path.getsize(self.request.get_local_filename()) ) l = data_end - Spec.data_start l //= self._shape[0] * self._shape[1] * self._dtype.itemsize if l != self._len: logger.warning( "The file header of %s claims there are %s frames, " "but there are actually %s frames.", self.request.filename, self._len, l, ) self._len = min(l, self._len) self._meta = None def _get_meta_data(self, index): if self._meta is None: if self._file_header_ver < 3: self._init_meta_data_pre_v3() else: self._init_meta_data_post_v3() return self._meta def _close(self): # The file should be closed by `self.request` pass def _init_meta_data_pre_v3(self): self._meta = self._parse_header(Spec.metadata) nr = self._meta.pop("NumROI", None) nr = 1 if nr < 1 else nr self._meta["ROIs"] = roi_array_to_dict(self._meta["ROIs"][:nr]) # chip sizes self._meta["chip_size"] = [ self._meta.pop("xDimDet", None), self._meta.pop("yDimDet", None), ] self._meta["virt_chip_size"] = [ self._meta.pop("VChipXdim", None), self._meta.pop("VChipYdim", None), ] self._meta["pre_pixels"] = [ self._meta.pop("XPrePixels", None), self._meta.pop("YPrePixels", None), ] self._meta["post_pixels"] = [ self._meta.pop("XPostPixels", None), self._meta.pop("YPostPixels", None), ] # comments self._meta["comments"] = [str(c) for c in self._meta["comments"]] # geometric operations g = [] f = self._meta.pop("geometric", 0) if f & 1: g.append("rotate") if f & 2: g.append("reverse") if f & 4: g.append("flip") self._meta["geometric"] = g # Make some additional information more human-readable t = self._meta["type"] if 1 <= t <= len(Spec.controllers): self._meta["type"] = Spec.controllers[t - 1] else: self._meta["type"] = "" m = self._meta["readout_mode"] if 1 <= m <= len(Spec.readout_modes): self._meta["readout_mode"] = Spec.readout_modes[m - 1] else: self._meta["readout_mode"] = "" # bools for k in ( "absorb_live", "can_do_virtual_chip", "threshold_min_live", "threshold_max_live", ): self._meta[k] = bool(self._meta[k]) # frame shape self._meta["frame_shape"] = self._shape def _parse_header(self, spec): ret = {} # Decode each string from the numpy array read by np.fromfile decode = np.vectorize(lambda x: x.decode(self._char_encoding)) for name, sp in spec.items(): self._file.seek(sp[0]) cnt = 1 if len(sp) < 3 else sp[2] v = np.fromfile(self._file, dtype=sp[1], count=cnt) if v.dtype.kind == "S" and name not in Spec.no_decode: # Silently ignore string decoding failures try: v = decode(v) except Exception: logger.warning( 'Failed to decode "{}" metadata ' "string. Check `char_encoding` " "parameter.".format(name) ) try: # For convenience, if the array contains only one single # entry, return this entry itself. v = v.item() except ValueError: v = np.squeeze(v) ret[name] = v return ret def _init_meta_data_post_v3(self): info = self._parse_header(Spec.basic) self._file.seek(info["xml_footer_offset"]) xml = self._file.read() self._meta = {"__xml": xml} def _get_length(self): if self.request.mode[1] in "vV": return 1 else: return self._len def _get_data(self, index): if index < 0: raise IndexError("Image index %i < 0" % index) if index >= self._len: raise IndexError("Image index %i > %i" % (index, self._len)) if self.request.mode[1] in "vV": if index != 0: raise IndexError("Index has to be 0 in v and V modes") self._file.seek(Spec.data_start) data = np.fromfile( self._file, dtype=self._dtype, count=self._shape[0] * self._shape[1] * self._len, ) data = data.reshape((self._len,) + self._shape) else: self._file.seek( Spec.data_start + index * self._shape[0] * self._shape[1] * self._dtype.itemsize ) data = np.fromfile( self._file, dtype=self._dtype, count=self._shape[0] * self._shape[1] ) data = data.reshape(self._shape) return data, self._get_meta_data(index) def roi_array_to_dict(a): """Convert the `ROIs` structured arrays to :py:class:`dict` Parameters ---------- a : numpy.ndarray Structured array containing ROI data Returns ------- list of dict One dict per ROI. Keys are "top_left", "bottom_right", and "bin", values are tuples whose first element is the x axis value and the second element is the y axis value. """ l = [] a = a[["startx", "starty", "endx", "endy", "groupx", "groupy"]] for sx, sy, ex, ey, gx, gy in a: d = { "top_left": [int(sx), int(sy)], "bottom_right": [int(ex), int(ey)], "bin": [int(gx), int(gy)], } l.append(d) return l fmt = SpeFormat("spe", "SPE file format", ".spe", "iIvV") formats.add_format(fmt, overwrite=True)