fabio.OXDimage

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

# Source Code for Module fabio.OXDimage

      1  #!/usr/bin/env python

      2  #coding: utf8

      3

      4  from __future__ import with_statement
      5  __doc__ = """

      6  Reads Oxford Diffraction Sapphire 3 images

      7

      8  Authors:

      9  ........

     10  * Henning O. Sorensen & Erik Knudsen:

     11    Center for Fundamental Research: Metal Structures in Four Dimensions;

     12    Risoe National Laboratory;

     13    Frederiksborgvej 399;

     14    DK-4000 Roskilde;

     15    email:erik.knudsen@risoe.dk

     16  * Jon Wright, Jérôme Kieffer & Gaël Goret:

     17    European Synchrotron Radiation Facility;

     18    Grenoble (France)

     19

     20  """
     21

     22  import time, logging, struct
     23  logger = logging.getLogger("OXDimage")
     24  import numpy
     25  from fabioimage import fabioimage
     26  from compression import decTY1, compTY1
     27

     28  try:
     29      from numpy import rad2deg, deg2rad
     30  except ImportError: #naive implementation for very old numpy (v1.0.1 on MacOSX from Risoe)
     31      rad2deg = lambda x: 180.0 * x / numpy.pi
     32      deg2rad = lambda x: x * numpy.pi / 180.
     33

     34  DETECTOR_TYPES = {0: 'Sapphire/KM4CCD (1x1: 0.06mm, 2x2: 0.12mm)',

     35  1: 'Sapphire2-Kodak (1x1: 0.06mm, 2x2: 0.12mm)',

     36  2: 'Sapphire3-Kodak (1x1: 0.03mm, 2x2: 0.06mm, 4x4: 0.12mm)',

     37  3: 'Onyx-Kodak (1x1: 0.06mm, 2x2: 0.12mm, 4x4: 0.24mm)',

     38  4: 'Unknown Oxford diffraction detector'}
     39

     40  DEFAULT_HEADERS = {'Header Version':  'OD SAPPHIRE  3.0',

     41                     'Compression': "TY1",

     42                     'Header Size In Bytes': 5120,

     43                     "ASCII Section size in Byte": 256,

     44                     "General Section size in Byte": 512,

     45                     "Special Section size in Byte": 768,

     46                     "KM4 Section size in Byte": 1024,

     47                     "Statistic Section in Byte": 512,

     48                     "History Section in Byte": 2048,

     49                     'NSUPPLEMENT':0

     50                     }
     51

     52 -class OXDimage(fabioimage):
     53      """

     54      Oxford Diffraction Sapphire 3 images reader/writer class

     55      """
     56 -    def _readheader(self, infile):
     57

     58          infile.seek(0)
     59

     60          # Ascii header part 256 byes long

     61          self.header['Header Version'] = infile.readline()[:-2]
     62          block = infile.readline()
     63          self.header['Compression'] = block[12:15]
     64          block = infile.readline()
     65          self.header['NX'] = int(block[3:7])
     66          self.header['NY'] = int(block[11:15])
     67          self.header['OI'] = int(block[19:26])
     68          self.header['OL'] = int(block[30:37])
     69          block = infile.readline()
     70          self.header['Header Size In Bytes'] = int(block[8:15])
     71          self.header['General Section size in Byte'] = int(block[19:26])
     72          self.header['Special Section size in Byte'] = int(block[30:37])
     73          self.header['KM4 Section size in Byte'] = int(block[41:48])
     74          self.header['Statistic Section in Byte'] = int(block[52:59])
     75          self.header['History Section in Byte'] = int(block[63:])
     76          block = infile.readline()
     77          self.header['NSUPPLEMENT'] = int(block[12:19])
     78          block = infile.readline()
     79          self.header['Time'] = block[5:29]
     80          self.header["ASCII Section size in Byte"] = self.header['Header Size In Bytes']\

     81                                                  - self.header['General Section size in Byte']\

     82                                                  - self.header['Special Section size in Byte'] \

     83                                                  - self.header['KM4 Section size in Byte']\

     84                                                  - self.header['Statistic Section in Byte']\

     85                                                  - self.header['History Section in Byte']\

     86          # Skip to general section (NG) 512 byes long <<<<<<"
     87          infile.seek(self.header["ASCII Section size in Byte"])
     88          block = infile.read(self.header['General Section size in Byte'])
     89          self.header['Binning in x'] = numpy.fromstring(block[0:2], numpy.uint16)[0]
     90          self.header['Binning in y'] = numpy.fromstring(block[2:4], numpy.uint16)[0]
     91          self.header['Detector size x'] = numpy.fromstring(block[22:24], numpy.uint16)[0]
     92          self.header['Detector size y'] = numpy.fromstring(block[24:26], numpy.uint16)[0]
     93          self.header['Pixels in x'] = numpy.fromstring(block[26:28], numpy.uint16)[0]
     94          self.header['Pixels in y'] = numpy.fromstring(block[28:30], numpy.uint16)[0]
     95          self.header['No of pixels'] = numpy.fromstring(block[36:40], numpy.uint32)[0]
     96

     97          # Speciel section (NS) 768 bytes long

     98          block = infile.read(self.header['Special Section size in Byte'])
     99          self.header['Gain'] = numpy.fromstring(block[56:64], numpy.float)[0]
    100          self.header['Overflows flag'] = numpy.fromstring(block[464:466], numpy.int16)[0]
    101          self.header['Overflow after remeasure flag'] = numpy.fromstring(block[466:468], numpy.int16)[0]
    102          self.header['Overflow threshold'] = numpy.fromstring(block[472:476], numpy.int32)[0]
    103          self.header['Exposure time in sec'] = numpy.fromstring(block[480:488], numpy.float)[0]
    104          self.header['Overflow time in sec'] = numpy.fromstring(block[488:496], numpy.float)[0]
    105          self.header['Monitor counts of raw image 1'] = numpy.fromstring(block[528:532], numpy.int32)[0]
    106          self.header['Monitor counts of raw image 2'] = numpy.fromstring(block[532:536], numpy.int32)[0]
    107          self.header['Monitor counts of overflow raw image 1'] = numpy.fromstring(block[536:540], numpy.int32)[0]
    108          self.header['Monitor counts of overflow raw image 2'] = numpy.fromstring(block[540:544], numpy.int32)[0]
    109          self.header['Unwarping'] = numpy.fromstring(block[544:548], numpy.int32)[0]
    110          self.header['Detector type'] = DETECTOR_TYPES[numpy.fromstring(block[548:552], numpy.int32)[0]]
    111          self.header['Real pixel size x (mm)'] = numpy.fromstring(block[568:576], numpy.float)[0]
    112          self.header['Real pixel size y (mm)'] = numpy.fromstring(block[576:584], numpy.float)[0]
    113

    114          # KM4 goniometer section (NK) 1024 bytes long

    115          block = infile.read(self.header['KM4 Section size in Byte'])
    116          # Spatial correction file

    117          self.header['Spatial correction file'] = block[26:272].strip("\x00")
    118          self.header['Spatial correction file date'] = block[0:26].strip("\x00")
    119          # Angles are in steps due to stepper motors - conversion factor RAD

    120          # angle[0] = omega, angle[1] = theta, angle[2] = kappa, angle[3] = phi,

    121          start_angles_step = numpy.fromstring(block[284:304], numpy.int32)
    122          end_angles_step = numpy.fromstring(block[324:344], numpy.int32)
    123          step2rad = numpy.fromstring(block[368:408], numpy.float)
    124          step_angles_deg = rad2deg(step2rad)
    125          # calc angles

    126          start_angles_deg = start_angles_step * step_angles_deg
    127          end_angles_deg = end_angles_step * step_angles_deg
    128          self.header['Omega start in deg'] = start_angles_deg[0]
    129          self.header['Theta start in deg'] = start_angles_deg[1]
    130          self.header['Kappa start in deg'] = start_angles_deg[2]
    131          self.header['Phi start in deg'] = start_angles_deg[3]
    132          self.header['Omega end in deg'] = end_angles_deg[0]
    133          self.header['Theta end in deg'] = end_angles_deg[1]
    134          self.header['Kappa end in deg'] = end_angles_deg[2]
    135          self.header['Phi end in deg'] = end_angles_deg[3]
    136          self.header['Omega step in deg'] = step_angles_deg[0]
    137          self.header['Theta step in deg'] = step_angles_deg[1]
    138          self.header['Kappa step in deg'] = step_angles_deg[2]
    139          self.header['Phi step in deg'] = step_angles_deg[3]
    140

    141

    142          zero_correction_soft_step = numpy.fromstring(block[512:532], numpy.int32)
    143          zero_correction_soft_deg = zero_correction_soft_step * step_angles_deg
    144          self.header['Omega zero corr. in deg'] = zero_correction_soft_deg[0]
    145          self.header['Theta zero corr. in deg'] = zero_correction_soft_deg[1]
    146          self.header['Kappa zero corr. in deg'] = zero_correction_soft_deg[2]
    147          self.header['Phi zero corr. in deg'] = zero_correction_soft_deg[3]
    148          # Beam rotation about e2,e3

    149          self.header['Beam rot in deg (e2)'] = numpy.fromstring(block[552:560], numpy.float)[0]
    150          self.header['Beam rot in deg (e3)'] = numpy.fromstring(block[560:568], numpy.float)[0]
    151          # Wavelenghts alpha1, alpha2, beta

    152          self.header['Wavelength alpha1'] = numpy.fromstring(block[568:576], numpy.float)[0]
    153          self.header['Wavelength alpha2'] = numpy.fromstring(block[576:584], numpy.float)[0]
    154          self.header['Wavelength alpha'] = numpy.fromstring(block[584:592], numpy.float)[0]
    155          self.header['Wavelength beta'] = numpy.fromstring(block[592:600], numpy.float)[0]
    156

    157          # Detector tilts around e1,e2,e3 in deg

    158          self.header['Detector tilt e1 in deg'] = numpy.fromstring(block[640:648], numpy.float)[0]
    159          self.header['Detector tilt e2 in deg'] = numpy.fromstring(block[648:656], numpy.float)[0]
    160          self.header['Detector tilt e3 in deg'] = numpy.fromstring(block[656:664], numpy.float)[0]
    161

    162

    163          # Beam center

    164          self.header['Beam center x'] = numpy.fromstring(block[664:672], numpy.float)[0]
    165          self.header['Beam center y'] = numpy.fromstring(block[672:680], numpy.float)[0]
    166          # Angle (alpha) between kappa rotation axis and e3 (ideally 50 deg)

    167          self.header['Alpha angle in deg'] = numpy.fromstring(block[672:680], numpy.float)[0]
    168          # Angle (beta) between phi rotation axis and e3 (ideally 0 deg)

    169          self.header['Beta angle in deg'] = numpy.fromstring(block[672:680], numpy.float)[0]
    170

    171          # Detector distance

    172          self.header['Distance in mm'] = numpy.fromstring(block[712:720], numpy.float)[0]
    173          # Statistics section (NS) 512 bytes long

    174          block = infile.read(self.header['Statistic Section in Byte'])
    175          self.header['Stat: Min '] = numpy.fromstring(block[0:4], numpy.int32)[0]
    176          self.header['Stat: Max '] = numpy.fromstring(block[4:8], numpy.int32)[0]
    177          self.header['Stat: Average '] = numpy.fromstring(block[24:32], numpy.float)[0]
    178          self.header['Stat: Stddev '] = numpy.sqrt(numpy.fromstring(block[32:40], numpy.float)[0])
    179          self.header['Stat: Skewness '] = numpy.fromstring(block[40:48], numpy.float)[0]
    180

    181          # History section (NH) 2048 bytes long

    182          block = infile.read(self.header['History Section in Byte'])
    183          self.header['Flood field image'] = block[99:126].strip("\x00")
    184

    185 -    def read(self, fname, frame=None):
    186          """

    187          Read in header into self.header and

    188              the data   into self.data

    189          """
    190          self.header = {}
    191          self.resetvals()
    192          infile = self._open(fname)
    193          self._readheader(infile)
    194

    195          infile.seek(self.header['Header Size In Bytes'])
    196

    197          # Compute image size

    198          try:
    199              self.dim1 = int(self.header['NX'])
    200              self.dim2 = int(self.header['NY'])
    201          except:
    202              raise Exception("Oxford  file", str(fname) + \

    203                                  "is corrupt, cannot read it")
    204          #

    205          if self.header['Compression'] == 'TY1':
    206              #Compressed with the KM4CCD compression

    207              raw8 = infile.read(self.dim1 * self.dim2)
    208              raw16 = None
    209              raw32 = None
    210              if self.header['OI'] > 0:
    211                  raw16 = infile.read(self.header['OI'] * 2)
    212              if self.header['OL'] > 0:
    213                  raw32 = infile.read(self.header['OL'] * 4)
    214              #DEBUG stuff ...

    215              self.raw8 = raw8
    216              self.raw16 = raw16
    217              self.raw32 = raw32
    218              #END DEBUG

    219              block = decTY1(raw8, raw16, raw32)
    220              bytecode = block.dtype
    221

    222          else:
    223              bytecode = numpy.int32
    224              self.bpp = len(numpy.array(0, bytecode).tostring())
    225              ReadBytes = self.dim1 * self.dim2 * self.bpp
    226              block = numpy.fromstring(infile.read(ReadBytes), bytecode)
    227

    228          logger.debug('OVER_SHORT2: %s', block.dtype)
    229          logger.debug("%s" % (block < 0).sum())
    230          infile.close()
    231          logger.debug("BYTECODE: %s", bytecode)
    232          self.data = block.reshape((self.dim2, self.dim1))
    233          self.bytecode = self.data.dtype.type
    234          self.pilimage = None
    235          return self
    236

    237 -    def _writeheader(self):
    238          """

    239          @return a string containing the header for Oxford images

    240          """
    241          linesep = "\r\n"
    242          for key in DEFAULT_HEADERS:
    243              if key not in self.header_keys:
    244                  self.header_keys.append(key)
    245                  self.header[key] = DEFAULT_HEADERS[key]
    246

    247          if "NX" not in self.header.keys() or "NY" not in self.header.keys():
    248                          self.header['NX'] = self.dim1
    249                          self.header['NY'] = self.dim2
    250          ascii_headers = [self.header['Header Version'],

    251                         "COMPRESSION=%s (%5.1f)" % (self.header["Compression"], self.getCompressionRatio()),

    252                         "NX=%4i NY=%4i OI=%7i OL=%7i " % (self.header["NX"], self.header["NY"], self.header["OI"], self.header["OL"]),

    253                         "NHEADER=%7i NG=%7i NS=%7i NK=%7i NS=%7i NH=%7i" % (self.header['Header Size In Bytes'],

    254                                                                                   self.header['General Section size in Byte'],

    255                                                                                   self.header['Special Section size in Byte'],

    256                                                                                   self.header['KM4 Section size in Byte'],

    257                                                                                   self.header['Statistic Section in Byte'],

    258                                                                                   self.header['History Section in Byte']),

    259                          "NSUPPLEMENT=%7i" % (self.header["NSUPPLEMENT"])]
    260          if "Time" in self.header:
    261              ascii_headers.append("TIME=%s" % self.header["Time"])
    262          else:
    263

    264              ascii_headers.append("TIME=%s" % time.ctime())
    265

    266          header = (linesep.join(ascii_headers)).ljust(256)
    267

    268

    269          NG = Section(self.header['General Section size in Byte'], self.header)
    270          NG.setData('Binning in x', 0, numpy.uint16)
    271          NG.setData('Binning in y', 2, numpy.uint16)
    272          NG.setData('Detector size x', 22, numpy.uint16)
    273          NG.setData('Detector size y', 24, numpy.uint16)
    274          NG.setData('Pixels in x', 26, numpy.uint16)
    275          NG.setData('Pixels in y', 28, numpy.uint16)
    276          NG.setData('No of pixels', 36, numpy.uint32)
    277          header += NG.__repr__()
    278

    279          NS = Section(self.header['Special Section size in Byte'], self.header)
    280          NS.setData('Gain', 56, numpy.float)
    281          NS.setData('Overflows flag', 464, numpy.int16)
    282          NS.setData('Overflow after remeasure flag', 466, numpy.int16)
    283          NS.setData('Overflow threshold', 472, numpy.int32)
    284          NS.setData('Exposure time in sec', 480, numpy.float)
    285          NS.setData('Overflow time in sec', 488, numpy.float)
    286          NS.setData('Monitor counts of raw image 1', 528, numpy.int32)
    287          NS.setData('Monitor counts of raw image 2', 532, numpy.int32)
    288          NS.setData('Monitor counts of overflow raw image 1', 536, numpy.int32)
    289          NS.setData('Monitor counts of overflow raw image 2', 540, numpy.int32)
    290          NS.setData('Unwarping', 544, numpy.int32)
    291          if 'Detector type' in  self.header:
    292              for key, value in  DETECTOR_TYPES.items():
    293                  if value == self.header['Detector type']:
    294                      NS.setData(None, 548, numpy.int32, default=key)
    295          NS.setData('Real pixel size x (mm)', 568, numpy.float)
    296          NS.setData('Real pixel size y (mm)', 576, numpy.float)
    297          header += NS.__repr__()
    298

    299          KM = Section(self.header['KM4 Section size in Byte'], self.header)
    300          KM.setData('Spatial correction file date', 0, "|S26")
    301          KM.setData('Spatial correction file', 26, "|S246")
    302          # Angles are in steps due to stepper motors - conversion factor RAD

    303          # angle[0] = omega, angle[1] = theta, angle[2] = kappa, angle[3] = phi,

    304          if self.header.get('Omega step in deg', None):
    305              KM.setData(None, 368, numpy.float64, deg2rad(self.header["Omega step in deg"]))
    306              if self.header.get('Omega start in deg', None):
    307                  KM.setData(None, 284, numpy.int32, self.header["Omega start in deg"] / self.header["Omega step in deg"])
    308              if self.header.get('Omega end in deg', None):
    309                  KM.setData(None, 324, numpy.int32, self.header["Omega end in deg"] / self.header["Omega step in deg"])
    310              if self.header.get('Omega zero corr. in deg', None):
    311                  KM.setData(None, 512, numpy.int32, self.header['Omega zero corr. in deg'] / self.header["Omega step in deg"])
    312

    313          if self.header.get('Theta step in deg', None):
    314              KM.setData(None, 368 + 8, numpy.float64, deg2rad(self.header["Theta step in deg"]))
    315              if self.header.get('Theta start in deg', None):
    316                  KM.setData(None, 284 + 4, numpy.int32, self.header["Theta start in deg"] / self.header["Theta step in deg"])
    317              if self.header.get('Theta end in deg', None):
    318                  KM.setData(None, 324 + 4, numpy.int32, self.header["Theta end in deg"] / self.header["Theta step in deg"])
    319              if self.header.get('Theta zero corr. in deg', None):
    320                  KM.setData(None, 512 + 4, numpy.int32, self.header['Theta zero corr. in deg'] / self.header["Theta step in deg"])
    321

    322          if self.header.get('Kappa step in deg', None):
    323              KM.setData(None, 368 + 16, numpy.float64, deg2rad(self.header["Kappa step in deg"]))
    324              if self.header.get('Kappa start in deg', None):
    325                  KM.setData(None, 284 + 8, numpy.int32, self.header["Kappa start in deg"] / self.header["Kappa step in deg"])
    326              if self.header.get('Kappa end in deg', None):
    327                  KM.setData(None, 324 + 8, numpy.int32, self.header["Kappa end in deg"] / self.header["Kappa step in deg"])
    328              if self.header.get('Kappa zero corr. in deg', None):
    329                  KM.setData(None, 512 + 8, numpy.int32, self.header['Kappa zero corr. in deg'] / self.header["Kappa step in deg"])
    330

    331          if self.header.get('Phi step in deg', None):
    332              KM.setData(None, 368 + 24, numpy.float64, deg2rad(self.header["Phi step in deg"]))
    333              if self.header.get('Phi start in deg', None):
    334                  KM.setData(None, 284 + 12, numpy.int32, self.header["Phi start in deg"] / self.header["Phi step in deg"])
    335              if self.header.get('Phi end in deg', None):
    336                  KM.setData(None, 324 + 12, numpy.int32, self.header["Phi end in deg"] / self.header["Phi step in deg"])
    337              if self.header.get('Phi zero corr. in deg', None):
    338                  KM.setData(None, 512 + 12, numpy.int32, self.header['Phi zero corr. in deg'] / self.header["Phi step in deg"])
    339

    340          # Beam rotation about e2,e3

    341          KM.setData('Beam rot in deg (e2)', 552, numpy.float64)
    342          KM.setData('Beam rot in deg (e3)', 560, numpy.float64)
    343          # Wavelenghts alpha1, alpha2, beta

    344          KM.setData('Wavelength alpha1', 568, numpy.float64)
    345          KM.setData('Wavelength alpha2', 576, numpy.float64)
    346          KM.setData('Wavelength alpha', 584, numpy.float64)
    347          KM.setData('Wavelength beta', 592, numpy.float64)
    348

    349          # Detector tilts around e1,e2,e3 in deg

    350          KM.setData('Detector tilt e1 in deg', 640, numpy.float64)
    351          KM.setData('Detector tilt e2 in deg', 648, numpy.float64)
    352          KM.setData('Detector tilt e3 in deg', 656, numpy.float64)
    353

    354          # Beam center

    355          KM.setData('Beam center x', 664, numpy.float64)
    356          KM.setData('Beam center y', 672, numpy.float64)
    357          # Angle (alpha) between kappa rotation axis and e3 (ideally 50 deg)

    358          KM.setData('Alpha angle in deg', 672, numpy.float64)
    359          # Angle (beta) between phi rotation axis and e3 (ideally 0 deg)

    360          KM.setData('Beta angle in deg', 672, numpy.float64)
    361

    362          # Detector distance

    363          KM.setData('Distance in mm', 712, numpy.float64)
    364          header += KM.__repr__()
    365

    366          SS = Section(self.header['Statistic Section in Byte'], self.header)
    367          SS.setData('Stat: Min ', 0, numpy.int32)
    368          SS.setData('Stat: Max ', 4, numpy.int32)
    369          SS.setData('Stat: Average ', 24, numpy.float64)
    370          if self.header.get('Stat: Stddev ', None):
    371              SS.setData(None, 32, numpy.float64, self.header['Stat: Stddev '] ** 2)
    372          SS.setData('Stat: Skewness ', 40, numpy.float64)
    373          header += SS.__repr__()
    374

    375          HS = Section(self.header['History Section in Byte'], self.header)
    376          HS.setData('Flood field image', 99, "|S27")
    377          header += HS.__repr__()
    378

    379          return header
    380

    381

    382 -    def write(self, fname):
    383          """Write Oxford diffraction images: this is still beta

    384          @param fname: output filename

    385          """
    386          datablock8, datablock16, datablock32 = compTY1(self.data)
    387          self.header["OI"] = len(datablock16) / 2
    388          self.header["OL"] = len(datablock32) / 4
    389          with self._open(fname, mode="wb") as outfile:
    390              outfile.write(self._writeheader())
    391              outfile.write(datablock8)
    392              outfile.write(datablock16)
    393              outfile.write(datablock32)
    394

    395 -    def getCompressionRatio(self):
    396          "calculate the compression factor obtained vs raw data"
    397          return 100.0 * (self.data.size + 2 * self.header["OI"] + 4 * self.header["OL"]) / (self.data.size * 4)
    398

    399      @staticmethod
    400 -    def checkData(data=None):
    401          if data is None:
    402              return None
    403          else:
    404              return data.astype(int)
    405

    406 -class Section(object):
    407      """

    408      Small helper class for writing binary headers

    409      """
    410 -    def __init__(self, size, dictHeader):
    411          """

    412          @param size: size of the header section in bytes

    413          @param dictHeader: headers of the image

    414          """
    415          self.size = size
    416          self.header = dictHeader
    417          self.lstChr = ["\x00"] * size
    418          self._dictSize = {}
    419 -    def __repr__(self):
    420          return "".join(self.lstChr)
    421

    422 -    def getSize(self, dtype):
    423          if not dtype in self._dictSize:
    424              self._dictSize[dtype] = len(numpy.zeros(1, dtype=dtype).tostring())
    425          return self._dictSize[dtype]
    426

    427 -    def setData(self, key, offset, dtype, default=None):
    428          """

    429          @param offset: int, starting position in the section

    430          @param key: name of the header key

    431          @param dtype: type of the data to insert (defines the size!)

    432          """
    433          if key in self.header:
    434              value = self.header[key]
    435          elif key in DEFAULT_HEADERS:
    436              value = DEFAULT_HEADERS[key]
    437          else:
    438              value = default
    439          if value is None:
    440              value = "\x00" * self.getSize(dtype)
    441          else:
    442              value = numpy.array(value).astype(dtype).tostring()
    443          self.lstChr[offset:offset + self.getSize(dtype)] = value
    444