1
2
3
4
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
25
26
27
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()
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
56 self.bpp = 2
57
58 self.mean = self.maxval = self.stddev = self.minval = None
59
60 self.area_sum = None
61 self.slice = None
62
63 self.nframes = 1
64 self.currentframe = 0
65 self.filename = None
66
68 """ returns the file numbered 'num' in the series as a fabioimage """
69 if self.nframes == 1:
70
71 return fabio.openimage.openimage(
72 fabio.jump_filename(self.filename, num))
73 raise Exception("getframe out of range")
74
79
84
85
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
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
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
132 """ returns self.header """
133 return self.header
134
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
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
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
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
161
162
163
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
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
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
196 """ return the mean """
197 if self.mean is None:
198 self.mean = N.mean(self.data)
199 return float(self.mean)
200
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
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
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
239 i = 1
240 while i < x_rebin_fact:
241
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
252 self.update_header()
253
255 """
256 To be overwritten - write the file
257 """
258 raise Exception("Class has not implemented readheader method yet")
259
271
273 """
274 Must be overridden in classes
275 """
276 raise Exception("Class has not implemented _readheader method yet")
277
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
301 return fname
302 if type(fname) in [type(" "), type(u" ")]:
303
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
317
318
319
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
332
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
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
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