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