Ejemplo n.º 1
0
Archivo: core.py Proyecto: taggei/AMICO
    def load_kernels(self):
        """Load rotated kernels and project to the specific gradient scheme of this subject.
        Dispatch to the proper function, depending on the model.
        """
        if self.model is None:
            ERROR('Model not set; call "set_model()" method first')
        if self.scheme is None:
            ERROR('Scheme not loaded; call "load_data()" first')

        tic = time.time()
        LOG('\n-> Resampling LUT for subject "%s":' %
            self.get_config('subject'))

        # auxiliary data structures
        idx_OUT, Ylm_OUT = amico.lut.aux_structures_resample(
            self.scheme, self.get_config('lmax'))

        # hash table
        self.htable = amico.lut.load_precomputed_hash_table(
            self.get_config('ndirs'))

        # Dispatch to the right handler for each model
        self.KERNELS = self.model.resample(self.get_config('ATOMS_path'),
                                           idx_OUT, Ylm_OUT,
                                           self.get_config('doMergeB0'),
                                           self.get_config('ndirs'))

        LOG('   [ %.1f seconds ]' % (time.time() - tic))
Ejemplo n.º 2
0
def precompute_rotation_matrices(lmax, ndirs):
    """Precompute the rotation matrices to rotate the high-resolution kernels (500 directions/shell).

    Parameters
    ----------
    lmax : int
        Maximum SH order to use for the rotation phase (default : 12)
    ndirs : int
        Number of directions on the half of the sphere representing the possible orientations of the response functions (default : 32761)
    """
    if not isdir(dipy_home):
        makedirs(dipy_home)
    filename = pjoin(
        dipy_home,
        'AMICO_aux_matrices_lmax=%d_ndirs=%d.pickle' % (lmax, ndirs))
    if isfile(filename):
        return

    directions = load_directions(ndirs)

    LOG('\n-> Precomputing rotation matrices for l_max=%d:' % lmax)
    AUX = {}
    AUX['lmax'] = lmax
    AUX['ndirs'] = ndirs

    # matrix to fit the SH coefficients
    _, theta, phi = cart2sphere(grad[:, 0], grad[:, 1], grad[:, 2])
    tmp, _, _ = real_sym_sh_basis(lmax, theta, phi)
    AUX['fit'] = np.dot(np.linalg.pinv(np.dot(tmp.T, tmp)), tmp.T)

    # matrices to rotate the functions in SH space
    AUX['Ylm_rot'] = np.zeros(ndirs, dtype=np.object)
    for i in range(ndirs):
        _, theta, phi = cart2sphere(directions[i][0], directions[i][1],
                                    directions[i][2])
        tmp, _, _ = real_sym_sh_basis(lmax, theta, phi)
        AUX['Ylm_rot'][i] = tmp.reshape(-1)

    # auxiliary data to perform rotations
    AUX['const'] = np.zeros(AUX['fit'].shape[0], dtype=np.float64)
    AUX['idx_m0'] = np.zeros(AUX['fit'].shape[0], dtype=np.int32)
    i = 0
    for l in range(0, AUX['lmax'] + 1, 2):
        const = np.sqrt(4.0 * np.pi / (2.0 * l + 1.0))
        idx_m0 = (l * l + l + 2.0) / 2.0 - 1
        for m in range(-l, l + 1):
            AUX['const'][i] = const
            AUX['idx_m0'][i] = idx_m0
            i += 1

    with open(filename, 'wb+') as fid:
        pickle.dump(AUX, fid, protocol=2)

    LOG('   [ DONE ]')
Ejemplo n.º 3
0
Archivo: core.py Proyecto: taggei/AMICO
    def generate_kernels(self, regenerate=False, lmax=12, ndirs=32761):
        """Generate the high-resolution response functions for each compartment.
        Dispatch to the proper function, depending on the model.

        Parameters
        ----------
        regenerate : boolean
            Regenerate kernels if they already exist (default : False)
        lmax : int
            Maximum SH order to use for the rotation procedure (default : 12)
        ndirs : int
            Number of directions on the half of the sphere representing the possible orientations of the response functions (default : 32761)
         """
        if self.scheme is None:
            ERROR('Scheme not loaded; call "load_data()" first')
        if self.model is None:
            ERROR('Model not set; call "set_model()" method first')
        if not is_valid(ndirs):
            ERROR(
                'Unsupported value for ndirs.\nNote: Supported values for ndirs are [500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 32761 (default)]'
            )

        # store some values for later use
        self.set_config('lmax', lmax)
        self.set_config('ndirs', ndirs)
        self.model.scheme = self.scheme
        LOG('\n-> Creating LUT for "%s" model:' % self.model.name)

        # check if kernels were already generated
        tmp = glob.glob(pjoin(self.get_config('ATOMS_path'), 'A_*.npy'))
        if len(tmp) > 0 and not regenerate:
            LOG('   [ LUT already computed. USe option "regenerate=True" to force regeneration ]'
                )
            return

        # create folder or delete existing files (if any)
        if not exists(self.get_config('ATOMS_path')):
            makedirs(self.get_config('ATOMS_path'))
        else:
            for f in glob.glob(pjoin(self.get_config('ATOMS_path'), '*')):
                remove(f)

        # auxiliary data structures
        aux = amico.lut.load_precomputed_rotation_matrices(lmax, ndirs)
        idx_IN, idx_OUT = amico.lut.aux_structures_generate(self.scheme, lmax)

        # Dispatch to the right handler for each model
        tic = time.time()
        self.model.generate(self.get_config('ATOMS_path'), aux, idx_IN,
                            idx_OUT, ndirs)
        LOG('   [ %.1f seconds ]' % (time.time() - tic))
