fabio.mar345image

Package fabio :: Module mar345image |
[hide private]
[frames] | no frames]

# Source Code for Module fabio.mar345image

      1  #!/usr/bin/env python
      2  #coding: utf8
      3  from __future__ import with_statement
      4  __doc__ = """
      5
      6  Authors:
      7  ........
      8  * Henning O. Sorensen & Erik Knudsen:
      9    Center for Fundamental Research: Metal Structures in Four Dimensions;
     10    Risoe National Laboratory;
     11    Frederiksborgvej 399;
     12    DK-4000 Roskilde;
     13    email:erik.knudsen@risoe.dk
     14  * Jon Wright, Jérôme Kieffer & Gaël Goret:
     15    European Synchrotron Radiation Facility;
     16    Grenoble (France)
     17
     18
     19  """
     20
     21  from fabioimage import fabioimage
     22  import numpy, struct, time, sys
     23  import logging
     24  logger = logging.getLogger("mar345image")
     25  from compression import compPCK, decPCK
     26
     27 -class mar345image(fabioimage):
     28      _need_a_real_file = True
     29 -    def __init__(self, *args, **kwargs):
     30          fabioimage.__init__(self, *args, **kwargs)
     31          self.numhigh = None
     32          self.numpixels = None
     33
     34 -    def read(self, fname, frame=None):
     35          """ Read a mar345 image"""
     36          self.filename = fname
     37          f = self._open(self.filename, "rb")
     38          self._readheader(f)
     39          if 'compressed' in self.header['Format']:
     40              try:
     41                  self.data = decPCK(f, self.dim1, self.dim2, self.numhigh)
     42              except Exception, error:
     43                  logger.error('%s. importing the mar345_io backend: generate an empty 1x1 picture' % error)
     44                  f.close()
     45                  self.dim1 = 1
     46                  self.dim2 = 1
     47                  self.bytecode = numpy.int #
     48                  self.data = numpy.resize(numpy.array([0], numpy.int), [1, 1])
     49                  return self
     50
     51          else:
     52              logger.error("cannot handle these formats yet " + \
     53                  "due to lack of documentation")
     54              return None
     55          self.bytecode = numpy.uint
     56          f.close()
     57          return self
     58
     59 -    def _readheader(self, infile=None):
     60          """ Read a mar345 image header """
     61          # clip was not used anywhere - commented out
     62          # clip = '\x00'
     63          #using a couple of local variables inside this function
     64          f = infile
     65          h = {}
     66
     67          #header is 4096 bytes long
     68          l = f.read(64)
     69          #the contents of the mar345 header is taken to be as
     70          # described in
     71          # http://www.mar-usa.com/support/downloads/mar345_formats.pdf
     72          #the first 64 bytes are 4-byte integers (but in the CBFlib
     73          # example image it seems to 128 bytes?)
     74          #first 4-byte integer is a marker to check endianness
     75          if struct.unpack("<i", l[0:4])[0] == 1234:
     76              fs = '<i'
     77          else:
     78              fs = '>i'
     79
     80          #image dimensions
     81          self.dim1 = self.dim2 = int(struct.unpack(fs, l[4:8])[0])
     82          #number of high intensity pixels
     83          self.numhigh = struct.unpack(fs, l[2 * 4 : (2 + 1) * 4])[0]
     84          h['NumHigh'] = self.numhigh
     85          #Image format
     86          i = struct.unpack(fs, l[3 * 4 : (3 + 1) * 4])[0]
     87          if i == 1:
     88              h['Format'] = 'compressed'
     89          elif i == 2:
     90              h['Format'] = 'spiral'
     91          else:
     92              h['Format'] = 'compressed'
     93              logger.warning("image format could not be detetermined" + \
     94                  "- assuming compressed mar345")
     95          #collection mode
     96          h['Mode'] = {0:'Dose', 1: 'Time'}[struct.unpack(fs, l[4 * 4:(4 + 1) * 4])[0]]
     97          #total number of pixels
     98          self.numpixels = struct.unpack(fs, l[5 * 4:(5 + 1) * 4])[0]
     99          h['NumPixels'] = str(self.numpixels)
    100          #pixel dimensions (length,height) in mm
    101          h['PixelLength'] = struct.unpack(fs, l[6 * 4:(6 + 1) * 4])[0] / 1000.0
    102          h['PixelHeight'] = struct.unpack(fs, l[7 * 4:(7 + 1) * 4])[0] / 1000.0
    103          #x-ray wavelength in AA
    104          h['Wavelength'] = struct.unpack(fs, l[8 * 4:(8 + 1) * 4])[0] / 1000000.0
    105          #used distance
    106          h['Distance'] = struct.unpack(fs, l[9 * 4:(9 + 1) * 4])[0] / 1000.0
    107          #starting and ending phi
    108          h['StartPhi'] = struct.unpack(fs, l[10 * 4:11 * 4])[0] / 1000.0
    109          h['EndPhi'] = struct.unpack(fs, l[11 * 4:12 * 4])[0] / 1000.0
    110          #starting and ending omega
    111          h['StartOmega'] = struct.unpack(fs, l[12 * 4:13 * 4])[0] / 1000.0
    112          h['EndOmega'] = struct.unpack(fs, l[13 * 4:14 * 4])[0] / 1000.0
    113          #Chi and Twotheta angles
    114          h['Chi'] = struct.unpack(fs, l[14 * 4:15 * 4])[0] / 1000.0
    115          h['TwoTheta'] = struct.unpack(fs, l[15 * 4:16 * 4])[0] / 1000.0
    116
    117          #the rest of the header is ascii
    118          # TODO: validate these values against the binaries already read
    119          l = f.read(128)
    120          if not 'mar research' in l:
    121              logger.warning("the string \"mar research\" should be in " + \
    122                  "bytes 65-76 of the header but was not")
    123              start = 128
    124          else:
    125              start = l.index('mar research')
    126              f.seek(64 + start)
    127          l = f.read(4096 - start - 64).strip()
    128          for m in l.splitlines():
    129              if m == 'END OF HEADER':
    130                  break
    131              n = m.split(' ', 1)
    132              if n[0] == '':
    133                  continue
    134              if n[0] in ('PROGRAM', 'DATE', 'SCANNER', 'HIGH', 'MULTIPLIER',
    135                          'GAIN', 'WAVELENGTH', 'DISTANCE', 'RESOLUTION',
    136                          'CHI', 'TWOTHETA', 'MODE', 'TIME', 'GENERATOR',
    137                          'MONOCHROMATOR', 'REMARK'):
    138                  logger.debug("reading: %s %s", n[0], n[1])
    139                  h[n[0]] = n[1].strip()
    140                  continue
    141              if n[0] in ('FORMAT'):
    142                  (h['DIM'], h['FORMAT_TYPE'], h['NO_PIXELS']) = n[1].split()
    143                  continue
    144              if n[0] in ('PIXEL', 'OFFSET', 'PHI', 'OMEGA', 'COUNTS',
    145                          'CENTER', 'INTENSITY', 'HISTOGRAM', 'COLLIMATOR'):
    146                  n = m.split()
    147                  h.update([(n[0] + '_' + n[j], n[j + 1]) for j in range(1, len(n), 2)])
    148                  continue
    149          self.header = h
    150          return h
    151
    152 -    def write(self, fname):
    153          """Try to write mar345 file. This is still in beta version.
    154          It uses CCP4 (LGPL) PCK1 algo from JPA"""
    155          headers = self._writeheader()
    156          hotpixels = self._high_intensity_pixel_records()
    157          compressed_stream = compPCK(self.data)
    158          try:
    159              outfile = self._open(fname, mode="wb")
    160              outfile.write(headers)
    161              outfile.write(hotpixels)
    162              outfile.write(compressed_stream)
    163              outfile.close()
    164          except Exception, error:
    165              logger.error("Error in writing file %s: %s" % (fname, error))
    166
    167 -    def _writeheader(self, linesep="\n", size=4096):#the standard padding does not inclued
    168          """
    169          @param linesep: end of line separator
    170          @return string/bytes containing the mar345 header
    171          """
    172          try:
    173              version = sys.modules["fabio"].version
    174          except (KeyError, AttributeError):
    175              version = "0.1.1"
    176          lnsep = len(linesep)
    177
    178          self.header["HIGH"] = str(self.nb_overflow_pixels())
    179          binheader = numpy.zeros(16, "int32")
    180          binheader[:4] = numpy.array([1234, self.dim1, int(self.header["HIGH"]), 1])
    181          binheader[4] = (self.header.get("MODE", "TIME") == "TIME")
    182          binheader[5] = self.dim1 * self.dim2
    183          binheader[6] = int(self.header.get("PIXEL_LENGTH", 1))
    184          binheader[7] = int(self.header.get("PIXEL_HEIGHT", 1))
    185          binheader[8] = int(float(self.header.get("WAVELENGTH", 1)) * 1e6)
    186          binheader[9] = int(float(self.header.get("DISTANCE", 1)) * 1e3)
    187          binheader[10] = int(float(self.header.get("PHI_START", 1)) * 1e3)
    188          binheader[11] = int(float(self.header.get("PHI_END", 1)) * 1e3)
    189          binheader[12] = int(float(self.header.get("OMEGA_START", 1)) * 1e3)
    190          binheader[13] = int(float(self.header.get("OMEGA_END", 1)) * 1e3)
    191          binheader[14] = int(float(self.header.get("CHI", 1)) * 1e3)
    192          binheader[15] = int(float(self.header.get("TWOTHETA", 1)) * 1e3)
    193          lstout = [binheader.tostring() + 'mar research'.ljust(64 - lnsep)]
    194          lstout.append("PROGRAM".ljust(15) + (str(self.header.get("PROGRAM", "FabIO Version %s" % (version))).ljust(49 - lnsep)))
    195          lstout.append("DATE".ljust(15) + (str(self.header.get("DATE", time.ctime()))).ljust(49 - lnsep))
    196          key = "SCANNER"
    197          if key in self.header:
    198              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    199          key = "FORMAT_TYPE"
    200          if key in self.header:
    201              lstout.append("FORMAT".ljust(15) + ("%s  %s %s" % (self.dim1, self.header[key], self.dim1 * self.dim2)).ljust(49 - lnsep))
    202          key = "HIGH"
    203          if key in self.header:
    204              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    205          key1 = "PIXEL_LENGTH"
    206          key2 = "PIXEL_HEIGHT"
    207          if (key1 in self.header) and (key2 in self.header):
    208              lstout.append("PIXEL".ljust(15) + ("LENGTH %s  HEIGHT %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    209          key1 = "OFFSET_ROFF"
    210          key2 = "OFFSET_TOFF"
    211          if key1 in self.header and key2 in self.header:
    212              lstout.append("OFFSET".ljust(15) + ("ROFF %s  TOFF %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    213          key = "MULTIPLIER"
    214          if key in self.header:
    215              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    216          key = "GAIN"
    217          if key in self.header:
    218              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    219          key = "WAVELENGTH"
    220          if key in self.header:
    221              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    222          key = "DISTANCE"
    223          if key in self.header:
    224              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    225          key = "RESOLUTION"
    226          if key in self.header:
    227              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    228          key1 = "PHI_START"
    229          key2 = "PHI_END"
    230          key3 = "PHI_OSC"
    231          if (key1 in self.header) and (key2 in self.header) and (key3 in self.header):
    232              lstout.append("PHI".ljust(15) + ("START %s  END %s  OSC %s" % (self.header[key1], self.header[key2], self.header[key3])).ljust(49 - lnsep))
    233          key1 = "OMEGA_START"
    234          key2 = "OMEGA_END"
    235          key3 = "OMEGA_OSC"
    236          if (key1 in self.header) and (key2 in self.header) and (key3 in self.header):
    237              lstout.append("OMEGA".ljust(15) + ("START %s  END %s  OSC %s" % (self.header[key1], self.header[key2], self.header[key3])).ljust(49 - lnsep))
    238          key = "CHI"
    239          if key in self.header:
    240              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    241          key = "TWOTHETA"
    242          if key in self.header:
    243              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    244          key1 = "CENTER_X"
    245          key2 = "CENTER_Y"
    246          if (key1 in self.header) and (key2 in self.header):
    247              lstout.append("CENTER".ljust(15) + ("X %s  Y %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    248          key = "MODE"
    249          if key in self.header:
    250              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    251          key = "TIME"
    252          if key in self.header:
    253              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    254          key1 = "COUNTS_START"
    255          key2 = "COUNTS_END"
    256          key3 = "COUNTS_NMEAS"
    257          if key1 in self.header and key2 in self.header and key3 in self.header:
    258              lstout.append("COUNTS".ljust(15) + ("START %s  END %s  NMEAS %s" % (self.header[key1], self.header[key2], self.header[key3])).ljust(49 - lnsep))
    259          key1 = "COUNTS_MIN"
    260          key2 = "COUNTS_MAX"
    261          if key1 in self.header and key2 in self.header:
    262              lstout.append("COUNTS".ljust(15) + ("MIN %s  MAX %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    263          key1 = "COUNTS_AVE"
    264          key2 = "COUNTS_SIG"
    265          if key1 in self.header and key2 in self.header:
    266              lstout.append("COUNTS".ljust(15) + ("AVE %s  SIG %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    267          key1 = "INTENSITY_MIN"
    268          key2 = "INTENSITY_MAX"
    269          key3 = "INTENSITY_AVE"
    270          key4 = "INTENSITY_SIG"
    271          if key1 in self.header and key2 in self.header and key3 in self.header and key4 in self.header:
    272              lstout.append("INTENSITY".ljust(15) + ("MIN %s  MAX %s  AVE %s  SIG %s" % (self.header[key1], self.header[key2], self.header[key3], self.header[key4])).ljust(49 - lnsep))
    273          key1 = "HISTOGRAM_START"
    274          key2 = "HISTOGRAM_END"
    275          key3 = "HISTOGRAM_MAX"
    276          if key1 in self.header and key2 in self.header and key3 in self.header:
    277              lstout.append("HISTOGRAM".ljust(15) + ("START %s  END %s  MAX %s" % (self.header[key1], self.header[key2], self.header[key3])).ljust(49 - lnsep))
    278          key = "GENERATOR"
    279          if key in self.header:
    280              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    281          key = "MONOCHROMATOR"
    282          if key in self.header:
    283              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    284          key1 = "COLLIMATOR_WIDTH"
    285          key2 = "COLLIMATOR_HEIGHT"
    286          if key1 in self.header and key2 in self.header:
    287              lstout.append("COLLIMATOR".ljust(15) + ("WIDTH %s  HEIGHT %s" % (self.header[key1], self.header[key2])).ljust(49 - lnsep))
    288          key = "REMARK"
    289          if key in self.header:
    290              lstout.append(key.ljust(15) + str(self.header[key]).ljust(49 - lnsep))
    291          else:
    292              lstout.append(key.ljust(64 - lnsep))
    293          key = "END OF HEADER"
    294          lstout.append(key)
    295          return linesep.join(lstout).ljust(size)
    296
    297
    298 -    def _high_intensity_pixel_records(self):
    299          flt_data = self.data.flatten()
    300          pix_location = numpy.where(flt_data > 65535)[0]
    301          nb_pix = pix_location.size
    302          if nb_pix % 8 == 0:
    303              tmp = numpy.zeros((nb_pix, 2), dtype="int32")
    304          else:
    305              tmp = numpy.zeros(((nb_pix // 8 + 1) * 8, 2), dtype="int32")
    306          tmp[:nb_pix, 0] = pix_location + 1
    307          tmp[:nb_pix, 1] = flt_data[pix_location]
    308          return tmp.tostring()
    309
    310 -    def nb_overflow_pixels(self):
    311          return (self.data > 65535).sum()
    312
    313      @staticmethod
    314 -    def checkData(data=None):
    315          if data is None:
    316              return None
    317          else:
    318  #            enforce square image
    319              shape = data.shape
    320              assert len(shape) == 2, "image has 2 dimensions"
    321              mshape = max(shape)
    322              z = numpy.zeros((mshape, mshape), dtype=int)
    323              z[:shape[0], :shape[1]] = data
    324              return z
    325