Package nifti :: Module niftiimage
[hide private]
[frames] | no frames]

Source Code for Module nifti.niftiimage

  1  #emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- 
  2  #ex: set sts=4 ts=4 sw=4 et: 
  3  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
  4  # 
  5  #   See COPYING file distributed along with the PyNIfTI package for the 
  6  #   copyright and license terms. 
  7  # 
  8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
  9  """Python class representation of a NIfTI image""" 
 10   
 11  __docformat__ = 'restructuredtext' 
 12   
 13   
 14  # the swig wrapper if the NIfTI C library 
 15  import nifticlib 
 16  from niftiformat import NiftiFormat 
 17  from utils import splitFilename, nifti2numpy_dtype_map 
 18  import numpy as N 
 19   
 20   
21 -class NiftiImage(NiftiFormat):
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 # setup all nifti header related stuff 57 NiftiFormat.__init__(self, source, header) 58 59 # where the data will go to 60 self._data = None 61 62 # load data 63 if type(source) == N.ndarray: 64 # assign data from source array 65 self._data = source[:] 66 elif type(source) in (str, unicode): 67 # only load image data from file if requested 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
76 - def __del__(self):
77 self.unload() 78 79 # it is required to call base class destructors! 80 NiftiFormat.__del__(self)
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 # If image data is not yet loaded, do it now. 111 # It is important to do it already here, because nifti_image_load 112 # depends on the correct filename set in the nifti_image struct 113 # and this will be modified in this function! 114 self.load() 115 116 # set a default description if there is none 117 if not self.description: 118 self.description = 'Created with PyNIfTI' 119 120 # update header information 121 self.updateCalMinMax() 122 123 # saving for the first time? 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 # if still no data is present data source has been an array 133 # -> allocate memory in nifti struct and assign data to it 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 # now save it 142 nifticlib.nifti_image_write_hdr_img(self.raw_nimg, 1, 'wb') 143 # yoh comment: unfortunately return value of nifti_image_write_hdr_img 144 # can't be used to track the successful completion of save 145 # raise IOError, 'An error occured while attempting to save the image 146 # file.' 147 148 # take data pointer away from nifticlibs so we can let Python manage 149 # the memory 150 nifticlib.detachDataFromImage(self.raw_nimg)
151 152
153 - def __haveImageData(self):
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
162 - def load(self):
163 """Load the image data into memory, if it is not already accessible. 164 165 It is save to call this method several times successively. 166 """ 167 # do nothing if there already is data 168 # which included memory mapped arrays not just data in memory 169 if self.__haveImageData(): 170 return 171 172 if nifticlib.nifti_image_load( self.raw_nimg ) < 0: 173 raise RuntimeError, "Unable to load image data." 174 175 self._data = nifticlib.wrapImageDataWithArray(self.raw_nimg) 176 177 # take data pointer away from nifticlibs so we can let Python manage 178 # the memory 179 nifticlib.detachDataFromImage(self.raw_nimg)
180 181
182 - def unload(self):
183 """Unload image data and free allocated memory. 184 185 This methods does nothing in case of memory mapped files. 186 """ 187 # simply assign none. The data array will free itself when the 188 # reference count goes to zero. 189 self._data = None
190 191
192 - def getDataArray(self):
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
200 - def asarray(self, copy = True):
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 # make sure data is accessible 209 self.load() 210 211 if copy: 212 return self._data.copy() 213 else: 214 return self._data
215 216
217 - def getScaledData(self):
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
231 - def updateCalMinMax(self):
232 """Update the image data maximum and minimum value in the nifti header. 233 """ 234 self.raw_nimg.cal_max = float(self.data.max()) 235 self.raw_nimg.cal_min = float(self.data.min())
236 237
238 - def getBoundingBox(self):
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
257 - def setFilename(self, filename, filetype = 'NIFTI'):
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 # If image data is not yet loaded, do it now. 305 # It is important to do it already here, because nifti_image_load 306 # depends on the correct filename set in the nifti_image struct 307 # and this will be modified in this function! 308 self.load() 309 310 # if no filename is given simply reset it to nothing 311 if not filename: 312 self.raw_nimg.fname = '' 313 self.raw_nimg.iname = '' 314 return 315 316 # separate basename and extension 317 base, ext = splitFilename(filename) 318 319 # if no extension default to nifti single files 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 # Determine the filetype and set header and image filename 340 # appropriately. 341 342 # nifti single files are easy 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 # uncompressed nifti file pairs 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 # compressed file pairs 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
367 - def updateHeader(self, hdrdict):
368 """Deprecated method only here for backward compatibility. 369 370 Please refer to NiftiFormat.updateFromDict() 371 """ 372 NiftiFormat.updateFromDict(self, hdrdict)
373 374 375 # class properties 376 # read only 377 data = property(fget=getDataArray) 378 bbox = property(fget=getBoundingBox)
379 380 381
382 -class MemMappedNiftiImage(NiftiImage):
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
393 - def __init__(self, source):
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 # not working on compressed files 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 # determine byte-order 417 if nifticlib.nifti_short_order() == 1: 418 byteorder_flag = '<' 419 else: 420 byteorder_flag = '>' 421 422 # create memmap array 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
432 - def __del__(self):
433 """Do all necessary cleanups by calling __close(). 434 """ 435 self._data.flush() 436 437 # it is required to call base class destructors! 438 NiftiFormat.__del__(self)
439 440
441 - def save(self):
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
454 - def load(self):
455 """Does nothing for memory mapped images. 456 """ 457 return
458 459
460 - def unload(self):
461 """Does nothing for memory mapped images. 462 """ 463 return
464 465
466 - def setFilename(self, filename, filetype = 'NIFTI'):
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
475 -def cropImage( nimg, bbox ):
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 # build crop command 486 cmd = 'nimg.data.squeeze()[' 487 cmd += ','.join( [ ':'.join( [ str(i) for i in dim ] ) for dim in bbox ] ) 488 cmd += ']' 489 490 # crop the image data array 491 cropped = eval(cmd).copy() 492 493 # return the cropped image with preserved header data 494 return NiftiImage(cropped, nimg.header)
495