1
2
3
4
5
6
7
8
9 """Python class representation of a NIfTI image"""
10
11 __docformat__ = 'restructuredtext'
12
13
14
15 import nifticlib
16 from niftiformat import NiftiFormat
17 from utils import splitFilename, nifti2numpy_dtype_map
18 import numpy as N
19
20
22 """Wrapper class for convenient access to NIfTI data.
23
24 The class can either load an image from file or convert a NumPy ndarray
25 into a NIfTI file structure. Either way is automatically determined
26 by the type of the 'source' argument. If `source` is a string, it is
27 assumed to be a filename an ndarray is treated as such.
28
29 One can optionally specify whether the image data should be loaded into
30 memory when opening NIfTI data from files (`load`). When converting a NumPy
31 array one can optionally specify a dictionary with NIfTI header data as
32 available via the `header` attribute.
33 """
34
35 - def __init__(self, source, header={}, load=False):
36 """
37 This method decides whether to load a nifti image from file or create
38 one from ndarray data, depending on the datatype of `source`.
39
40 :Parameters:
41 source: str | ndarray
42 If source is a string, it is assumed to be a filename and an
43 attempt will be made to open the corresponding NIfTI file.
44 In case of an ndarray the array data will be used for the to be
45 created nifti image and a matching nifti header is generated.
46 If an object of a different type is supplied as 'source' a
47 ValueError exception will be thrown.
48 header: dict
49 Additional header data might be supplied. However,
50 dimensionality and datatype are determined from the ndarray and
51 not taken from a header dictionary.
52 load: Boolean
53 If set to True the image data will be loaded into memory. This
54 is only useful if loading a NIfTI image from file.
55 """
56
57 NiftiFormat.__init__(self, source, header)
58
59
60 self._data = None
61
62
63 if type(source) == N.ndarray:
64
65 self._data = source[:]
66 elif type(source) in (str, unicode):
67
68 if load:
69 self.load()
70 else:
71 raise ValueError, \
72 "Unsupported source type. Only NumPy arrays and filename " \
73 + "string are supported."
74
75
81
82
83
84 - def save(self, filename=None, filetype = 'NIFTI'):
85 """Save the image.
86
87 If the image was created using array data (not loaded from a file) one
88 has to specify a filename.
89
90 Warning: There will be no exception if writing fails for any reason,
91 as the underlying function nifti_write_hdr_img() from libniftiio does
92 not provide any feedback. Suggestions for improvements are appreciated.
93
94 If not yet done already, the image data will be loaded into memory
95 before saving the file.
96
97 :Parameters:
98 filename: str | None
99 Calling save() with `filename` equal None on a NiftiImage
100 loaded from a file, it will overwrite the original file.
101
102 Usually setting the filename also determines the filetype
103 (NIfTI/ANALYZE). Please see the documentation of the
104 `setFilename()` method for some more details.
105 filetype: str
106 Override filetype. Please see the documentation of the
107 `setFilename()` method for some more details.
108 """
109
110
111
112
113
114 self.load()
115
116
117 if not self.description:
118 self.description = 'Created with PyNIfTI'
119
120
121 self.updateCalMinMax()
122
123
124 if not self.filename or filename:
125 if not filename:
126 raise ValueError, \
127 "When saving an image for the first time a filename " \
128 + "has to be specified."
129
130 self.setFilename(filename, filetype)
131
132
133
134 if not self.raw_nimg.data:
135 if not nifticlib.allocateImageMemory(self.raw_nimg):
136 raise RuntimeError, "Could not allocate memory for image data."
137
138 a = nifticlib.wrapImageDataWithArray(self.raw_nimg)
139 a[:] = self._data[:]
140
141
142 nifticlib.nifti_image_write_hdr_img(self.raw_nimg, 1, 'wb')
143
144
145
146
147
148
149
150 nifticlib.detachDataFromImage(self.raw_nimg)
151
152
154 """Returns if the image data is accessible -- either loaded into
155 memory or memory mapped.
156
157 See: `load()`, `unload()`
158 """
159 return (not self._data == None)
160
161
180
181
183 """Unload image data and free allocated memory.
184
185 This methods does nothing in case of memory mapped files.
186 """
187
188
189 self._data = None
190
191
193 """Return the NIfTI image data wrapped into a NumPy array.
194
195 The `data` property is an alternative way to access this function.
196 """
197 return self.asarray(False)
198
199
201 """Convert the image data into a ndarray.
202
203 :Parameters:
204 copy: Boolean
205 If set to False the array only wraps the image data, while True
206 will return a copy of the data array.
207 """
208
209 self.load()
210
211 if copy:
212 return self._data.copy()
213 else:
214 return self._data
215
216
218 """Returns a scaled copy of the data array.
219
220 Scaling is done by multiplying with the slope and adding the intercept
221 that is stored in the NIfTI header.
222
223 :Returns:
224 ndarray
225 """
226 data = self.asarray(copy = True)
227
228 return data * self.slope + self.intercept
229
230
236
237
239 """Get the bounding box of the image.
240
241 This functions returns a tuple of (min, max) tuples. It contains as
242 many tuples as image dimensions. The order of dimensions is identical
243 to that in the data array.
244
245 The `bbox` property is an alternative way to access this function.
246 """
247 nz = self.data.squeeze().nonzero()
248
249 bbox = []
250
251 for dim in nz:
252 bbox.append( ( dim.min(), dim.max() ) )
253
254 return tuple(bbox)
255
256
258 """Set the filename for the NIfTI image.
259
260 Setting the filename also determines the filetype. If the filename
261 ends with '.nii' the type will be set to NIfTI single file. A '.hdr'
262 extension can be used for NIfTI file pairs. If the desired filetype
263 is ANALYZE the extension should be '.img'. However, one can use the
264 '.hdr' extension and force the filetype to ANALYZE by setting the
265 filetype argument to ANALYZE. Setting filetype if the filename
266 extension is '.nii' has no effect, the file will always be in NIFTI
267 format.
268
269 If the filename carries an additional '.gz' the resulting file(s) will
270 be compressed.
271
272 Uncompressed NIfTI single files are the default filetype that will be
273 used if the filename has no valid extension. The '.nii' extension is
274 appended automatically. The 'filetype' argument can be used to force a
275 certain filetype when no extension can be used to determine it.
276 'filetype' can be one of the nifticlibs filtetypes or any of 'NIFTI',
277 'NIFTI_GZ', 'NIFTI_PAIR', 'NIFTI_PAIR_GZ', 'ANALYZE', 'ANALYZE_GZ'.
278
279 Setting the filename will cause the image data to be loaded into memory
280 if not yet done already. This has to be done, because without the
281 filename of the original image file there would be no access to the
282 image data anymore. As a side-effect a simple operation like setting a
283 filename may take a significant amount of time (e.g. for a large 4d
284 dataset).
285
286 By passing an empty string or none as filename one can reset the
287 filename and detach the NiftiImage object from any file on disk.
288
289 Examples:
290
291 Filename Output of save()
292 ----------------------------------
293 exmpl.nii exmpl.nii (NIfTI)
294 exmpl.hdr exmpl.hdr, exmpl.img (NIfTI)
295 exmpl.img exmpl.hdr, exmpl.img (ANALYZE)
296 exmpl exmpl.nii (NIfTI)
297 exmpl.hdr.gz exmpl.hdr.gz, exmpl.img.gz (NIfTI)
298
299 ! exmpl.gz exmpl.gz.nii (uncompressed NIfTI)
300
301 Setting the filename is also possible by assigning to the 'filename'
302 property.
303 """
304
305
306
307
308 self.load()
309
310
311 if not filename:
312 self.raw_nimg.fname = ''
313 self.raw_nimg.iname = ''
314 return
315
316
317 base, ext = splitFilename(filename)
318
319
320 if ext == '':
321 if filetype == 'NIFTI' \
322 or filetype == nifticlib.NIFTI_FTYPE_NIFTI1_1:
323 ext = 'nii'
324 elif filetype == 'NIFTI_PAIR' \
325 or filetype == nifticlib.NIFTI_FTYPE_NIFTI1_2:
326 ext = 'hdr'
327 elif filetype == 'ANALYZE' \
328 or filetype == nifticlib.NIFTI_FTYPE_ANALYZE:
329 ext = 'img'
330 elif filetype == 'NIFTI_GZ':
331 ext = 'nii.gz'
332 elif filetype == 'NIFTI_PAIR_GZ':
333 ext = 'hdr.gz'
334 elif filetype == 'ANALYZE_GZ':
335 ext = 'img.gz'
336 else:
337 raise RuntimeError, "Unhandled filetype."
338
339
340
341
342
343 if ext == 'nii.gz' or ext == 'nii':
344 self.raw_nimg.fname = base + '.' + ext
345 self.raw_nimg.iname = base + '.' + ext
346 self.raw_nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_1
347
348 elif ext in [ 'hdr', 'img' ]:
349 self.raw_nimg.fname = base + '.hdr'
350 self.raw_nimg.iname = base + '.img'
351 if ext == 'hdr' and not filetype.startswith('ANALYZE'):
352 self.raw_nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_2
353 else:
354 self.raw_nimg.nifti_type = nifticlib.NIFTI_FTYPE_ANALYZE
355
356 elif ext in [ 'hdr.gz', 'img.gz' ]:
357 self.raw_nimg.fname = base + '.hdr.gz'
358 self.raw_nimg.iname = base + '.img.gz'
359 if ext == 'hdr.gz' and not filetype.startswith('ANALYZE'):
360 self.raw_nimg.nifti_type = nifticlib.NIFTI_FTYPE_NIFTI1_2
361 else:
362 self.raw_nimg.nifti_type = nifticlib.NIFTI_FTYPE_ANALYZE
363 else:
364 raise RuntimeError, "Unhandled filetype."
365
366
368 """Deprecated method only here for backward compatibility.
369
370 Please refer to NiftiFormat.updateFromDict()
371 """
372 NiftiFormat.updateFromDict(self, hdrdict)
373
374
375
376
377 data = property(fget=getDataArray)
378 bbox = property(fget=getBoundingBox)
379
380
381
383 """Memory mapped access to uncompressed NIfTI files.
384
385 This access mode might be the prefered one whenever whenever only a small
386 part of the image data has to be accessed or the memory is not sufficient
387 to load the whole dataset.
388 Please note, that memory-mapping is not required when exclusively header
389 information shall be accessed. By default the `NiftiImage` class does not
390 load any image data into memory.
391 """
392
394 """Create a NiftiImage object.
395
396 This method decides whether to load a nifti image from file or create
397 one from ndarray data, depending on the datatype of `source`.
398
399 :Parameters:
400 source: str | ndarray
401 If source is a string, it is assumed to be a filename and an
402 attempt will be made to open the corresponding NIfTI file.
403 In case of an ndarray the array data will be used for the to be
404 created nifti image and a matching nifti header is generated.
405 If an object of a different type is supplied as 'source' a
406 ValueError exception will be thrown.
407 """
408 NiftiImage.__init__(self, source)
409
410
411 if nifticlib.nifti_is_gzfile(self.raw_nimg.iname):
412 raise RuntimeError, \
413 "Memory mapped access is only supported for " \
414 "uncompressed files."
415
416
417 if nifticlib.nifti_short_order() == 1:
418 byteorder_flag = '<'
419 else:
420 byteorder_flag = '>'
421
422
423 self._data = N.memmap(
424 self.raw_nimg.iname,
425 shape=self.extent[::-1],
426 offset=self.raw_nimg.iname_offset,
427 dtype=byteorder_flag + \
428 nifti2numpy_dtype_map[self.raw_nimg.datatype],
429 mode='r+')
430
431
433 """Do all necessary cleanups by calling __close().
434 """
435 self._data.flush()
436
437
438 NiftiFormat.__del__(self)
439
440
442 """Save the image.
443
444 This methods does nothing except for syncing the file on the disk.
445
446 Please note that the NIfTI header might not be completely up-to-date.
447 For example, the min and max values might be outdated, but this
448 class does not automatically update them, because it would require to
449 load and search through the whole array.
450 """
451 self._data.flush()
452
453
455 """Does nothing for memory mapped images.
456 """
457 return
458
459
461 """Does nothing for memory mapped images.
462 """
463 return
464
465
467 """Does not work for memory mapped images and therefore raises an
468 exception.
469 """
470 raise RuntimeError, \
471 "Filename modifications are not supported for memory mapped " \
472 "images."
473
474
476 """ Crop an image.
477
478 'bbox' has to be a sequency of (min,max) tuples (one for each image
479 dimension).
480
481 The function returns the cropped image. The data is not shared with the
482 original image, but is copied.
483 """
484
485
486 cmd = 'nimg.data.squeeze()['
487 cmd += ','.join( [ ':'.join( [ str(i) for i in dim ] ) for dim in bbox ] )
488 cmd += ']'
489
490
491 cropped = eval(cmd).copy()
492
493
494 return NiftiImage(cropped, nimg.header)
495