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

Source Code for Module fabio.fabioimage

  1  #!/usr/bin/env python 
  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 
25 26 27 -class fabioimage(object):
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() # holds key ordering 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 # numpy typecode 56 self.bpp = 2 # bytes per pixel 57 # cache for image statistics 58 self.mean = self.maxval = self.stddev = self.minval = None 59 # Cache roi 60 self.roi = None 61 self.area_sum = None 62 self.slice = None 63 # New for multiframe files 64 self.nframes = 1 65 self.currentframe = 0 66 self.filename = None 67 self.filenumber = None
68 69 @staticmethod
70 - def checkHeader(header=None):
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
80 - def checkData(data=None):
81 """ 82 Empty for fabioimage but may be populated by others classes, especially for format accepting only integers 83 """ 84 return data
85
86 - def getclassname(self):
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
96 - def getframe(self, num):
97 """ returns the file numbered 'num' in the series as a fabioimage """ 98 if self.nframes == 1: 99 # single image per file 100 import openimage 101 return openimage.openimage( 102 fabioutils.jump_filename(self.filename, num)) 103 raise Exception("getframe out of range")
104
105 - def previous(self):
106 """ returns the previous file in the series as a fabioimage """ 107 import openimage 108 return openimage.openimage( 109 fabioutils.previous_filename(self.filename))
110
111 - def next(self):
112 """ returns the next file in the series as a fabioimage """ 113 import openimage 114 return openimage.openimage( 115 fabioutils.next_filename(self.filename))
116
117 - def toPIL16(self, filename=None):
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 # mode map 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 # hack for byteswapping for PIL in MacOS 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
164 - def getheader(self):
165 """ returns self.header """ 166 return self.header
167
168 - def getmax(self):
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
174 - def getmin(self):
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
180 - def make_slice(self, coords):
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 # fabian edfimage preference 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 #in fabian: normally coordinates are given as (x,y) whereas 194 # a matrix is given as row,col 195 # also the (for whichever reason) the image is flipped upside 196 # down wrt to the matrix hence these tranformations 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
205 - def integrate_area(self, coords):
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 # This should return NAN, not zero ? 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
231 - def getmean(self):
232 """ return the mean """ 233 if self.mean is None: 234 self.mean = self.data.mean(dtype=numpy.double) 235 return self.mean
236
237 - def getstddev(self):
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
256 - def resetvals(self):
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: #method faster for small binning (4x4) 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: #method faster for large binning (8x8) 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 #update header 303 self.update_header()
304
305 - def write(self, fname):
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
315 - def readheader(self, filename):
316 """ 317 Call the _readheader function... 318 """ 319 # Override the needs asserting that all headers can be read via python modules 320 save_state = self._need_a_real_file , self._need_a_seek_to_read 321 self._need_a_real_file , self._need_a_seek_to_read = False, False 322 fin = self._open(filename) 323 self._readheader(fin) 324 fin.close() 325 self._need_a_real_file , self._need_a_seek_to_read = save_state
326
327 - def _readheader(self, fik_obj):
328 """ 329 Must be overridden in classes 330 """ 331 raise Exception("Class has not implemented _readheader method yet")
332
333 - def update_header(self , **kwds):
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 # return self 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 # It is already something we can use 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 # Here we return the file even though it may be bzipped or gzipped 395 # but named incorrectly... 396 # 397 # FIXME - should we fix that or complain about the daft naming? 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 # assert that python modules are always OK based on performance benchmark 415 # Try to fix the way we are using them? 416 fobj = None 417 if self._need_a_real_file and mode[0] == "r": 418 fo = python_uncompress(fname, mode) 419 # fobj = os.tmpfile() 420 #problem when not administrator under certain flavors of windows 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
434 - def convert(self, dest):
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() #temporary instance (to be overwritten) 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
480 -def test():
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 # New object, so... 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