Esempio n. 1
0
def image_call(xyz_image, function, inaxis='t', outaxis='new'):
    """
    Take an XYZImage, perform some operation on it, over
    a specified axis, and return a new XYZImage.

    For the sake of testing things out, we will assume that
    the operation can only operate on the first
    axis of the array.

    Parameters
    ----------

    xyz_image : XYZImage

    function : callable
        An operation that does something over the first axis,
        such as lambda x: x[::2]

    inaxis : str or int
        Specification of axis of XYZImage

    outaxis : str
        Name of new axis in new XYZImage

    Returns
    -------

    newim : XYZImage with axis inaxis replaced with outaxis

    """

    if inaxis in xyz_image.reference.coord_names + \
            xyz_image.axes.coord_names[:3] + tuple(range(3)):
        raise ValueError('cannot reduce over a spatial axis' +
                         'or we will not be able to output XYZImages')

    image = xyz_image.to_image()
    image = image_rollaxis(image, inaxis)
    inaxis = image.axes.coord_names[0] # now it's a string

    newdata = function(image.get_data())
    newcoordmap = image.coordmap.renamed_domain({inaxis:outaxis})
    newimage = Image(newdata, newcoordmap)

    # we have to roll the axis back

    axis_index = xyz_image.axes.index(inaxis)
    newimage = image_rollaxis(newimage, axis_index, inverse=True)

    return XYZImage(newimage.get_data(), xyz_image.affine, 
                    newimage.axes.coord_names)
Esempio n. 2
0
def setup():
    tmp_img = load_image(funcfile)
    # For now, img is an Image
    # instead of an XYZImage
    A = np.identity(4)
    A[:3,:3] = tmp_img.affine[:3,:3]
    A[:3,-1] = tmp_img.affine[:3,-1]
    xyz_data = tmp_img.get_data()
    xyz_img = XYZImage(xyz_data, A,
                       tmp_img.axes.coord_names[:3] + ('t',))
    # If load_image returns an XYZImage, I'd really be
    # starting from xyz_img, so from here

    # Here, I'm just doing this so I know that
    # img.shape[0] is the number of volumes
    img = image_rollaxis(Image(xyz_img._data, xyz_img.coordmap), 't')
    data_dict['nimages'] = img.shape[0]
    # It might be worth to make
    # data a public attribute of Image/XYZImage
    # and we might rename get_data-> data_as_array = np.asarray(self.data)
    # Then, the above would not access a private attribute
    
    # Below, I am just making a mask
    # because I already have img, I 
    # know I can do this
    # In principle, though, the pca function
    # will just take another XYZImage as a mask
    img_data = img.get_data()
    first_frame = img_data[0]
    mask = XYZImage(np.greater(np.asarray(first_frame), 500).astype(np.float64), A, xyz_img.axes.coord_names[:3])
    data_dict['fmridata'] = xyz_img
    data_dict['mask'] = mask
    data_dict['img'] = img
    print data_dict['mask'].shape, np.sum(np.array(data_dict['mask']))
Esempio n. 3
0
def need_specific_axis_reduce(xyz_image, reduce_op):
    """
    Take an XYZImage, perform some reduce operation on it, over
    the axis 'specific', and returns a new XYZImage.

    For the sake of testing things out, we will assume that
    the operation reduces over the first axis only.

    Parameters
    ----------

    xyz_image : XYZImage

    reduce_op : callable
        An operation that reduces over the first axis,
        such as lambda x: x.sum(0)

    Returns
    -------

    newim : XYZImage, missing axis 

    """

    image = xyz_image.to_image()
    image = image_rollaxis(image, 'specific')

    axis_name = image.axes.coord_names[0]
    output_axes = list(xyz_image.axes.coord_names)
    output_axes.remove(axis_name)

    newdata = reduce_op(image.get_data())
    return XYZImage(newdata, xyz_image.affine, output_axes)
Esempio n. 4
0
def image_modify(xyz_image, modify, axis='y+PA'):
    """
    Take an XYZImage, perform some operation on it, over
    a specified axis, and return a new XYZImage.

    For this operation, we are allowed to iterate over
    spatial axes.

    For the sake of testing things out, we will assume that
    the operation modify can only operate by iterating
    over the first axis of an array.

    Parameters
    ----------

    xyz_image : XYZImage

    modify : callable
        An operation that does modifies an array.
        Something like 

        def f(x):
           x[:] = x.mean()

    axis : str or int
        Specification of axis of XYZImage

    Returns
    -------

    newim : XYZImage with a modified copy of xyz_image._data.

    """

    image = Image(xyz_image.get_data(), xyz_image.coordmap)
    image = image_rollaxis(image, axis)

    data = image.get_data().copy()
    for d in data:
        modify(d)

    import copy
    newimage = Image(data, copy.copy(image.coordmap))

    # Now, we have to put the data back in order
    # with XYZImage

    newimage = synchronized_order(newimage, xyz_image)
    return XYZImage.from_image(newimage)
