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

Source Code for Module fabio.fabioimage

  1  #!/usr/bin/env python  
  2   
  3   
  4  ## Automatically adapted for numpy.oldnumeric Oct 05, 2007 by alter_code1.py 
  5   
  6  """ 
  7   
  8  Authors: Henning O. Sorensen & Erik Knudsen 
  9           Center for Fundamental Research: Metal Structures in Four Dimensions 
 10           Risoe National Laboratory 
 11           Frederiksborgvej 399 
 12           DK-4000 Roskilde 
 13           email:erik.knudsen@risoe.dk 
 14            
 15           and Jon Wright, ESRF 
 16   
 17  """ 
 18   
 19  import numpy as N, math, os, cStringIO, gzip, bz2 
 20  import Image 
 21  import fabio 
 22   
 23   
 24  # i = Image.open('lena.jpg') 
 25  # a = numpy.asarray(i) # a is readonly 
 26  # i = Image.fromarray(a) 
 27   
28 -class fabioimage:
29 """ 30 A common object for images in fable 31 Contains a numpy array (.data) and dict of meta data (.header) 32 """ 33 34 _need_a_seek_to_read = False 35 _need_a_real_file = False 36
37 - def __init__(self, data=None , header=None):
38 """ 39 Set up initial values 40 """ 41 if type(data) == type("string"): 42 raise Exception("fabioimage.__init__ bad argument - " + \ 43 "data should be numpy array") 44 self.data = data 45 self.pilimage = None 46 if header is None: 47 self.header = {} 48 else: 49 self.header = header 50 self.header_keys = self.header.keys() # holds key ordering 51 if data is not None: 52 self.dim1, self.dim2 = 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.area_sum = None 61 self.slice = None 62 # New for multiframe files 63 self.nframes = 1 64 self.currentframe = 0 65 self.filename = None
66
67 - def getframe(self, num):
68 """ returns the file numbered 'num' in the series as a fabioimage """ 69 if self.nframes == 1: 70 # single image per file 71 return fabio.openimage.openimage( 72 fabio.jump_filename(self.filename, num)) 73 raise Exception("getframe out of range")
74
75 - def previous(self):
76 """ returns the previous file in the series as a fabioimage """ 77 return fabio.openimage.openimage( 78 fabio.previous_filename(self.filename))
79
80 - def next(self):
81 """ returns the next file in the series as a fabioimage """ 82 return fabio.openimage.openimage( 83 fabio.next_filename(self.filename))
84 85
86 - def toPIL16(self, filename=None):
87 """ 88 Convert to Python Imaging Library 16 bit greyscale image 89 90 FIXME - this should be handled by the libraries now 91 """ 92 if filename: 93 self.read(filename) 94 if self.pilimage is not None: 95 return self.pilimage 96 # mode map 97 size = self.data.shape[:2][::-1] 98 typmap = { 99 'float32' : "F" , 100 'int32' : "F;32S" , 101 'uint32' : "F;32" , 102 'int16' : "F;16S" , 103 'uint16' : "F;16" , 104 'int8' : "F;8S" , 105 'uint8' : "F;8" } 106 if typmap.has_key(self.data.dtype.name): 107 mode2 = typmap[ self.data.dtype.name ] 108 mode1 = mode2[0] 109 else: 110 raise Exception("Unknown numpy type " + str(self.data.dtype.type)) 111 # 112 # hack for byteswapping for PIL in MacOS 113 testval = N.array((1, 0), N.uint8).view(N.uint16)[0] 114 if testval == 1: 115 dats = self.data.tostring() 116 elif testval == 256: 117 dats = self.data.byteswap().tostring() 118 else: 119 raise Exception("Endian unknown in fabioimage.toPIL16") 120 121 self.pilimage = Image.frombuffer(mode1, 122 size, 123 dats, 124 "raw", 125 mode2, 126 0, 127 1) 128 129 return self.pilimage
130
131 - def getheader(self):
132 """ returns self.header """ 133 return self.header
134
135 - def getmax(self):
136 """ Find max value in self.data, caching for the future """ 137 if self.maxval is None: 138 self.maxval = N.max(self.data) 139 return self.maxval
140
141 - def getmin(self):
142 """ Find min value in self.data, caching for the future """ 143 if self.minval is None: 144 self.minval = N.min(self.data) 145 return self.minval
146
147 - def make_slice(self, coords):
148 """ 149 Convert a len(4) set of coords into a len(2) 150 tuple (pair) of slice objects 151 the latter are immutable, meaning the roi can be cached 152 """ 153 assert len(coords) == 4 154 if len(coords) == 4: 155 # fabian edfimage preference 156 if coords[0] > coords[2]: 157 coords[0:3:2] = [coords[2], coords[0]] 158 if coords[1] > coords[3]: 159 coords[1:4:2] = [coords[3], coords[1]] 160 #in fabian: normally coordinates are given as (x,y) whereas 161 # a matrix is given as row,col 162 # also the (for whichever reason) the image is flipped upside 163 # down wrt to the matrix hence these tranformations 164 fixme = (self.dim2 - coords[3] - 1, 165 coords[0] , 166 self.dim2 - coords[1] - 1, 167 coords[2]) 168 return (slice(int(fixme[0]), int(fixme[2]) + 1) , 169 slice(int(fixme[1]), int(fixme[3]) + 1))
170 171
172 - def integrate_area(self, coords):
173 """ 174 Sums up a region of interest 175 if len(coords) == 4 -> convert coords to slices 176 if len(coords) == 2 -> use as slices 177 floor -> ? removed as unused in the function. 178 """ 179 if self.data == None: 180 # This should return NAN, not zero ? 181 return 0 182 if len(coords) == 4: 183 sli = self.make_slice(coords) 184 elif len(coords) == 2 and isinstance(coords[0], slice) and \ 185 isinstance(coords[1], slice): 186 sli = coords 187 if sli == self.slice and self.area_sum is not None: 188 return self.area_sum 189 self.slice = sli 190 self.area_sum = N.sum( 191 N.ravel( 192 self.data[ self.slice ].astype(N.float))) 193 return self.area_sum
194
195 - def getmean(self):
196 """ return the mean """ 197 if self.mean is None: 198 self.mean = N.mean(self.data) 199 return float(self.mean)
200
201 - def getstddev(self):
202 """ return the standard deviation """ 203 if self.stddev == None: 204 self.stddev = N.std(self.data) 205 return float(self.stddev)
206
207 - def add(self, other):
208 """ 209 Add another Image - warnign, does not clip to 16 bit images by default 210 """ 211 if not hasattr(other, 'data'): 212 print 'edfimage.add() called with something that ' + \ 213 'does not have a data field' 214 assert self.data.shape == other.data.shape , \ 215 'incompatible images - Do they have the same size?' 216 self.data = self.data + other.data 217 self.resetvals()
218 219
220 - def resetvals(self):
221 """ Reset cache - call on changing data """ 222 self.mean = self.stddev = self.maxval = self.minval = None 223 self.area_sum = None
224
225 - def rebin(self, x_rebin_fact, y_rebin_fact):
226 """ Rebin the data and adjust dims """ 227 if self.data == None: 228 raise Exception('Please read in the file you wish to rebin first') 229 (mantis_x, exp_x) = math.frexp(x_rebin_fact) 230 (mantis_y, exp_y) = math.frexp(y_rebin_fact) 231 # FIXME - this is a floating point comparison, is it always exact? 232 if (mantis_x != 0.5 or mantis_y != 0.5): 233 raise Exception('Rebin factors not power of 2 not supported (yet)') 234 if int(self.dim1 / x_rebin_fact) * x_rebin_fact != self.dim1 or \ 235 int(self.dim2 / x_rebin_fact) * x_rebin_fact != self.dim2 : 236 raise('image size is not divisible by rebin factor - ' + \ 237 'skipping rebin') 238 pass ## self.data.savespace(1) # avoid the upcasting behaviour 239 i = 1 240 while i < x_rebin_fact: 241 # FIXME - why do you divide by 2? Rebinning should increase counts? 242 self.data = ((self.data[:, ::2] + self.data[:, 1::2]) / 2) 243 i = i * 2 244 i = 1 245 while i < y_rebin_fact: 246 self.data = ((self.data[::2, :] + self.data[1::2, :]) / 2) 247 i = i * 2 248 self.resetvals() 249 self.dim1 = self.dim1 / x_rebin_fact 250 self.dim2 = self.dim2 / y_rebin_fact 251 #update header 252 self.update_header()
253
254 - def write(self, fname):
255 """ 256 To be overwritten - write the file 257 """ 258 raise Exception("Class has not implemented readheader method yet")
259
260 - def readheader(self, filename):
261 """ 262 Call the _readheader function... 263 """ 264 # Override the needs asserting that all headers can be read via python modules 265 save_state = self._need_a_real_file , self._need_a_seek_to_read 266 self._need_a_real_file , self._need_a_seek_to_read = False, False 267 fin = self._open(filename) 268 self._readheader(fin) 269 fin.close() 270 self._need_a_real_file , self._need_a_seek_to_read = save_state
271
272 - def _readheader(self, fik_obj):
273 """ 274 Must be overridden in classes 275 """ 276 raise Exception("Class has not implemented _readheader method yet")
277
278 - def update_header(self , **kwds):
279 """ 280 update the header entries 281 by default pass in a dict of key, values. 282 """ 283 self.header.update(kwds)
284
285 - def read(self, filename):
286 """ 287 To be overridden - fill in self.header and self.data 288 """ 289 raise Exception("Class has not implemented read method yet")
290 291
292 - def _open(self, fname, mode="rb"):
293 """ 294 Try to handle compressed files, streams, shared memory etc 295 Return an object which can be used for "read" and "write" 296 ... FIXME - what about seek ? 297 """ 298 self.filename = fname 299 if hasattr(fname, "read") and hasattr(fname, "write"): 300 # It is already something we can use 301 return fname 302 if type(fname) in [type(" "), type(u" ")]: 303 # filename is a string 304 self.header["filename"] = fname 305 if os.path.splitext(fname)[1] == ".gz": 306 return self._compressed_stream(fname, 307 fabio.COMPRESSORS['.gz'], 308 gzip.GzipFile, 309 mode) 310 if os.path.splitext(fname)[1] == '.bz2': 311 return self._compressed_stream(fname, 312 fabio.COMPRESSORS['.bz2'], 313 bz2.BZ2File, 314 mode) 315 # 316 # Here we return the file even though it may be bzipped or gzipped 317 # but named incorrectly... 318 # 319 # FIXME - should we fix that or complain about the daft naming? 320 return open(fname, mode)
321
322 - def _compressed_stream(self, 323 fname, 324 system_uncompress, 325 python_uncompress, 326 mode='rb'):
327 """ 328 Try to transparently handle gzip / bzip without always getting python 329 performance 330 """ 331 # assert that python modules are always OK based on performance benchmark 332 # Try to fix the way we are using them? 333 if self._need_a_real_file and mode[0] == "r": 334 fo = python_uncompress(fname, mode) 335 fobj = os.tmpfile() 336 fobj.write(fo.read()) 337 fo.close() 338 fobj.seek(0) 339 return fobj 340 if self._need_a_seek_to_read and mode[0] == "r": 341 fo = python_uncompress(fname, mode) 342 return cStringIO.StringIO(fo.read()) 343 return python_uncompress(fname, mode)
344 345 346 347
348 -def test():
349 """ 350 check some basic fabioimage functionality 351 """ 352 import time 353 start = time.time() 354 355 dat = N.ones((1024, 1024), N.uint16) 356 dat = (dat * 50000).astype(N.uint16) 357 assert dat.dtype.char == N.ones((1), N.uint16).dtype.char 358 hed = {"Title":"50000 everywhere"} 359 obj = fabioimage(dat, hed) 360 361 assert obj.getmax() == 50000 362 assert obj.getmin() == 50000 363 assert obj.getmean() == 50000 , obj.getmean() 364 assert obj.getstddev() == 0. 365 366 dat2 = N.zeros((1024, 1024), N.uint16, savespace=1) 367 cord = [ 256, 256, 790, 768 ] 368 slic = obj.make_slice(cord) 369 dat2[slic] = dat2[slic] + 100 370 371 obj = fabioimage(dat2, hed) 372 373 # New object, so... 374 assert obj.maxval is None 375 assert obj.minval is None 376 377 assert obj.getmax() == 100, obj.getmax() 378 assert obj.getmin() == 0 , obj.getmin() 379 npix = (slic[0].stop - slic[0].start) * (slic[1].stop - slic[1].start) 380 obj.resetvals() 381 area1 = obj.integrate_area(cord) 382 obj.resetvals() 383 area2 = obj.integrate_area(slic) 384 assert area1 == area2 385 assert obj.integrate_area(cord) == obj.integrate_area(slic) 386 assert obj.integrate_area(cord) == npix * 100, obj.integrate_area(cord) 387 388 389 def clean(): 390 """ clean up the created testfiles""" 391 for name in ["testfile", "testfile.gz", "testfile.bz2"]: 392 try: 393 os.remove(name) 394 except: 395 continue
396 397 398 clean() 399 400 gzip.open("testfile.gz", "wb").write("{ hello }") 401 fout = obj._open("testfile.gz") 402 readin = fout.read() 403 assert readin == "{ hello }", readin + " gzipped file" 404 405 406 bz2.BZ2File("testfilebz", "wb").write("{ hello }") 407 fout = obj._open("testfile.bz2") 408 readin = fout.read() 409 assert readin == "{ hello }", readin + " bzipped file" 410 411 ftest = open("testfile", "wb") 412 ftest.write("{ hello }") 413 assert ftest == obj._open(ftest) 414 ftest.close() 415 fout = obj._open("testfile") 416 readin = fout.read() 417 assert readin == "{ hello }", readin + "plain file" 418 fout.close() 419 ftest.close() 420 clean() 421 422 print "Passed in", time.time() - start, "s" 423 424 if __name__ == '__main__': 425 test() 426