Package mvpa :: Package datasets :: Module nifti
[hide private]
[frames] | no frames]

Source Code for Module mvpa.datasets.nifti

  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 PyMVPA package for the 
  6  #   copyright and license terms. 
  7  # 
  8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## 
  9  """Dataset that gets its samples from a NIfTI file""" 
 10   
 11  __docformat__ = 'restructuredtext' 
 12   
 13  from mvpa.base import externals 
 14  externals.exists('nifti', raiseException=True) 
 15   
 16  import numpy as N 
 17  from mvpa.misc.copy import deepcopy 
 18   
 19  # little trick to be able to import 'nifti' package (which has same name) 
 20  oldname = __name__ 
 21  # crazy name with close to zero possibility to cause whatever 
 22  __name__ = 'iaugf9zrkjsbdv89' 
 23  from nifti import NiftiImage 
 24  # restore old settings 
 25  __name__ = oldname 
 26   
 27  from mvpa.datasets.base import Dataset 
 28  from mvpa.datasets.mapped import MappedDataset 
 29  from mvpa.datasets.event import EventDataset 
 30  from mvpa.mappers.base import CombinedMapper 
 31  from mvpa.mappers.metric import DescreteMetric, cartesianDistance 
 32  from mvpa.mappers.array import DenseArrayMapper 
 33  from mvpa.base import warning 
 34   
 35   