Ejemplo n.º 4
0
def setup(lmax=12, ndirs=None):
    """General setup/initialization of the AMICO framework.

    Parameters
    ----------
    lmax : int
        Maximum SH order to use for the rotation phase (default : 12).
        NB: change only if you know what you are doing.
     ndirs : int
        DEPRECATED. Now, all directions are precomputed.
    """
    if ndirs is not None:
        WARNING('"ndirs" parameter is deprecated')
    LOG('\n-> Precomputing rotation matrices:')
    for n in tqdm(valid_dirs(),
                  ncols=70,
                  bar_format='   |{bar}| {percentage:4.1f}%',
                  disable=(get_verbose() < 3)):
        amico.lut.precompute_rotation_matrices(lmax, n)
    LOG('   [ DONE ]')
Ejemplo n.º 5
0
Archivo: core.py Proyecto: taggei/AMICO
    def load_data(self,
                  dwi_filename='DWI.nii',
                  scheme_filename='DWI.scheme',
                  mask_filename=None,
                  b0_thr=0):
        """Load the diffusion signal and its corresponding acquisition scheme.

        Parameters
        ----------
        dwi_filename : string
            The file name of the DWI data, relative to the subject folder (default : 'DWI.nii')
        scheme_filename : string
            The file name of the corresponding acquisition scheme (default : 'DWI.scheme')
        mask_filename : string
            The file name of the (optional) binary mask (default : None)
        b0_thr : float
            The threshold below which a b-value is considered a b0 (default : 0)
        """

        # Loading data, acquisition scheme and mask (optional)
        LOG('\n-> Loading data:')
        tic = time.time()

        print('\t* DWI signal')
        self.set_config('dwi_filename', dwi_filename)
        self.niiDWI = nibabel.load(
            pjoin(self.get_config('DATA_path'), dwi_filename))
        self.niiDWI_img = self.niiDWI.get_data().astype(np.float32)
        hdr = self.niiDWI.header if nibabel.__version__ >= '2.0.0' else self.niiDWI.get_header(
        )
        self.set_config('dim', self.niiDWI_img.shape[:3])
        self.set_config('pixdim', tuple(hdr.get_zooms()[:3]))
        print('\t\t- dim    = %d x %d x %d x %d' % self.niiDWI_img.shape)
        print('\t\t- pixdim = %.3f x %.3f x %.3f' % self.get_config('pixdim'))
        # Scale signal intensities (if necessary)
        if (np.isfinite(hdr['scl_slope']) and np.isfinite(hdr['scl_inter'])
                and hdr['scl_slope'] != 0
                and (hdr['scl_slope'] != 1 or hdr['scl_inter'] != 0)):
            print('\t\t- rescaling data ', end='')
            self.niiDWI_img = self.niiDWI_img * hdr['scl_slope'] + hdr[
                'scl_inter']
            print('[OK]')

        print('\t* Acquisition scheme')
        self.set_config('scheme_filename', scheme_filename)
        self.set_config('b0_thr', b0_thr)
        self.scheme = amico.scheme.Scheme(
            pjoin(self.get_config('DATA_path'), scheme_filename), b0_thr)
        print('\t\t- %d samples, %d shells' %
              (self.scheme.nS, len(self.scheme.shells)))
        print('\t\t- %d @ b=0' % (self.scheme.b0_count), end=' ')
        for i in range(len(self.scheme.shells)):
            print(', %d @ b=%.1f' % (len(
                self.scheme.shells[i]['idx']), self.scheme.shells[i]['b']),
                  end=' ')
        print()

        if self.scheme.nS != self.niiDWI_img.shape[3]:
            ERROR('Scheme does not match with DWI data')

        print('\t* Binary mask')
        if mask_filename is not None:
            self.niiMASK = nibabel.load(
                pjoin(self.get_config('DATA_path'), mask_filename))
            self.niiMASK_img = self.niiMASK.get_data().astype(np.uint8)
            niiMASK_hdr = self.niiMASK.header if nibabel.__version__ >= '2.0.0' else self.niiMASK.get_header(
            )
            print('\t\t- dim    = %d x %d x %d' % self.niiMASK_img.shape[:3])
            print('\t\t- pixdim = %.3f x %.3f x %.3f' %
                  niiMASK_hdr.get_zooms()[:3])
            if self.niiMASK.ndim != 3:
                ERROR('The provided MASK if 4D, but a 3D dataset is expected')
            if self.get_config('dim') != self.niiMASK_img.shape[:3]:
                ERROR('MASK geometry does not match with DWI data')
        else:
            self.niiMASK = None
            self.niiMASK_img = np.ones(self.get_config('dim'))
            print('\t\t- not specified')
        print('\t\t- voxels = %d' % np.count_nonzero(self.niiMASK_img))

        LOG('   [ %.1f seconds ]' % (time.time() - tic))

        # Preprocessing
        LOG('\n-> Preprocessing:')
        tic = time.time()

        if self.get_config('doDebiasSignal'):
            print('\t* Debiasing signal... ', end='')
            sys.stdout.flush()
            if self.get_config('DWI-SNR') == None:
                ERROR(
                    "Set noise variance for debiasing (eg. ae.set_config('RicianNoiseSigma', sigma))"
                )
            self.niiDWI_img = debiasRician(self.niiDWI_img,
                                           self.get_config('DWI-SNR'),
                                           self.niiMASK_img, self.scheme)
            print(' [OK]')

        if self.get_config('doNormalizeSignal'):
            print('\t* Normalizing to b0... ', end='')
            sys.stdout.flush()
            if self.scheme.b0_count > 0:
                self.mean_b0s = np.mean(self.niiDWI_img[:, :, :,
                                                        self.scheme.b0_idx],
                                        axis=3)
            else:
                ERROR('No b0 volume to normalize signal with')
            norm_factor = self.mean_b0s.copy()
            idx = self.mean_b0s <= 0
            norm_factor[idx] = 1
            norm_factor = 1 / norm_factor
            norm_factor[idx] = 0
            for i in range(self.scheme.nS):
                self.niiDWI_img[:, :, :, i] *= norm_factor
            print('[ min=%.2f,  mean=%.2f, max=%.2f ]' %
                  (self.niiDWI_img.min(), self.niiDWI_img.mean(),
                   self.niiDWI_img.max()))

        if self.get_config('doMergeB0'):
            print('\t* Merging multiple b0 volume(s)')
            mean = np.expand_dims(np.mean(self.niiDWI_img[:, :, :,
                                                          self.scheme.b0_idx],
                                          axis=3),
                                  axis=3)
            self.niiDWI_img = np.concatenate(
                (mean, self.niiDWI_img[:, :, :, self.scheme.dwi_idx]), axis=3)
        else:
            print('\t* Keeping all b0 volume(s)')

        LOG('   [ %.1f seconds ]' % (time.time() - tic))