Esempio n. 5
0
def image_reduce(xyz_image, reduce_op, axis='t'):
    """
    Take an XYZImage, perform some reduce operation on it, over
    a specified axis, and return a new XYZImage.

    For the sake of testing things out, we will assume that
    the operation reduces over the first axis only.

    Parameters
    ----------

    xyz_image : XYZImage

    reduce_op : callable
        An operation that reduces over the first axis,
        such as lambda x: x.sum(0)

    axis : str or int
        Specification of axis of XYZImage

    Returns
    -------

    newim : XYZImage, missing axis 

    """

    if axis in xyz_image.reference.coord_names + \
            xyz_image.axes.coord_names[:3] + tuple(range(3)):
        raise ValueError('cannot reduce over a spatial axis' +
                         'or we will not be able to output XYZImages')

    image = xyz_image.to_image()
    image = image_rollaxis(image, axis)

    axis_name = image.axes.coord_names[0]
    output_axes = list(xyz_image.axes.coord_names)
    output_axes.remove(axis_name)

    newdata = reduce_op(image.get_data())
    return XYZImage(newdata, xyz_image.affine, output_axes)
Esempio n. 6
0
def pca_image(xyz_image, axis='t', mask=None, ncomp=None, standardize=True,
              design_keep=None, design_resid='mean', tol_ratio=0.01):
    """ Compute the PCA of an image over a specified axis. 

    Parameters
    ----------
    data : XYZImage
        The image on which to perform PCA over its first axis.
    axis : str or int
        Axis over which to perform PCA. Cannot be a spatial axis because
        the results have to be XYZImages.  Default is 't'
    mask : XYZImage
        An optional mask, should have shape == image.shape[:3]
        and the same XYZTransform.
    ncomp : {None, int}, optional
       How many component basis projections to return. If ncomp is None
       (the default) then the number of components is given by the
       calculated rank of the data, after applying `design_keep`,
       `design_resid` and `tol_ratio` below.  We always return all the
       basis vectors and percent variance for each component; `ncomp`
       refers only to the number of basis_projections returned.
    standardize : bool, optional
       If True, standardize so each time series (after application of
       `design_keep` and `design_resid`) has the same standard
       deviation, as calculated by the ``np.std`` function.
    design_keep : None or ndarray, optional
       Data is projected onto the column span of design_keep.
       None (default) equivalent to ``np.identity(data.shape[axis])``
    design_resid : str or None or ndarray, optional
       After projecting onto the column span of design_keep, data is
       projected perpendicular to the column span of this matrix.  If
       None, we do no such second projection.  If a string 'mean', then
       the mean of the data is removed, equivalent to passing a column
       vector matrix of 1s.
    tol_ratio : float, optional
       If ``XZ`` is the vector of singular values of the projection
       matrix from `design_keep` and `design_resid`, and S are the
       singular values of ``XZ``, then `tol_ratio` is the value used to
       calculate the effective rank of the projection of the design, as
       in ``rank = ((S / S.max) > tol_ratio).sum()``

    Returns
    -------
    results : dict
        $L$ is the number of non-trivial components found after applying
       `tol_ratio` to the projections of `design_keep` and
       `design_resid`.
    
       `results` has keys:

       * ``basis_vectors``: series over `axis`, shape (data.shape[axis], L) -
          the eigenvectors of the PCA
       * ``pcnt_var``: percent variance explained by component, shape
          (L,)
       * ``basis_projections``: PCA components, with components varying
          over axis `axis`; thus shape given by: ``s = list(data.shape);
          s[axis] = ncomp``
       * ``axis``: axis over which PCA has been performed.
    """
    if axis in xyz_image.reference.coord_names + \
            xyz_image.axes.coord_names[:3] + tuple(range(3)):
        raise ValueError('cannot perform PCA over a spatial axis' +
                         'or we will not be able to output XYZImages')
    xyz_data = xyz_image.get_data()
    image = Image(xyz_data, xyz_image.coordmap)
    image = image_rollaxis(image, axis)
    if mask is not None:
        if mask.xyz_transform != xyz_image.xyz_transform:
            raise ValueError('mask and xyz_image have different coordinate systems')
        if mask.ndim != image.ndim - 1:
            raise ValueError('mask should have one less dimension than xyz_image')
        if mask.axes.coord_names != image.axes.coord_names[1:]:
            raise ValueError('mask should have axes %s'
                             % str(image.axes.coord_names[1:]))
    data = image.get_data()
    if mask is not None:
        mask_data = mask.get_data()
    else:
        mask_data = None
    # do the PCA
    res = pca(data, 0, mask_data, ncomp, standardize,
              design_keep, design_resid, tol_ratio)
    # Clean up images after PCA 
    img_first_axis = image.axes.coord_names[0]
    # Rename the axis.
    #
    # Because we started with XYZImage, all non-spatial
    # coordinates agree in the range and the domain
    # so this will work and the renamed_range
    # call is not even necessary because when we call
    # XYZImage, we only use the axisnames
    output_coordmap = image.coordmap.renamed_domain(
        {img_first_axis:'PCA components'}).renamed_range({img_first_axis:'PCA components'})
    output_img = Image(res['basis_projections'], output_coordmap)
    # We have to roll the axis back
    roll_index = xyz_image.axes.index(img_first_axis)
    output_img = image_rollaxis(output_img, roll_index, inverse=True)
    output_xyz = XYZImage(output_img.get_data(), 
                          xyz_image.affine,
                          output_img.axes.coord_names)
    key = 'basis_vectors over %s' % img_first_axis
    res[key] = res['basis_vectors']
    res['basis_projections'] = output_xyz
    return res