36 -def getNiftiFromAnySource(src):
37 """Load/access NIfTI data from files or instances. 38 39 :Parameter: 40 src: str | NiftiImage 41 Filename of a NIfTI image or a `NiftiImage` instance. 42 43 :Returns: 44 NiftiImage | None 45 If the source is not supported None is returned. 46 """ 47 nifti = None 48 49 # figure out what type 50 if isinstance(src, str): 51 # open the nifti file 52 try: 53 nifti = NiftiImage(src) 54 except RuntimeError, e: 55 warning("ERROR: NiftiDatasets: Cannot open NIfTI file %s" \ 56 % src) 57 raise e 58 elif isinstance(src, NiftiImage): 59 # nothing special 60 nifti = src 61 62 return nifti
63 64
65 -def getNiftiData(nim):
66 """Convenience function to extract the data array from a NiftiImage 67 68 This function will make use of advanced features of PyNIfTI to prevent 69 unnecessary copying if a sufficent version is available. 70 """ 71 if externals.exists('nifti >= 0.20081017.1'): 72 return nim.data 73 else: 74 return nim.asarray()
75 76
77 -class NiftiDataset(MappedDataset):
78 """Dataset loading its samples from a NIfTI image or file. 79 80 Samples can be loaded from a NiftiImage instance or directly from a NIfTI 81 file. This class stores all relevant information from the NIfTI file header 82 and provides information about the metrics and neighborhood information of 83 all voxels. 84 85 Most importantly it allows to map data back into the original data space 86 and format via :meth:`~mvpa.datasets.nifti.NiftiDataset.map2Nifti`. 87 88 This class allows for convenient pre-selection of features by providing a 89 mask to the constructor. Only non-zero elements from this mask will be 90 considered as features. 91 92 NIfTI files are accessed via PyNIfTI. See 93 http://niftilib.sourceforge.net/pynifti/ for more information about 94 pynifti. 95 """ 96 # XXX: Every dataset should really have an example of howto instantiate 97 # it (necessary parameters).
98 - def __init__(self, samples=None, mask=None, dsattr=None, **kwargs):
99 """ 100 :Parameters: 101 samples: str | NiftiImage 102 Filename of a NIfTI image or a `NiftiImage` instance. 103 mask: str | NiftiImage 104 Filename of a NIfTI image or a `NiftiImage` instance. 105 """ 106 # if in copy constructor mode 107 if not dsattr is None and dsattr.has_key('mapper'): 108 MappedDataset.__init__(self, 109 samples=samples, 110 dsattr=dsattr, 111 **kwargs) 112 return 113 114 # 115 # the following code only deals with contructing fresh datasets from 116 # scratch 117 # 118 119 # load the samples 120 niftisamples = getNiftiFromAnySource(samples) 121 samples = niftisamples.data 122 123 # do not put the whole NiftiImage in the dict as this will most 124 # likely be deepcopy'ed at some point and ensuring data integrity 125 # of the complex Python-C-Swig hybrid might be a tricky task. 126 # Only storing the header dict should achieve the same and is more 127 # memory efficient and even simpler 128 dsattr = {'niftihdr': niftisamples.header} 129 130 131 # figure out what the mask is, but onyl handle known cases, the rest 132 # goes directly into the mapper which maybe knows more 133 niftimask = getNiftiFromAnySource(mask) 134 if not niftimask is None: 135 mask = getNiftiData(niftimask) 136 137 # build an appropriate mapper that knows about the metrics of the NIfTI 138 # data 139 # NiftiDataset uses a DescreteMetric with cartesian 140 # distance and element size from the NIfTI header 141 142 # 'voxdim' is (x,y,z) while 'samples' are (t,z,y,x) 143 elementsize = [i for i in reversed(niftisamples.voxdim)] 144 mapper = DenseArrayMapper(mask=mask, shape=samples.shape[1:], 145 metric=DescreteMetric(elementsize=elementsize, 146 distance_function=cartesianDistance)) 147 148 MappedDataset.__init__(self, 149 samples=samples, 150 mapper=mapper, 151 dsattr=dsattr, 152 **kwargs)
153 154
155 - def map2Nifti(self, data=None):
156 """Maps a data vector into the dataspace and wraps it with a 157 NiftiImage. The header data of this object is used to initialize 158 the new NiftiImage. 159 160 :Parameters: 161 data : ndarray or Dataset 162 The data to be wrapped into NiftiImage. If None (default), it 163 would wrap samples of the current dataset. If it is a Dataset 164 instance -- takes its samples for mapping 165 """ 166 if data is None: 167 data = self.samples 168 elif isinstance(data, Dataset): 169 # ease users life 170 data = data.samples 171 dsarray = self.mapper.reverse(data) 172 return NiftiImage(dsarray, self.niftihdr)
173 174 175 niftihdr = property(fget=lambda self: self._dsattr['niftihdr'], 176 doc='Access to the NIfTI header dictionary.')
177 178 179
180 -class ERNiftiDataset(EventDataset):
181 """Dataset with event-defined samples from a NIfTI timeseries image. 182 183 This is a convenience dataset to facilitate the analysis of event-related 184 fMRI datasets. Boxcar-shaped samples are automatically extracted from the 185 full timeseries using :class:`~mvpa.misc.support.Event` definition lists. 186 For each event all volumes covering that particular event in time 187 (including partial coverage) are used to form the corresponding sample. 188 189 The class supports the conversion of events defined in 'realtime' into the 190 descrete temporal space defined by the NIfTI image. Moreover, potentially 191 varying offsets between true event onset and timepoint of the first selected 192 volume can be stored as an additional feature in the dataset. 193 194 Additionally, the dataset supports masking. This is done similar to the 195 masking capabilities of :class:`~mvpa.datasets.nifti.NiftiDataset`. However, 196 the mask can either be of the same shape as a single NIfTI volume, or 197 can be of the same shape as the generated boxcar samples, i.e. 198 a samples consisting of three volumes with 24 slices and 64x64 inplane 199 resolution needs a mask with shape (3, 24, 64, 64). In the former case the 200 mask volume is automatically expanded to be identical in a volumes of the 201 boxcar. 202 """
203 - def __init__(self, samples=None, events=None, mask=None, evconv=False, 204 storeoffset=False, tr=None, **kwargs):
205 """ 206 :Paramaters: 207 evconv: bool 208 Convert event definitions using `onset` and `duration` in some 209 temporal unit into #sample notation. 210 storeoffset: Bool 211 Whether to store temproal offset information when converting 212 Events into descrete time. Only considered when evconv == True. 213 tr: float 214 Temporal distance of two adjacent NIfTI volumes. This can be used 215 to override the corresponding value in the NIfTI header. 216 """ 217 # check if we are in copy constructor mode 218 if events is None: 219 EventDataset.__init__(self, samples=samples, events=events, 220 mask=mask, **kwargs) 221 return 222 223 nifti = getNiftiFromAnySource(samples) 224 # no copying 225 samples = nifti.data 226 227 # do not put the whole NiftiImage in the dict as this will most 228 # likely be deepcopy'ed at some point and ensuring data integrity 229 # of the complex Python-C-Swig hybrid might be a tricky task. 230 # Only storing the header dict should achieve the same and is more 231 # memory efficient and even simpler 232 dsattr = {'niftihdr': nifti.header} 233 234 # NiftiDataset uses a DescreteMetric with cartesian 235 # distance and element size from the NIfTI header 236 # 'voxdim' is (x,y,z) while 'samples' are (t,z,y,x) 237 elementsize = [i for i in reversed(nifti.voxdim)] 238 # XXX metric might be inappropriate if boxcar has length 1 239 # might move metric setup after baseclass init and check what has 240 # really happened 241 metric = DescreteMetric(elementsize=elementsize, 242 distance_function=cartesianDistance) 243 244 # convert EVs if necessary -- not altering original 245 if evconv: 246 # determine TR, take from NIfTI header by default 247 dt = nifti.rtime 248 # override if necessary 249 if not tr is None: 250 dt = tr 251 if dt == 0: 252 raise ValueError, "'dt' cannot be zero when converting Events" 253 254 events = [ev.asDescreteTime(dt, storeoffset) for ev in events] 255 else: 256 # do not touch the original 257 events = deepcopy(events) 258 259 # forcefully convert onset and duration into integers, as expected 260 # by the baseclass 261 for ev in events: 262 oldonset = ev['onset'] 263 oldduration = ev['duration'] 264 ev['onset'] = int(ev['onset']) 265 ev['duration'] = int(ev['duration']) 266 if not oldonset == ev['onset'] \ 267 or not oldduration == ev['duration']: 268 warning("Loosing information during automatic integer " 269 "conversion of EVs. Consider an explicit conversion" 270 " by setting `evconv` in ERNiftiDataset().") 271 272 # pull mask array from NIfTI (if present) 273 if not mask is None: 274 mask_nim = getNiftiFromAnySource(mask) 275 if not mask_nim is None: 276 mask = getNiftiData(mask_nim) 277 else: 278 raise ValueError, "Cannot load mask from '%s'" % mask 279 280 # finally init baseclass 281 EventDataset.__init__(self, samples=samples, events=events, 282 mask=mask, dametric=metric, dsattr=dsattr, 283 **kwargs)
284 285
286 - def map2Nifti(self, data=None):
287 """Maps a data vector into the dataspace and wraps it with a 288 NiftiImage. The header data of this object is used to initialize 289 the new NiftiImage. 290 291 .. note:: 292 Only the features corresponding to voxels are mapped back -- not 293 any additional features passed via the Event definitions. 294 295 :Parameters: 296 data : ndarray or Dataset 297 The data to be wrapped into NiftiImage. If None (default), it 298 would wrap samples of the current dataset. If it is a Dataset 299 instance -- takes its samples for mapping 300 """ 301 if data is None: 302 data = self.samples 303 elif isinstance(data, Dataset): 304 # ease users life 305 data = data.samples 306 307 mr = self.mapper.reverse(data) 308 309 # trying to determine which part should go into NiftiImage 310 if isinstance(self.mapper, CombinedMapper): 311 # we have additional feature in the dataset -- ignore them 312 mr = mr[0] 313 else: 314 pass 315 316 return NiftiImage(mr, self.niftihdr)
317 318 319 niftihdr = property(fget=lambda self: self._dsattr['niftihdr'], 320 doc='Access to the NIfTI header dictionary.')
321