Ejemplo n.º 6
0
Archivo: core.py Proyecto: taggei/AMICO
    def save_results(self, path_suffix=None):
        """Save the output (directions, maps etc).

        Parameters
        ----------
        path_suffix : string
            Text to be appended to the output path (default : None)
        """
        if self.RESULTS is None:
            ERROR('Model not fitted to the data; call "fit()" first')
        if self.get_config('OUTPUT_path') is None:
            RESULTS_path = pjoin('AMICO', self.model.id)
            if path_suffix:
                RESULTS_path = RESULTS_path + '_' + path_suffix
            self.RESULTS['RESULTS_path'] = RESULTS_path
            LOG('\n-> Saving output to "%s/*":' % RESULTS_path)

            # delete previous output
            RESULTS_path = pjoin(self.get_config('DATA_path'), RESULTS_path)
        else:
            RESULTS_path = self.get_config('OUTPUT_path')
            if path_suffix:
                RESULTS_path = RESULTS_path + '_' + path_suffix
            self.RESULTS['RESULTS_path'] = RESULTS_path
            LOG('\n-> Saving output to "%s/*":' % RESULTS_path)

        if not exists(RESULTS_path):
            makedirs(RESULTS_path)
        else:
            for f in glob.glob(pjoin(RESULTS_path, '*')):
                remove(f)

        # configuration
        print('\t- configuration', end=' ')
        with open(pjoin(RESULTS_path, 'config.pickle'), 'wb+') as fid:
            pickle.dump(self.CONFIG, fid, protocol=2)
        print(' [OK]')

        # estimated orientations
        print('\t- FIT_dir.nii.gz', end=' ')
        niiMAP_img = self.RESULTS['DIRs']
        affine = self.niiDWI.affine if nibabel.__version__ >= '2.0.0' else self.niiDWI.get_affine(
        )
        niiMAP = nibabel.Nifti1Image(niiMAP_img, affine)
        niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
        )
        niiMAP_hdr['cal_min'] = -1
        niiMAP_hdr['cal_max'] = 1
        niiMAP_hdr['scl_slope'] = 1
        niiMAP_hdr['scl_inter'] = 0
        nibabel.save(niiMAP, pjoin(RESULTS_path, 'FIT_dir.nii.gz'))
        print(' [OK]')

        # fitting error
        if self.get_config('doComputeNRMSE'):
            print('\t- FIT_nrmse.nii.gz', end=' ')
            niiMAP_img = self.RESULTS['NRMSE']
            niiMAP = nibabel.Nifti1Image(niiMAP_img, affine)
            niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
            )
            niiMAP_hdr['cal_min'] = 0
            niiMAP_hdr['cal_max'] = 1
            niiMAP_hdr['scl_slope'] = 1
            niiMAP_hdr['scl_inter'] = 0
            nibabel.save(niiMAP, pjoin(RESULTS_path, 'FIT_nrmse.nii.gz'))
            print(' [OK]')

        if self.get_config('doSaveCorrectedDWI'):
            if self.model.name == 'Free-Water':
                print('\t- dwi_fw_corrected.nii.gz', end=' ')
                niiMAP_img = self.RESULTS['DWI_corrected']
                niiMAP = nibabel.Nifti1Image(niiMAP_img, affine)
                niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
                )
                niiMAP_hdr['cal_min'] = 0
                niiMAP_hdr['cal_max'] = 1
                nibabel.save(niiMAP,
                             pjoin(RESULTS_path, 'dwi_fw_corrected.nii.gz'))
                print(' [OK]')
            else:
                WARNING(
                    '"doSaveCorrectedDWI" option not supported for "%s" model'
                    % self.model.name)

        # voxelwise maps
        for i in range(len(self.model.maps_name)):
            print('\t- FIT_%s.nii.gz' % self.model.maps_name[i], end=' ')
            niiMAP_img = self.RESULTS['MAPs'][:, :, :, i]
            niiMAP = nibabel.Nifti1Image(niiMAP_img, affine)
            niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
            )
            niiMAP_hdr['descrip'] = self.model.maps_descr[i]
            niiMAP_hdr['cal_min'] = niiMAP_img.min()
            niiMAP_hdr['cal_max'] = niiMAP_img.max()
            niiMAP_hdr['scl_slope'] = 1
            niiMAP_hdr['scl_inter'] = 0
            nibabel.save(
                niiMAP,
                pjoin(RESULTS_path, 'FIT_%s.nii.gz' % self.model.maps_name[i]))
            print(' [OK]')

        LOG('   [ DONE ]')
