1
2
3 """
4
5 Authors: Henning O. Sorensen & Erik Knudsen
6 Center for Fundamental Research: Metal Structures in Four Dimensions
7 Risoe National Laboratory
8 Frederiksborgvej 399
9 DK-4000 Roskilde
10 email:erik.knudsen@risoe.dk
11
12 and Jon Wright, Jerome Kieffer: ESRF
13
14 """
15 from __future__ import with_statement
16 import os, types, logging, sys, tempfile
17 logger = logging.getLogger("fabioimage")
18 import numpy
19 try:
20 import Image
21 except ImportError:
22 logger.warning("PIL is not installed ... trying to do without")
23 Image = None
24 import fabioutils, converters
28 """
29 A common object for images in fable
30 Contains a numpy array (.data) and dict of meta data (.header)
31 """
32
33 _need_a_seek_to_read = False
34 _need_a_real_file = False
35
36 - def __init__(self, data=None , header=None):
37 """
38 Set up initial values
39 """
40 self._classname = None
41 if type(data) in types.StringTypes:
42 raise Exception("fabioimage.__init__ bad argument - " + \
43 "data should be numpy array")
44 self.data = self.checkData(data)
45 self.pilimage = None
46 if header is None:
47 self.header = {}
48 else:
49 self.header = self.checkHeader(header)
50 self.header_keys = self.header.keys()
51 if self.data is not None:
52 self.dim2, self.dim1 = self.data.shape
53 else:
54 self.dim1 = self.dim2 = 0
55 self.bytecode = None
56 self.bpp = 2
57
58 self.mean = self.maxval = self.stddev = self.minval = None
59
60 self.roi = None
61 self.area_sum = None
62 self.slice = None
63
64 self.nframes = 1
65 self.currentframe = 0
66 self.filename = None
67 self.filenumber = None
68
69 @staticmethod
71 """
72 Empty for fabioimage but may be populated by others classes
73 """
74 if header is None:
75 return {}
76 else:
77 return header
78
79 @staticmethod
81 """
82 Empty for fabioimage but may be populated by others classes, especially for format accepting only integers
83 """
84 return data
85
87 """
88 Retrieves the name of the class
89 @return: the name of the class
90 """
91 if self._classname is None:
92 self._classname = str(self.__class__).replace("<class '", "").replace("'>", "").split(".")[-1]
93 return self._classname
94 classname = property(getclassname)
95
104
110
116
118 """
119 Convert to Python Imaging Library 16 bit greyscale image
120
121 FIXME - this should be handled by the libraries now
122 """
123 if not Image:
124 raise RuntimeError("PIL is not installed !!! ")
125 if filename:
126 self.read(filename)
127 if self.pilimage is not None:
128 return self.pilimage
129
130 size = self.data.shape[:2][::-1]
131 typmap = {
132 'float32' : "F" ,
133 'int32' : "F;32S" ,
134 'uint32' : "F;32" ,
135 'int16' : "F;16S" ,
136 'uint16' : "F;16" ,
137 'int8' : "F;8S" ,
138 'uint8' : "F;8" }
139 if typmap.has_key(self.data.dtype.name):
140 mode2 = typmap[ self.data.dtype.name ]
141 mode1 = mode2[0]
142 else:
143 raise Exception("Unknown numpy type " + str(self.data.dtype.type))
144
145
146 testval = numpy.array((1, 0), numpy.uint8).view(numpy.uint16)[0]
147 if testval == 1:
148 dats = self.data.tostring()
149 elif testval == 256:
150 dats = self.data.byteswap().tostring()
151 else:
152 raise Exception("Endian unknown in fabioimage.toPIL16")
153
154 self.pilimage = Image.frombuffer(mode1,
155 size,
156 dats,
157 "raw",
158 mode2,
159 0,
160 1)
161
162 return self.pilimage
163
165 """ returns self.header """
166 return self.header
167
169 """ Find max value in self.data, caching for the future """
170 if self.maxval is None:
171 self.maxval = self.data.max()
172 return self.maxval
173
175 """ Find min value in self.data, caching for the future """
176 if self.minval is None:
177 self.minval = self.data.min()
178 return self.minval
179
181 """
182 Convert a len(4) set of coords into a len(2)
183 tuple (pair) of slice objects
184 the latter are immutable, meaning the roi can be cached
185 """
186 assert len(coords) == 4
187 if len(coords) == 4:
188
189 if coords[0] > coords[2]:
190 coords[0:3:2] = [coords[2], coords[0]]
191 if coords[1] > coords[3]:
192 coords[1:4:2] = [coords[3], coords[1]]
193
194
195
196
197 fixme = (self.dim2 - coords[3] - 1,
198 coords[0] ,
199 self.dim2 - coords[1] - 1,
200 coords[2])
201 return (slice(int(fixme[0]), int(fixme[2]) + 1) ,
202 slice(int(fixme[1]), int(fixme[3]) + 1))
203
204
206 """
207 Sums up a region of interest
208 if len(coords) == 4 -> convert coords to slices
209 if len(coords) == 2 -> use as slices
210 floor -> ? removed as unused in the function.
211 """
212 if self.data == None:
213
214 return 0
215 if len(coords) == 4:
216 sli = self.make_slice(coords)
217 elif len(coords) == 2 and isinstance(coords[0], slice) and \
218 isinstance(coords[1], slice):
219 sli = coords
220
221 if sli == self.slice and self.area_sum is not None:
222 pass
223 elif sli == self.slice and self.roi is not None:
224 self.area_sum = self.roi.sum(dtype=numpy.float)
225 else:
226 self.slice = sli
227 self.roi = self.data[ self.slice ]
228 self.area_sum = self.roi.sum(dtype=numpy.float)
229 return self.area_sum
230
232 """ return the mean """
233 if self.mean is None:
234 self.mean = self.data.mean(dtype=numpy.double)
235 return self.mean
236
238 """ return the standard deviation """
239 if self.stddev == None:
240 self.stddev = self.data.std(dtype=numpy.double)
241 return self.stddev
242
243 - def add(self, other):
244 """
245 Add another Image - warning, does not clip to 16 bit images by default
246 """
247 if not hasattr(other, 'data'):
248 logger.warning('edfimage.add() called with something that ' + \
249 'does not have a data field')
250 assert self.data.shape == other.data.shape , \
251 'incompatible images - Do they have the same size?'
252 self.data = self.data + other.data
253 self.resetvals()
254
255
257 """ Reset cache - call on changing data """
258 self.mean = self.stddev = self.maxval = self.minval = None
259 self.roi = self.slice = self.area_sum = None
260
261 - def rebin(self, x_rebin_fact, y_rebin_fact, keep_I=True):
262 """
263 Rebin the data and adjust dims
264 @param x_rebin_fact: x binning factor
265 @param y_rebin_fact: y binning factor
266 @param keep_I: shall the signal increase ?
267 @type x_rebin_fact: int
268 @type y_rebin_fact: int
269 @type keep_I: boolean
270
271
272 """
273 if self.data == None:
274 raise Exception('Please read in the file you wish to rebin first')
275
276 if (self.dim1 % x_rebin_fact != 0) or (self.dim2 % y_rebin_fact != 0):
277 raise RuntimeError('image size is not divisible by rebin factor - ' + \
278 'skipping rebin')
279 else:
280 dataIn = self.data.astype("float64")
281 shapeIn = self.data.shape
282 shapeOut = (shapeIn[0] / y_rebin_fact, shapeIn[1] / x_rebin_fact)
283 binsize = y_rebin_fact * x_rebin_fact
284 if binsize < 50:
285 out = numpy.zeros(shapeOut, dtype="float64")
286 for j in range(x_rebin_fact):
287 for i in range(y_rebin_fact):
288 out += dataIn[i::y_rebin_fact, j::x_rebin_fact]
289 else:
290 temp = self.data.astype("float64")
291 temp.shape = (shapeOut[0], y_rebin_fact, shapeOut[1], x_rebin_fact)
292 out = temp.sum(axis=3).sum(axis=1)
293 self.resetvals()
294 if keep_I:
295 self.data = (out / (y_rebin_fact * x_rebin_fact)).astype(self.data.dtype)
296 else:
297 self.data = out.astype(self.data.dtype)
298
299 self.dim1 = self.dim1 / x_rebin_fact
300 self.dim2 = self.dim2 / y_rebin_fact
301
302
303 self.update_header()
304
306 """
307 To be overwritten - write the file
308 """
309 raise Exception("Class has not implemented readheader method yet")
310
311 - def save(self, fname):
312 'wrapper for write'
313 self.write(fname)
314
326
328 """
329 Must be overridden in classes
330 """
331 raise Exception("Class has not implemented _readheader method yet")
332
334 """
335 update the header entries
336 by default pass in a dict of key, values.
337 """
338 self.header.update(kwds)
339
340 - def read(self, filename, frame=None):
341 """
342 To be overridden - fill in self.header and self.data
343 """
344 raise Exception("Class has not implemented read method yet")
345
346
347 - def load(self, *arg, **kwarg):
348 "Wrapper for read"
349 return self.read(*arg, **kwarg)
350
351 - def readROI(self, filename, frame=None, coords=None):
352 """
353 Method reading Region of Interest.
354 This implementation is the trivial one, just doing read and crop
355 """
356 self.read(filename, frame)
357 if len(coords) == 4:
358 self.slice = self.make_slice(coords)
359 elif len(coords) == 2 and isinstance(coords[0], slice) and \
360 isinstance(coords[1], slice):
361 self.slice = coords
362 else:
363 logger.warning('readROI: Unable to understand Region Of Interest: got %s', coords)
364 self.roi = self.data[ self.slice ]
365 return self.roi
366
367
368 - def _open(self, fname, mode="rb"):
369 """
370 Try to handle compressed files, streams, shared memory etc
371 Return an object which can be used for "read" and "write"
372 ... FIXME - what about seek ?
373 """
374 fileObject = None
375 self.filename = fname
376 self.filenumber = fabioutils.extract_filenumber(fname)
377
378 if hasattr(fname, "read") and hasattr(fname, "write"):
379
380 return fname
381 if isinstance(fname, (str, unicode)):
382 self.header["filename"] = fname
383 if os.path.splitext(fname)[1] == ".gz":
384 fileObject = self._compressed_stream(fname,
385 fabioutils.COMPRESSORS['.gz'],
386 fabioutils.GzipFile,
387 mode)
388 elif os.path.splitext(fname)[1] == '.bz2':
389 fileObject = self._compressed_stream(fname,
390 fabioutils.COMPRESSORS['.bz2'],
391 fabioutils.BZ2File,
392 mode)
393
394
395
396
397
398 else:
399 fileObject = fabioutils.File(fname, mode)
400 if "name" not in dir(fileObject):
401 fileObject.name = fname
402
403 return fileObject
404
405 - def _compressed_stream(self,
406 fname,
407 system_uncompress,
408 python_uncompress,
409 mode='rb'):
410 """
411 Try to transparently handle gzip / bzip without always getting python
412 performance
413 """
414
415
416 fobj = None
417 if self._need_a_real_file and mode[0] == "r":
418 fo = python_uncompress(fname, mode)
419
420
421 tmpfd, tmpfn = tempfile.mkstemp()
422 os.close(tmpfd)
423 fobj = fabioutils.File(tmpfn, "w+b")
424 fobj.write(fo.read())
425 fo.close()
426 fobj.seek(0)
427 elif self._need_a_seek_to_read and mode[0] == "r":
428 fo = python_uncompress(fname, mode)
429 fobj = fabioutils.StringIO(fo.read(), fname, mode)
430 else:
431 fobj = python_uncompress(fname, mode)
432 return fobj
433
435 """
436 Convert a fabioimage object into another fabioimage object (with possible conversions)
437 @param dest: destination type "EDF", "edfimage" or the class itself
438 """
439 if type(dest) in types.StringTypes:
440 dest = dest.lower()
441 modules = []
442 for val in fabioutils.FILETYPES.values():
443 modules += [i + "image" for i in val if i not in modules]
444 klass = None
445 module = None
446 klass_name = None
447 for klass_name in modules:
448 if klass_name.startswith(dest):
449 try:
450 module = sys.modules["fabio." + klass_name]
451 except KeyError:
452 try:
453 module = __import__(klass_name)
454 except:
455 logger.error("Failed to import %s", klass_name)
456 else:
457 logger.debug("imported %simage", klass_name)
458 if module is not None:
459 break
460 if module is not None:
461 if hasattr(module, klass_name):
462 klass = getattr(module, klass_name)
463 else:
464 logger.error("Module %s has no image class" % module)
465 elif isinstance(dest, self.__class__):
466 klass = dest.__class__
467 elif ("__new__" in dir(dest)) and isinstance(dest(), fabioimage):
468 klass = dest
469 else:
470 logger.warning("Unrecognized destination format: %s " % dest)
471 return self
472 if klass is None:
473 logger.warning("Unrecognized destination format: %s " % dest)
474 return self
475 other = klass()
476 other = klass(data=converters.convert_data(self.classname, other.classname, self.data),
477 header=converters.convert_header(self.classname, other.classname, self.header))
478 return other
479
481 """
482 check some basic fabioimage functionality
483 """
484 import time
485 start = time.time()
486
487 dat = numpy.ones((1024, 1024), numpy.uint16)
488 dat = (dat * 50000).astype(numpy.uint16)
489 assert dat.dtype.char == numpy.ones((1), numpy.uint16).dtype.char
490 hed = {"Title":"50000 everywhere"}
491 obj = fabioimage(dat, hed)
492
493 assert obj.getmax() == 50000
494 assert obj.getmin() == 50000
495 assert obj.getmean() == 50000 , obj.getmean()
496 assert obj.getstddev() == 0.
497
498 dat2 = numpy.zeros((1024, 1024), numpy.uint16, savespace=1)
499 cord = [ 256, 256, 790, 768 ]
500 slic = obj.make_slice(cord)
501 dat2[slic] = dat2[slic] + 100
502
503 obj = fabioimage(dat2, hed)
504
505
506 assert obj.maxval is None
507 assert obj.minval is None
508
509 assert obj.getmax() == 100, obj.getmax()
510 assert obj.getmin() == 0 , obj.getmin()
511 npix = (slic[0].stop - slic[0].start) * (slic[1].stop - slic[1].start)
512 obj.resetvals()
513 area1 = obj.integrate_area(cord)
514 obj.resetvals()
515 area2 = obj.integrate_area(slic)
516 assert area1 == area2
517 assert obj.integrate_area(cord) == obj.integrate_area(slic)
518 assert obj.integrate_area(cord) == npix * 100, obj.integrate_area(cord)
519
520
521 def clean():
522 """ clean up the created testfiles"""
523 for name in ["testfile", "testfile.gz", "testfile.bz2"]:
524 try:
525 os.remove(name)
526 except:
527 continue
528
529
530 clean()
531 import gzip, bz2
532 gzip.open("testfile.gz", "wb").write("{ hello }")
533 fout = obj._open("testfile.gz")
534 readin = fout.read()
535 assert readin == "{ hello }", readin + " gzipped file"
536
537
538 bz2.BZ2File("testfilebz", "wb").write("{ hello }")
539 fout = obj._open("testfile.bz2")
540 readin = fout.read()
541 assert readin == "{ hello }", readin + " bzipped file"
542
543 ftest = open("testfile", "wb")
544 ftest.write("{ hello }")
545 assert ftest == obj._open(ftest)
546 ftest.close()
547 fout = obj._open("testfile")
548 readin = fout.read()
549 assert readin == "{ hello }", readin + "plain file"
550 fout.close()
551 ftest.close()
552 clean()
553
554 print "Passed in", time.time() - start, "s"
555
556 if __name__ == '__main__':
557 test()
558