Esempio n. 7
0
def run_model(subj, run):
    """
    Single subject fitting of FIAC model
    """
    #----------------------------------------------------------------------
    # Set initial parameters of the FIAC dataset
    #----------------------------------------------------------------------
    # Number of volumes in the fMRI data
    nvol = 191
    # The TR of the experiment
    TR = 2.5 
    # The time of the first volume
    Tstart = 0.0
    # The array of times corresponding to each 
    # volume in the fMRI data
    volume_times = np.arange(nvol)*TR + Tstart
    # This recarray of times has one column named 't'
    # It is used in the function design.event_design
    # to create the design matrices.
    volume_times_rec = make_recarray(volume_times, 't')
    # Get a path description dictionary that contains all the path data
    # relevant to this subject/run
    path_info = futil.path_info(subj,run)

    #----------------------------------------------------------------------
    # Experimental design
    #----------------------------------------------------------------------

    # Load the experimental description from disk.  We have utilities in futil
    # that reformat the original FIAC-supplied format into something where the
    # factorial structure of the design is more explicit.  This has already
    # been run once, and get_experiment_initial() will simply load the
    # newly-formatted design description files (.csv) into record arrays.
    experiment, initial = futil.get_experiment_initial(path_info)

    # Create design matrices for the "initial" and "experiment" factors,
    # saving the default contrasts. 

    # The function event_design will create
    # design matrices, which in the case of "experiment"
    # will have num_columns =
    # (# levels of speaker) * (# levels of sentence) * len(delay.spectral) =
    #      2 * 2 * 2 = 8
    # For "initial", there will be
    # (# levels of initial) * len([hrf.glover]) = 1 * 1 = 1

    # Here, delay.spectral is a sequence of 2 symbolic HRFs that 
    # are described in 
    # 
    # Liao, C.H., Worsley, K.J., Poline, J-B., Aston, J.A.D., Duncan, G.H.,
    #    Evans, A.C. (2002). \'Estimating the delay of the response in fMRI
    #    data.\' NeuroImage, 16:593-606.

    # The contrasts, cons_exper,
    # is a dictionary with keys: ['constant_0', 'constant_1', 'speaker_0', 
    # 'speaker_1',
    # 'sentence_0', 'sentence_1', 'sentence:speaker_0', 'sentence:speaker_1']
    # representing the four default contrasts: constant, main effects + 
    # interactions,
    # each convolved with 2 HRFs in delay.spectral. Its values
    # are matrices with 8 columns.

    # XXX use the hrf __repr__ for naming contrasts

    X_exper, cons_exper = design.event_design(experiment, volume_times_rec,
                                              hrfs=delay.spectral)

    # The contrasts for 'initial' are ignored 
    # as they are "uninteresting" and are included
    # in the model as confounds.

    X_initial, _ = design.event_design(initial, volume_times_rec,
                                       hrfs=[hrf.glover])

    # In addition to factors, there is typically a "drift" term
    # In this case, the drift is a natural cubic spline with
    # a not at the midpoint (volume_times.mean())

    vt = volume_times # shorthand
    drift = np.array( [vt**i for i in range(4)] +
                      [(vt-vt.mean())**3 * (np.greater(vt, vt.mean()))] )
    for i in range(drift.shape[0]):
        drift[i] /= drift[i].max()

    # We transpose the drift so that its shape is (nvol,5) so that it will have
    # the same number of rows as X_initial and X_exper.
    drift = drift.T

    # There are helper functions to create these drifts: design.fourier_basis,
    # design.natural_spline.  Therefore, the above is equivalent (except for
    # the normalization by max for numerical stability) to
    #
    # >>> drift = design.natural_spline(t, [volume_times.mean()])

    # Stack all the designs, keeping the new contrasts which has the same keys
    # as cons_exper, but its values are arrays with 15 columns, with the
    # non-zero entries matching the columns of X corresponding to X_exper
    X, cons = design.stack_designs((X_exper, cons_exper),
                                   (X_initial, {}),
                                   (drift, {}))

    # Sanity check: delete any non-estimable contrasts
    # XXX - this seems to be broken right now, it's producing bogus warnings.
    ## for k in cons.keys():
    ##     if not isestimable(X, cons[k]):
    ##         del(cons[k])
    ##         warnings.warn("contrast %s not estimable for this run" % k)

    # The default contrasts are all t-statistics.  We may want to output
    # F-statistics for 'speaker', 'sentence', 'speaker:sentence' based on the
    # two coefficients, one for each HRF in delay.spectral

    cons['speaker'] = np.vstack([cons['speaker_0'], cons['speaker_1']])
    cons['sentence'] = np.vstack([cons['sentence_0'], cons['sentence_1']])
    cons['sentence:speaker'] = np.vstack([cons['sentence:speaker_0'],
                                          cons['sentence:speaker_1']])

    #----------------------------------------------------------------------
    # Data loading
    #----------------------------------------------------------------------

    # Load in the fMRI data, saving it as an array
    # It is transposed to have time as the first dimension,
    # i.e. fmri[t] gives the t-th volume.

    fmri_lpi = futil.get_fmri(path_info) # an LPIImage
    fmri_im = Image(fmri_lpi._data, fmri_lpi.coordmap)
    fmri_im = image_rollaxis(fmri_im, 't')

    fmri = fmri_im.get_data() # now, it's an ndarray

    nvol, volshape = fmri.shape[0], fmri.shape[1:]
    nslice, sliceshape = volshape[0], volshape[1:]

    #----------------------------------------------------------------------
    # Model fit
    #----------------------------------------------------------------------

    # The model is a two-stage model, the first stage being an OLS (ordinary
    # least squares) fit, whose residuals are used to estimate an AR(1)
    # parameter for each voxel.

    m = OLSModel(X)
    ar1 = np.zeros(volshape)

    # Fit the model, storing an estimate of an AR(1) parameter at each voxel
    for s in range(nslice):
        d = np.array(fmri[:,s])
        flatd = d.reshape((d.shape[0], -1))
        result = m.fit(flatd)
        ar1[s] = ((result.resid[1:] * result.resid[:-1]).sum(0) /
                  (result.resid**2).sum(0)).reshape(sliceshape)

    # We round ar1 to nearest one-hundredth
    # and group voxels by their rounded ar1 value,
    # fitting an AR(1) model to each batch of voxels.

    # XXX smooth here?
    # ar1 = smooth(ar1, 8.0)

    ar1 *= 100
    ar1 = ar1.astype(np.int) / 100.

    # We split the contrasts into F-tests and t-tests.
    # XXX helper function should do this

    fcons = {}; tcons = {}
    for n, v in cons.items():
        v = np.squeeze(v)
        if v.ndim == 1:
            tcons[n] = v
        else:
            fcons[n] = v

    # Setup a dictionary to hold all the output
    # XXX ideally these would be memmap'ed Image instances

    output = {}
    for n in tcons:
        tempdict = {}
        for v in ['sd', 't', 'effect']:
            tempdict[v] = np.memmap(NamedTemporaryFile(prefix='%s%s.nii' \
                                    % (n,v)), dtype=np.float,
                                    shape=volshape, mode='w+')
        output[n] = tempdict

    for n in fcons:
        output[n] = np.memmap(NamedTemporaryFile(prefix='%s%s.nii' \
                                    % (n,v)), dtype=np.float,
                                    shape=volshape, mode='w+')

    # Loop over the unique values of ar1

    for val in np.unique(ar1):
        armask = np.equal(ar1, val)
        m = ARModel(X, val)
        d = fmri[:,armask]
        results = m.fit(d)

        # Output the results for each contrast

        for n in tcons:
            resT = results.Tcontrast(tcons[n])
            output[n]['sd'][armask] = resT.sd
            output[n]['t'][armask] = resT.t
            output[n]['effect'][armask] = resT.effect

        for n in fcons:
            output[n][armask] = results.Fcontrast(fcons[n]).F

    # Dump output to disk
    odir = futil.output_dir(path_info,tcons,fcons)
    # The coordmap for a single volume in the time series
    vol0_map = fmri_im[0].coormap
    for n in tcons:
        for v in ['t', 'sd', 'effect']:
            im = Image(output[n][v], vol0_map)
            save_image(im, pjoin(odir, n, '%s.nii' % v))

    for n in fcons:
        im = Image(output[n], vol0_map)
        save_image(im, pjoin(odir, n, "F.nii"))