Ejemplo n.º 7
0
Archivo: core.py Proyecto: taggei/AMICO
    def fit(self):
        """Fit the model to the data iterating over all voxels (in the mask) one after the other.
        Call the appropriate fit() method of the actual model used.
        """
        if self.niiDWI is None:
            ERROR('Data not loaded; call "load_data()" first')
        if self.model is None:
            ERROR('Model not set; call "set_model()" first')
        if self.KERNELS is None:
            ERROR(
                'Response functions not generated; call "generate_kernels()" and "load_kernels()" first'
            )
        if self.KERNELS['model'] != self.model.id:
            ERROR('Response functions were not created with the same model')

        self.set_config('fit_time', None)
        totVoxels = np.count_nonzero(self.niiMASK_img)
        LOG('\n-> Fitting "%s" model to %d voxels:' %
            (self.model.name, totVoxels))

        # setup fitting directions
        peaks_filename = self.get_config('peaks_filename')
        if peaks_filename is None:
            DIRs = np.zeros([
                self.get_config('dim')[0],
                self.get_config('dim')[1],
                self.get_config('dim')[2], 3
            ],
                            dtype=np.float32)
            nDIR = 1
            if self.get_config('doMergeB0'):
                gtab = gradient_table(
                    np.hstack((0, self.scheme.b[self.scheme.dwi_idx])),
                    np.vstack((np.zeros(
                        (1, 3)), self.scheme.raw[self.scheme.dwi_idx, :3])))
            else:
                gtab = gradient_table(self.scheme.b, self.scheme.raw[:, :3])
            DTI = dti.TensorModel(gtab)
        else:
            niiPEAKS = nibabel.load(
                pjoin(self.get_config('DATA_path'), peaks_filename))
            DIRs = niiPEAKS.get_data().astype(np.float32)
            nDIR = np.floor(DIRs.shape[3] / 3)
            print('\t* peaks dim = %d x %d x %d x %d' % DIRs.shape[:4])
            if DIRs.shape[:3] != self.niiMASK_img.shape[:3]:
                ERROR('PEAKS geometry does not match with DWI data')

        # setup other output files
        MAPs = np.zeros([
            self.get_config('dim')[0],
            self.get_config('dim')[1],
            self.get_config('dim')[2],
            len(self.model.maps_name)
        ],
                        dtype=np.float32)

        if self.get_config('doComputeNRMSE'):
            NRMSE = np.zeros([
                self.get_config('dim')[0],
                self.get_config('dim')[1],
                self.get_config('dim')[2]
            ],
                             dtype=np.float32)

        if self.get_config('doSaveCorrectedDWI'):
            DWI_corrected = np.zeros(self.niiDWI.shape, dtype=np.float32)

        # fit the model to the data
        # =========================
        t = time.time()
        progress = ProgressBar(n=totVoxels, prefix="   ", erase=False)
        for iz in range(self.niiMASK_img.shape[2]):
            for iy in range(self.niiMASK_img.shape[1]):
                for ix in range(self.niiMASK_img.shape[0]):
                    if self.niiMASK_img[ix, iy, iz] == 0:
                        continue

                    # prepare the signal
                    y = self.niiDWI_img[ix, iy, iz, :].astype(np.float64)
                    y[y < 0] = 0  # [NOTE] this should not happen!

                    # fitting directions
                    if peaks_filename is None:
                        dirs = DTI.fit(y).directions[0]
                    else:
                        dirs = DIRs[ix, iy, iz, :]

                    # dispatch to the right handler for each model
                    MAPs[ix, iy,
                         iz, :], DIRs[ix, iy, iz, :], x, A = self.model.fit(
                             y, dirs.reshape(-1, 3), self.KERNELS,
                             self.get_config('solver_params'), self.htable)

                    # compute fitting error
                    if self.get_config('doComputeNRMSE'):
                        y_est = np.dot(A, x)
                        den = np.sum(y**2)
                        NRMSE[ix, iy,
                              iz] = np.sqrt(np.sum(
                                  (y - y_est)**2) / den) if den > 1e-16 else 0

                    if self.get_config('doSaveCorrectedDWI'):

                        if self.model.name == 'Free-Water':
                            n_iso = len(self.model.d_isos)

                            # keep only FW components of the estimate
                            x[0:x.shape[0] - n_iso] = 0

                            # y_fw_corrected below is the predicted signal by the anisotropic part (no iso part)
                            y_fw_part = np.dot(A, x)

                            # y is the original signal
                            y_fw_corrected = y - y_fw_part
                            y_fw_corrected[
                                y_fw_corrected <
                                0] = 0  # [NOTE] this should not happen!

                            if self.get_config('doNormalizeSignal'
                                               ) and self.scheme.b0_count > 0:
                                y_fw_corrected = y_fw_corrected * self.mean_b0s[
                                    ix, iy, iz]

                            if self.get_config('doKeepb0Intact'
                                               ) and self.scheme.b0_count > 0:
                                # put original b0 data back in.
                                y_fw_corrected[self.scheme.b0_idx] = y[
                                    self.scheme.b0_idx] * self.mean_b0s[ix, iy,
                                                                        iz]

                            DWI_corrected[ix, iy, iz, :] = y_fw_corrected

                    progress.update()

        self.set_config('fit_time', time.time() - t)
        LOG('   [ %s ]' % (time.strftime(
            "%Hh %Mm %Ss", time.gmtime(self.get_config('fit_time')))))

        # store results
        self.RESULTS = {}
        self.RESULTS['DIRs'] = DIRs
        self.RESULTS['MAPs'] = MAPs
        if self.get_config('doComputeNRMSE'):
            self.RESULTS['NRMSE'] = NRMSE
        if self.get_config('doSaveCorrectedDWI'):
            self.RESULTS['DWI_corrected'] = DWI_corrected
