1
2
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:
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 }
53 """
54 Oxford Diffraction Sapphire 3 images reader/writer class
55 """
57
58 infile.seek(0)
59
60
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
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
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
115 block = infile.read(self.header['KM4 Section size in Byte'])
116
117 self.header['Spatial correction file'] = block[26:272].strip("\x00")
118 self.header['Spatial correction file date'] = block[0:26].strip("\x00")
119
120
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
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
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
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
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
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
167 self.header['Alpha angle in deg'] = numpy.fromstring(block[672:680], numpy.float)[0]
168
169 self.header['Beta angle in deg'] = numpy.fromstring(block[672:680], numpy.float)[0]
170
171
172 self.header['Distance in mm'] = numpy.fromstring(block[712:720], numpy.float)[0]
173
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
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
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
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
215 self.raw8 = raw8
216 self.raw16 = raw16
217 self.raw32 = raw32
218
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
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
303
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
341 KM.setData('Beam rot in deg (e2)', 552, numpy.float64)
342 KM.setData('Beam rot in deg (e3)', 560, numpy.float64)
343
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
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
355 KM.setData('Beam center x', 664, numpy.float64)
356 KM.setData('Beam center y', 672, numpy.float64)
357
358 KM.setData('Alpha angle in deg', 672, numpy.float64)
359
360 KM.setData('Beta angle in deg', 672, numpy.float64)
361
362
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
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
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
401 if data is None:
402 return None
403 else:
404 return data.astype(int)
405
407 """
408 Small helper class for writing binary headers
409 """
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 = {}
420 return "".join(self.lstChr)
421
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