Ejemplo n.º 8
0
    def save_results(self, path_suffix=None, save_dir_avg=False):
        """Save the output (directions, maps etc).

        Parameters
        ----------
        path_suffix : string
            Text to be appended to the output path (default : None)
        save_dir_avg : boolean
            If true and the option doDirectionalAverage is true 
            the directional average signal and the scheme 
            will be saved in files (default : False)
        """
        if self.RESULTS is None:
            ERROR('Model not fitted to the data; call "fit()" first')
        if self.get_config('OUTPUT_path') is None:
            RESULTS_path = pjoin('AMICO', self.model.id)
            if path_suffix:
                RESULTS_path = RESULTS_path + '_' + path_suffix
            self.RESULTS['RESULTS_path'] = RESULTS_path
            LOG('\n-> Saving output to "%s/*":' % RESULTS_path)

            # delete previous output
            RESULTS_path = pjoin(self.get_config('DATA_path'), RESULTS_path)
        else:
            RESULTS_path = self.get_config('OUTPUT_path')
            if path_suffix:
                RESULTS_path = RESULTS_path + '_' + path_suffix
            self.RESULTS['RESULTS_path'] = RESULTS_path
            LOG('\n-> Saving output to "%s/*":' % RESULTS_path)

        if not exists(RESULTS_path):
            makedirs(RESULTS_path)
        else:
            for f in glob.glob(pjoin(RESULTS_path, '*')):
                remove(f)

        # configuration
        print('\t- configuration', end=' ')
        with open(pjoin(RESULTS_path, 'config.pickle'), 'wb+') as fid:
            pickle.dump(self.CONFIG, fid, protocol=2)
        print(' [OK]')

        affine = self.niiDWI.affine if nibabel.__version__ >= '2.0.0' else self.niiDWI.get_affine(
        )
        hdr = self.niiDWI.header if nibabel.__version__ >= '2.0.0' else self.niiDWI.get_header(
        )
        hdr['datatype'] = 16
        hdr['bitpix'] = 32

        # estimated orientations
        if not self.get_config('doDirectionalAverage'):
            print('\t- FIT_dir.nii.gz', end=' ')
            niiMAP_img = self.RESULTS['DIRs']
            niiMAP = nibabel.Nifti1Image(niiMAP_img, affine, hdr)
            niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
            )
            niiMAP_hdr['cal_min'] = -1
            niiMAP_hdr['cal_max'] = 1
            niiMAP_hdr['scl_slope'] = 1
            niiMAP_hdr['scl_inter'] = 0
            nibabel.save(niiMAP, pjoin(RESULTS_path, 'FIT_dir.nii.gz'))
            print(' [OK]')

        # fitting error
        if self.get_config('doComputeNRMSE'):
            print('\t- FIT_nrmse.nii.gz', end=' ')
            niiMAP_img = self.RESULTS['NRMSE']
            niiMAP = nibabel.Nifti1Image(niiMAP_img, affine, hdr)
            niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
            )
            niiMAP_hdr['cal_min'] = 0
            niiMAP_hdr['cal_max'] = 1
            niiMAP_hdr['scl_slope'] = 1
            niiMAP_hdr['scl_inter'] = 0
            nibabel.save(niiMAP, pjoin(RESULTS_path, 'FIT_nrmse.nii.gz'))
            print(' [OK]')

        if self.get_config('doSaveCorrectedDWI'):
            if self.model.name == 'Free-Water':
                print('\t- dwi_fw_corrected.nii.gz', end=' ')
                niiMAP_img = self.RESULTS['DWI_corrected']
                niiMAP = nibabel.Nifti1Image(niiMAP_img, affine, hdr)
                niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
                )
                niiMAP_hdr['cal_min'] = 0
                niiMAP_hdr['cal_max'] = 1
                nibabel.save(niiMAP,
                             pjoin(RESULTS_path, 'dwi_fw_corrected.nii.gz'))
                print(' [OK]')
            else:
                WARNING(
                    '"doSaveCorrectedDWI" option not supported for "%s" model'
                    % self.model.name)

        # voxelwise maps
        for i in range(len(self.model.maps_name)):
            print('\t- FIT_%s.nii.gz' % self.model.maps_name[i], end=' ')
            niiMAP_img = self.RESULTS['MAPs'][:, :, :, i]
            niiMAP = nibabel.Nifti1Image(niiMAP_img, affine, hdr)
            niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
            )
            niiMAP_hdr['descrip'] = self.model.maps_descr[
                i] + ' (AMICO v%s)' % self.get_config('version')
            niiMAP_hdr['cal_min'] = niiMAP_img.min()
            niiMAP_hdr['cal_max'] = niiMAP_img.max()
            niiMAP_hdr['scl_slope'] = 1
            niiMAP_hdr['scl_inter'] = 0
            nibabel.save(
                niiMAP,
                pjoin(RESULTS_path, 'FIT_%s.nii.gz' % self.model.maps_name[i]))
            print(' [OK]')

        # Directional average signal
        if save_dir_avg:
            if self.get_config('doDirectionalAverage'):
                print('\t- dir_avg_signal.nii.gz', end=' ')
                niiMAP = nibabel.Nifti1Image(self.niiDWI_img, affine, hdr)
                niiMAP_hdr = niiMAP.header if nibabel.__version__ >= '2.0.0' else niiMAP.get_header(
                )
                niiMAP_hdr[
                    'descrip'] = 'Directional average signal of each shell' + ' (AMICO v%s)' % self.get_config(
                        'version')
                nibabel.save(niiMAP,
                             pjoin(RESULTS_path, 'dir_avg_signal.nii.gz'))
                print(' [OK]')

                print('\t- dir_avg.scheme', end=' ')
                np.savetxt(pjoin(RESULTS_path, 'dir_avg.scheme'),
                           self.scheme.get_table(),
                           fmt="%.06f",
                           delimiter="\t",
                           header="VERSION: {}".format(self.scheme.version),
                           comments='')
                print(' [OK]')
            else:
                WARNING(
                    'The directional average signal was not created (The option doDirectionalAverage is False).'
                )

        LOG('   [ DONE ]')
Ejemplo n.º 9
0
    def fit(self):
        """Fit the model to the data iterating over all voxels (in the mask) one after the other.
        Call the appropriate fit() method of the actual model used.
        """
        @delayed
        @wrap_non_picklable_objects
        def fit_voxel(self, ix, iy, iz, dirs, DTI):
            """Perform the fit in a single voxel.
            """
            # prepare the signal
            y = self.niiDWI_img[ix, iy, iz, :].astype(np.float64)
            y[y < 0] = 0  # [NOTE] this should not happen!

            # fitting directions if not
            if not self.get_config('doDirectionalAverage') and DTI is not None:
                dirs = DTI.fit(y).directions[0].reshape(-1, 3)

            # dispatch to the right handler for each model
            results, dirs, x, A = self.model.fit(
                y, dirs, self.KERNELS, self.get_config('solver_params'),
                self.htable)

            # compute fitting error
            if self.get_config('doComputeNRMSE'):
                y_est = np.dot(A, x)
                den = np.sum(y**2)
                NRMSE = np.sqrt(np.sum(
                    (y - y_est)**2) / den) if den > 1e-16 else 0
            else:
                NRMSE = 0.0

            y_fw_corrected = None
            if self.get_config('doSaveCorrectedDWI'):

                if self.model.name == 'Free-Water':
                    n_iso = len(self.model.d_isos)

                    # keep only FW components of the estimate
                    x[0:x.shape[0] - n_iso] = 0

                    # y_fw_corrected below is the predicted signal by the anisotropic part (no iso part)
                    y_fw_part = np.dot(A, x)

                    # y is the original signal
                    y_fw_corrected = y - y_fw_part
                    y_fw_corrected[y_fw_corrected <
                                   0] = 0  # [NOTE] this should not happen!

                    if self.get_config(
                            'doNormalizeSignal') and self.scheme.b0_count > 0:
                        y_fw_corrected = y_fw_corrected * self.mean_b0s[ix, iy,
                                                                        iz]

                    if self.get_config(
                            'doKeepb0Intact') and self.scheme.b0_count > 0:
                        # put original b0 data back in.
                        y_fw_corrected[self.scheme.b0_idx] = y[
                            self.scheme.b0_idx] * self.mean_b0s[ix, iy, iz]

            return results, dirs, NRMSE, y_fw_corrected

        if self.niiDWI is None:
            ERROR('Data not loaded; call "load_data()" first')
        if self.model is None:
            ERROR('Model not set; call "set_model()" first')
        if self.KERNELS is None:
            ERROR(
                'Response functions not generated; call "generate_kernels()" and "load_kernels()" first'
            )
        if self.KERNELS['model'] != self.model.id:
            ERROR('Response functions were not created with the same model')
        n_jobs = self.get_config('parallel_jobs')
        if n_jobs == -1:
            n_jobs = cpu_count()
        elif n_jobs == 0 or n_jobs < -1:
            ERROR('Number of parallel jobs must be positive or -1')

        self.set_config('fit_time', None)
        totVoxels = np.count_nonzero(self.niiMASK_img)
        LOG(f'\n-> Fitting "{self.model.name}" model to {totVoxels} voxels (using {n_jobs} job{"s" if n_jobs>1 else ""}):'
            )

        # setup fitting directions
        peaks_filename = self.get_config('peaks_filename')
        if peaks_filename is None:
            DIRs = np.zeros([
                self.get_config('dim')[0],
                self.get_config('dim')[1],
                self.get_config('dim')[2], 3
            ],
                            dtype=np.float32)
            nDIR = 1
            if self.get_config('doMergeB0'):
                gtab = gradient_table(
                    np.hstack((0, self.scheme.b[self.scheme.dwi_idx])),
                    np.vstack((np.zeros(
                        (1, 3)), self.scheme.raw[self.scheme.dwi_idx, :3])))
            else:
                gtab = gradient_table(self.scheme.b, self.scheme.raw[:, :3])
            DTI = dti.TensorModel(gtab)
        else:
            if not isfile(pjoin(self.get_config('DATA_path'), peaks_filename)):
                ERROR('PEAKS file not found')
            niiPEAKS = nibabel.load(
                pjoin(self.get_config('DATA_path'), peaks_filename))
            DIRs = niiPEAKS.get_data().astype(np.float32)
            nDIR = np.floor(DIRs.shape[3] / 3)
            PRINT('\t* peaks dim = %d x %d x %d x %d' % DIRs.shape[:4])
            if DIRs.shape[:3] != self.niiMASK_img.shape[:3]:
                ERROR('PEAKS geometry does not match with DWI data')
            DTI = None

        # setup other output files
        MAPs = np.zeros([
            self.get_config('dim')[0],
            self.get_config('dim')[1],
            self.get_config('dim')[2],
            len(self.model.maps_name)
        ],
                        dtype=np.float32)

        # fit the model to the data
        # =========================
        t = time.time()

        ix, iy, iz = np.nonzero(self.niiMASK_img)
        n_per_thread = np.floor(totVoxels / n_jobs)
        idx = np.arange(0, totVoxels + 1, n_per_thread, dtype=np.int32)
        idx[-1] = totVoxels

        estimates = Parallel(n_jobs=n_jobs)(
            fit_voxel(self, ix[i], iy[i], iz[i], DIRs[ix[i], iy[i],
                                                      iz[i], :], DTI)
            for i in tqdm(range(totVoxels),
                          ncols=70,
                          bar_format='   |{bar}| {percentage:4.1f}%',
                          disable=(get_verbose() < 3)))

        self.set_config('fit_time', time.time() - t)
        LOG('   [ %s ]' % (time.strftime(
            "%Hh %Mm %Ss", time.gmtime(self.get_config('fit_time')))))

        # store results
        self.RESULTS = {}

        for i in range(totVoxels):
            MAPs[ix[i], iy[i], iz[i], :] = estimates[i][0]
            DIRs[ix[i], iy[i], iz[i], :] = estimates[i][1]
        self.RESULTS['DIRs'] = DIRs
        self.RESULTS['MAPs'] = MAPs

        if self.get_config('doComputeNRMSE'):
            NRMSE = np.zeros([
                self.get_config('dim')[0],
                self.get_config('dim')[1],
                self.get_config('dim')[2]
            ],
                             dtype=np.float32)
            for i in range(totVoxels):
                NRMSE[ix[i], iy[i], iz[i]] = estimates[i][2]
            self.RESULTS['NRMSE'] = NRMSE

        if self.get_config('doSaveCorrectedDWI'):
            DWI_corrected = np.zeros(self.niiDWI.shape, dtype=np.float32)
            for i in range(totVoxels):
                DWI_corrected[ix[i], iy[i], iz[i], :] = estimates[i][3]
            self.RESULTS['DWI_corrected'] = DWI_corrected
Ejemplo n.º 10
0
    def load_data(self,
                  dwi_filename='DWI.nii',
                  scheme_filename='DWI.scheme',
                  mask_filename=None,
                  b0_thr=0):
        """Load the diffusion signal and its corresponding acquisition scheme.

        Parameters
        ----------
        dwi_filename : string
            The file name of the DWI data, relative to the subject folder (default : 'DWI.nii')
        scheme_filename : string
            The file name of the corresponding acquisition scheme (default : 'DWI.scheme')
        mask_filename : string
            The file name of the (optional) binary mask (default : None)
        b0_thr : float
            The threshold below which a b-value is considered a b0 (default : 0)
        """

        # Loading data, acquisition scheme and mask (optional)
        LOG('\n-> Loading data:')
        tic = time.time()

        PRINT('\t* DWI signal')
        if not isfile(pjoin(self.get_config('DATA_path'), dwi_filename)):
            ERROR('DWI file not found')
        self.set_config('dwi_filename', dwi_filename)
        self.niiDWI = nibabel.load(
            pjoin(self.get_config('DATA_path'), dwi_filename))
        self.niiDWI_img = self.niiDWI.get_data().astype(np.float32)
        hdr = self.niiDWI.header if nibabel.__version__ >= '2.0.0' else self.niiDWI.get_header(
        )
        self.set_config('dim', self.niiDWI_img.shape[:3])
        self.set_config('pixdim', tuple(hdr.get_zooms()[:3]))
        PRINT('\t\t- dim    = %d x %d x %d x %d' % self.niiDWI_img.shape)
        PRINT('\t\t- pixdim = %.3f x %.3f x %.3f' % self.get_config('pixdim'))
        # Scale signal intensities (if necessary)
        if (np.isfinite(hdr['scl_slope']) and np.isfinite(hdr['scl_inter'])
                and hdr['scl_slope'] != 0
                and (hdr['scl_slope'] != 1 or hdr['scl_inter'] != 0)):
            PRINT('\t\t- rescaling data ', end='')
            self.niiDWI_img = self.niiDWI_img * hdr['scl_slope'] + hdr[
                'scl_inter']
            PRINT('[OK]')

        PRINT('\t* Acquisition scheme')
        if not isfile(pjoin(self.get_config('DATA_path'), scheme_filename)):
            ERROR('SCHEME file not found')
        self.set_config('scheme_filename', scheme_filename)
        self.set_config('b0_thr', b0_thr)
        self.scheme = amico.scheme.Scheme(
            pjoin(self.get_config('DATA_path'), scheme_filename), b0_thr)
        PRINT(
            f'\t\t- {self.scheme.nS} samples, {len(self.scheme.shells)} shells'
        )
        PRINT(f'\t\t- {self.scheme.b0_count} @ b=0', end=' ')
        for i in range(len(self.scheme.shells)):
            PRINT(
                f', {len(self.scheme.shells[i]["idx"])} @ b={self.scheme.shells[i]["b"]:.1f}',
                end=' ')
        PRINT()

        if self.scheme.nS != self.niiDWI_img.shape[3]:
            ERROR('Scheme does not match with DWI data')

        PRINT('\t* Binary mask')
        if mask_filename is not None:
            if not isfile(pjoin(self.get_config('DATA_path'), mask_filename)):
                ERROR('MASK file not found')
            self.niiMASK = nibabel.load(
                pjoin(self.get_config('DATA_path'), mask_filename))
            self.niiMASK_img = self.niiMASK.get_data().astype(np.uint8)
            niiMASK_hdr = self.niiMASK.header if nibabel.__version__ >= '2.0.0' else self.niiMASK.get_header(
            )
            PRINT('\t\t- dim    = %d x %d x %d' % self.niiMASK_img.shape[:3])
            PRINT('\t\t- pixdim = %.3f x %.3f x %.3f' %
                  niiMASK_hdr.get_zooms()[:3])
            if self.niiMASK.ndim != 3:
                ERROR('The provided MASK if 4D, but a 3D dataset is expected')
            if self.get_config('dim') != self.niiMASK_img.shape[:3]:
                ERROR('MASK geometry does not match with DWI data')
        else:
            self.niiMASK = None
            self.niiMASK_img = np.ones(self.get_config('dim'))
            PRINT('\t\t- not specified')
        PRINT(f'\t\t- voxels = {np.count_nonzero(self.niiMASK_img)}')

        LOG(f'   [ {time.time() - tic:.1f} seconds ]')

        # Preprocessing
        LOG('\n-> Preprocessing:')
        tic = time.time()

        if self.get_config('doDebiasSignal'):
            PRINT('\t* Debiasing signal... ', end='')
            sys.stdout.flush()
            if self.get_config('DWI-SNR') == None:
                ERROR(
                    "Set noise variance for debiasing (eg. ae.set_config('RicianNoiseSigma', sigma))"
                )
            self.niiDWI_img = debiasRician(self.niiDWI_img,
                                           self.get_config('DWI-SNR'),
                                           self.niiMASK_img, self.scheme)
            PRINT(' [OK]')

        if self.get_config('doNormalizeSignal'):
            PRINT('\t* Normalizing to b0... ', end='')
            sys.stdout.flush()
            if self.scheme.b0_count > 0:
                self.mean_b0s = np.mean(self.niiDWI_img[:, :, :,
                                                        self.scheme.b0_idx],
                                        axis=3)
            else:
                ERROR('No b0 volume to normalize signal with')
            norm_factor = self.mean_b0s.copy()
            idx = self.mean_b0s <= 0
            norm_factor[idx] = 1
            norm_factor = 1 / norm_factor
            norm_factor[idx] = 0
            for i in range(self.scheme.nS):
                self.niiDWI_img[:, :, :, i] *= norm_factor
            PRINT(
                f'[ min={self.niiDWI_img.min():.2f},  mean={self.niiDWI_img.mean():.2f}, max={self.niiDWI_img.max():.2f} ]'
            )

        if self.get_config('doMergeB0'):
            PRINT('\t* Merging multiple b0 volume(s)')
            mean = np.expand_dims(np.mean(self.niiDWI_img[:, :, :,
                                                          self.scheme.b0_idx],
                                          axis=3),
                                  axis=3)
            self.niiDWI_img = np.concatenate(
                (mean, self.niiDWI_img[:, :, :, self.scheme.dwi_idx]), axis=3)
        else:
            PRINT('\t* Keeping all b0 volume(s)')

        if self.get_config('doDirectionalAverage'):
            PRINT(
                '\t* Performing the directional average on the signal of each shell... '
            )
            numShells = len(self.scheme.shells)
            dir_avg_img = self.niiDWI_img[:, :, :, :(numShells + 1)]
            scheme_table = np.zeros([numShells + 1, 7])

            id_bval = 0
            dir_avg_img[:, :, :,
                        id_bval] = np.mean(self.niiDWI_img[:, :, :,
                                                           self.scheme.b0_idx],
                                           axis=3)
            scheme_table[id_bval, :] = np.array([1, 0, 0, 0, 0, 0, 0])

            bvals = []
            for shell in self.scheme.shells:
                bvals.append(shell['b'])

            sort_idx = np.argsort(bvals)

            for shell_idx in sort_idx:
                shell = self.scheme.shells[shell_idx]
                id_bval = id_bval + 1
                dir_avg_img[:, :, :,
                            id_bval] = np.mean(self.niiDWI_img[:, :, :,
                                                               shell['idx']],
                                               axis=3)
                scheme_table[id_bval, :] = np.array([
                    1, 0, 0, shell['G'], shell['Delta'], shell['delta'],
                    shell['TE']
                ])

            self.niiDWI_img = dir_avg_img.astype(np.float32)
            self.set_config('dim', self.niiDWI_img.shape[:3])
            PRINT('\t\t- dim    = %d x %d x %d x %d' % self.niiDWI_img.shape)
            PRINT('\t\t- pixdim = %.3f x %.3f x %.3f' %
                  self.get_config('pixdim'))

            PRINT('\t* Acquisition scheme')
            self.scheme = amico.scheme.Scheme(scheme_table, b0_thr)
            PRINT(
                f'\t\t- {self.scheme.nS} samples, {len(self.scheme.shells)} shells'
            )
            PRINT(f'\t\t- {self.scheme.b0_count} @ b=0', end=' ')
            for i in range(len(self.scheme.shells)):
                PRINT(
                    f', {len(self.scheme.shells[i]["idx"])} @ b={self.scheme.shells[i]["b"]:.1f}',
                    end=' ')
            PRINT()

            if self.scheme.nS != self.niiDWI_img.shape[3]:
                ERROR('Scheme does not match with DWI data')

        LOG(f'   [ {time.time() - tic:.1f} seconds ]')