Esempio n. 1
0
def test_mask():
    vol = np.zeros((30, 30, 30))
    vol[15, 15, 15] = 1
    struct = generate_binary_structure(3, 1)
    voln = binary_dilation(vol, structure=struct, iterations=4).astype('f4')
    initial = np.sum(voln > 0)
    mask = voln.copy()
    thresh = otsu(mask)
    mask = mask > thresh
    initial_otsu = np.sum(mask > 0)
    assert_equal(initial_otsu, initial)

    mins, maxs = bounding_box(mask)
    voln_crop = crop(mask, mins, maxs)
    initial_crop = np.sum(voln_crop > 0)
    assert_equal(initial_crop, initial)

    applymask(voln, mask)
    final = np.sum(voln > 0)
    assert_equal(final, initial)

    # Test multi_median.
    median_test = np.arange(25).reshape(5, 5)
    median_control = median_test.copy()
    medianradius = 3
    median_test = multi_median(median_test, medianradius, 3)

    medarr = np.ones_like(median_control.shape) * ((medianradius * 2) + 1)
    median_filter(median_control, medarr, output=median_control)
    median_filter(median_control, medarr, output=median_control)
    median_filter(median_control, medarr, output=median_control)
    assert_equal(median_test, median_control)
Esempio n. 2
0
def test_mask():
    vol = np.zeros((30, 30, 30))
    vol[15, 15, 15] = 1
    struct = generate_binary_structure(3, 1)
    voln = binary_dilation(vol, structure=struct, iterations=4).astype('f4')
    initial = np.sum(voln > 0)
    mask = voln.copy()
    thresh = otsu(mask)
    mask = mask > thresh
    initial_otsu = np.sum(mask > 0)
    assert_equal(initial_otsu, initial)

    mins, maxs = bounding_box(mask)
    voln_crop = crop(mask, mins, maxs)
    initial_crop = np.sum(voln_crop > 0)
    assert_equal(initial_crop, initial)

    applymask(voln, mask)
    final = np.sum(voln > 0)
    assert_equal(final, initial)

    # Test multi_median.
    median_test = np.arange(25).reshape(5, 5)
    median_control = median_test.copy()
    medianradius = 3
    median_test = multi_median(median_test, medianradius, 3)

    medarr = np.ones_like(median_control.shape) * ((medianradius * 2) + 1)
    median_filter(median_control, medarr, output=median_control)
    median_filter(median_control, medarr, output=median_control)
    median_filter(median_control, medarr, output=median_control)
    assert_equal(median_test, median_control)
def compute_masks_crop_bet(reference_scan, other_scan1, ref_scan_path,
                           ref_dir_path, other_dir_path, starting_dir):
    # Use bet for generating brain masks for each of the scans

    # Get the mask of the reference scan
    os.chdir(ref_dir_path)
    subprocess.call([
        "bet",
        os.path.basename(ref_scan_path), "Brain_temp", "-m", "-n", "-R", "-f",
        "0.2", "-t"
    ])
    reference_scan_mask = nib.load("Brain_temp_mask.nii.gz")
    reference_scan_mask = reference_scan_mask.get_data()
    # Delete the created files
    os.remove('Brain_temp.nii.gz')
    os.remove('Brain_temp_mask.nii.gz')

    # Go back to the original directory. We do this because the file paths specified are not
    # Required to be absolute
    os.chdir(starting_dir)

    # Similarly get the masks of the other scans
    os.chdir(other_dir_path)
    subprocess.call([
        "bet", "Full_Registered_Scan.nii.gz", "Brain_temp", "-m", "-n", "-R",
        "-f", "0.2", "-t"
    ])
    other_scan1_mask = nib.load("Brain_temp_mask.nii.gz")
    other_scan1_mask = other_scan1_mask.get_data()
    os.remove('Brain_temp.nii.gz')
    os.remove('Brain_temp_mask.nii.gz')

    #Get the intersection of the masks
    mask_union = np.logical_and(reference_scan_mask, other_scan1_mask)

    #Apply the combined mask to the scans
    reference_scan_brain = applymask(reference_scan, mask_union)
    other_scan1_brain = applymask(other_scan1, mask_union)

    #Crop the scans using the unioned mask
    (mins, maxs) = bounding_box(mask_union)
    reference_scan_brain = crop(reference_scan_brain, mins, maxs)

    return (reference_scan_brain, other_scan1_brain)
Esempio n. 4
0
def crop_nifti(img, wbbox):
    """Applies cropping from a world space defined bounding box and fixes the
    affine to keep data aligned."""
    data = img.get_fdata(dtype=np.float32, caching='unchanged')
    affine = img.affine

    voxel_bb_mins = world_to_voxel(wbbox.minimums, affine)
    voxel_bb_maxs = world_to_voxel(wbbox.maximums, affine)

    # Prevent from trying to crop outside data boundaries by clipping bbox
    extent = list(data.shape[:3])
    for i in range(3):
        voxel_bb_mins[i] = max(0, voxel_bb_mins[i])
        voxel_bb_maxs[i] = min(extent[i], voxel_bb_maxs[i])
    translation = voxel_to_world(voxel_bb_mins, affine)

    data_crop = np.copy(crop(data, voxel_bb_mins, voxel_bb_maxs))

    new_affine = np.copy(affine)
    new_affine[0:3, 3] = translation[0:3]

    return nib.Nifti1Image(data_crop, new_affine)
Esempio n. 5
0
 def get_bounded_image(self, vol):
     min_indicies, max_indicies = bounding_box(vol)
     bounded_image = crop(vol, min_indicies, max_indicies)
     return bounded_image
Esempio n. 6
0
def crop_using_mask(data, mask):
    mins, maxs = bounding_box(mask)
    cropped_data = crop(data, mins, maxs)
    return cropped_data
Esempio n. 7
0
    def show(self, rescale=False, fps=10, output=''):
        self._visuVolume = self._qVolume.astype("uint8")

        # Scale data direction wise or globally
        if rescale:
            log.info("Scaling data for every direction individually...")
            for i in range(0, self._qVolume.shape[2]):
                diff_dir_image = np.copy(self._qVolume[:, :, i].astype(float))
                mini, maxi = np.amin(diff_dir_image), np.amax(diff_dir_image)
                self._visuVolume[:, :, i] = (255.0 * (diff_dir_image - mini) /
                                             maxi).astype("uint8")
        else:
            image = np.copy(self._qVolume.astype(float))
            mini, maxi = np.amin(image), np.amax(image)
            self._visuVolume = (255.0 * (image - mini) / maxi).astype("uint8")

        if output:
            # Output the animation
            images = self._visuVolume
            crop_image = np.copy(images)

            # Cropping volume
            t = 0.05
            log.info("Cropping volume in a bounding box from pixels below " +
                     str(int(t * 100.0)) + " % intensity threshold ...")
            crop_image[crop_image <= int(t * 255.0)] = 0
            min_bounds, max_bounds = mask.bounding_box(crop_image)
            images = mask.crop(images, min_bounds, max_bounds)

            # Swap axes for gif writer
            images = np.swapaxes(images, 0, 2)
            images = np.swapaxes(images, 1, 2)

            if output.find(".gif") < 0:
                output = output + ".gif"
            imageio.mimwrite(output, images)
            log.info(output + " file created.")
        else:
            # Initialize figure
            fig = plt.figure()

            # Add current frame to figure
            plt.subplot(121)
            self._currentFrame = plt.imshow(self._visuVolume[:, :, 0],
                                            cmap='gray')
            if not rescale:
                plt.colorbar()
            self._currentFrame.set_interpolation('nearest')
            plt.axis('off')

            # Add text display to figure
            plt.subplot(122)
            plt.text(0.5,
                     0.7,
                     "Data : " + str(self._data.shape) + "\n" +
                     "Q-Space volume : " + str(self._qVolume.shape),
                     horizontalalignment='center',
                     verticalalignment='center')
            self._currentText = plt.text(
                0.5,
                0.5,
                "Position , action='store_true': Value, Mean\n" +
                str(self._currentPosition),
                horizontalalignment='center',
                verticalalignment='center',
                fontsize='x-large')
            plt.axis('off')

            # Connect the callback functions
            fig.canvas.mpl_connect('motion_notify_event', self._onMove)
            fig.canvas.mpl_connect('button_press_event', self._onClick)
            fig.canvas.mpl_connect('scroll_event', self._onScroll)

            # Create the animation in the figure
            anim = animation.FuncAnimation(fig,
                                           self._updateImage,
                                           frames=self._qVolume.shape[2],
                                           interval=1000.0 / fps)

            plt.show()
Esempio n. 8
0
# Load the data
img = nib.load(fdwi)
img_data = img.get_data()
# Load the mask
mask = nib.load(fmask)
mask_data = mask.get_data()

#load bvals, bvecs and gradient files
bvals, bvecs = read_bvals_bvecs(fbval, fbvec)
gtab = gradient_table(bvals, bvecs)

# Apply the mask to the volume
mask_boolean = mask_data > 0.01
mins, maxs = bounding_box(mask_boolean)
mask_boolean = crop(mask_boolean, mins, maxs)
cropped_volume = crop(img_data, mins, maxs)
data = applymask(cropped_volume, mask_boolean)

fw_runner = FreewaterRunner(data, gtab)
fw_runner.LOG = True  # turn on logging for this example
fw_runner.run_model(num_iter=100, dt=0.001)

# save the loss function to the working directory
#freewater_runner.plot_loss()

# Save the free water map somewhere
fw_file = output_directory + "/freewater.nii.gz"
nib.save(nib.Nifti1Image(fw_runner.get_fw_map(), img.affine), fw_file)

fw_md_file = output_directory + "/freewater_md.nii.gz"
Esempio n. 9
0
def rumba_deconv_global(data,
                        kernel,
                        mask,
                        n_iter=600,
                        recon_type='smf',
                        n_coils=1,
                        R=1,
                        use_tv=True,
                        verbose=False):
    r'''
    Fit fODF for all voxels simultaneously using RUMBA-SD.

    Deconvolves the kernel from the diffusion-weighted signal at each voxel by
    computing a maximum likelihood estimation of the fODF [1]_. Global fitting
    also permits the use of total variation regularization (RUMBA-SD + TV). The
    spatial dependence introduced by TV promotes smoother solutions (i.e.
    prevents oscillations), while still allowing for sharp discontinuities
    [2]_. This promotes smoothness and continuity along individual tracts while
    preventing smoothing of adjacent tracts.

    Generally, global_fit will proceed more quickly than the voxelwise fit
    provided that the computer has adequate RAM (>= 16 GB should be more than
    sufficient).

    Parameters
    ----------
    data : 4d ndarray (x, y, z, N)
        Signal values for entire brain. None of the volume dimensions x, y, z
        can be 1 if TV regularization is required.
    kernel : 2d ndarray (N, M)
        Deconvolution kernel mapping volume fractions of the M compartments to
        N-length signal. Last two columns should be for GM and CSF.
    mask : 3d ndarray(x, y, z)
        Binary mask specifying voxels of interest with 1; fODF will only be
        fit at these voxels (0 elsewhere).
    n_iter : int, optional
        Number of iterations for fODF estimation. Must be a positive int.
        Default: 600
    recon_type : {'smf', 'sos'}, optional
        MRI reconstruction method: spatial matched filter (SMF) or
        sum-of-squares (SoS). SMF reconstruction generates Rician noise while
        SoS reconstruction generates Noncentral Chi noise. Default: 'smf'
    n_coils : int, optional
        Number of coils in MRI scanner -- only relevant in SoS reconstruction.
        Must be a positive int. Default: 1
    use_tv : bool, optional
        If true, applies total variation regularization. This requires a brain
        volume with no singleton dimensions. Default: True
    verbose : bool, optional
        If true, logs updates on estimated signal-to-noise ratio after each
        iteration. Default: False

    Returns
    -------
    fit_array : 4d ndarray (x, y, z, M)
        fODF and GM/CSF volume fractions computed for each voxel. First M-2
        components are fODF, while last two are GM and CSf respectively.

    Notes
    -----
    TV modifies our cost function as follows:

    $J(\textbf{f}) = -\log{P(\textbf{S}|\textbf{H}, \textbf{f}, \sigma^2, n)})+
    \alpha_{TV}TV(\textbf{f})$

    where the first term is the negative log likelihood described in the notes
    of `rumba_deconv`, and the second term is the TV energy, or the sum of
    gradient absolute values for the fODF across the entire brain. This results
    in a new multiplicative factor in the iterative scheme, now becoming:

    $\textbf{f}^{k+1} = \textbf{f}^k \circ \frac{\textbf{H}^T\left[\textbf{S}
    \circ\frac{I_n(\textbf{S}\circ\textbf{Hf}^k/\sigma^2)} {I_{n-1}(\textbf{S}
    \circ\textbf{Hf}^k/\sigma^2)} \right ]} {\textbf{H}^T\textbf{Hf}^k}\circ
    \textbf{R}^k$

    where $\textbf{R}^k$ is computed voxelwise by:

    $(\textbf{R}^k)_j = \frac{1}{1 - \alpha_{TV}div\left(\frac{\triangledown[
    \textbf{f}^k_{3D}]_j}{\lvert\triangledown[\textbf{f}^k_{3D}]_j \rvert}
    \right)\biggr\rvert_{x, y, z}}$

    Here, $\triangledown$ is the symbol for the 3D gradient at any voxel.

    The regularization strength, $\alpha_{TV}$ is updated after each iteration
    by the discrepancy principle -- specifically, it is selected to match the
    estimated variance after each iteration [3]_.

    References
    ----------
    .. [1] Canales-Rodríguez, E. J., Daducci, A., Sotiropoulos, S. N., Caruyer,
           E., Aja-Fernández, S., Radua, J., Mendizabal, J. M. Y.,
           Iturria-Medina, Y., Melie-García, L., Alemán-Gómez, Y., Thiran,
           J.-P., Sarró, S., Pomarol-Clotet, E., & Salvador, R. (2015).
           Spherical Deconvolution of Multichannel Diffusion MRI Data with
           Non-Gaussian Noise Models and Spatial Regularization. PLOS ONE,
           10(10), e0138910. https://doi.org/10.1371/journal.pone.0138910

    .. [2] Rudin, L. I., Osher, S., & Fatemi, E. (1992). Nonlinear total
           variation based noise removal algorithms. Physica D: Nonlinear
           Phenomena, 60(1), 259–268.
           https://doi.org/10.1016/0167-2789(92)90242-F

    .. [3] Chambolle A. An algorithm for total variation minimization and
           applications. Journal of Mathematical Imaging and Vision. 2004;
           20:89–97.
    '''

    # Crop data to reduce memory consumption
    dim_orig = data.shape
    ixmin, ixmax = bounding_box(mask)
    data = crop(data, ixmin, ixmax)
    mask = crop(mask, ixmin, ixmax)

    if np.any(np.array(data.shape[:3]) == 1) and use_tv:
        raise ValueError("Cannot use TV regularization if any spatial" +
                         "dimensions are 1; " +
                         f"provided dimensions were {data.shape[:3]}")

    epsilon = 1e-7

    n_grad = kernel.shape[0]  # gradient directions
    n_comp = kernel.shape[1]  # number of compartments
    dim = data.shape
    n_v_tot = np.prod(dim[:3])  # total number of voxels

    # Initial guess is iso-probable
    fodf0 = np.ones((n_comp, 1), dtype=np.float32)
    fodf0 = fodf0 / np.sum(fodf0, axis=0)

    if recon_type == "smf":
        n_order = 1  # Rician noise (same as Noncentral Chi with order 1)
    elif recon_type == "sos":
        n_order = n_coils  # Noncentral Chi noise (order = # of coils)
    else:
        raise ValueError("Invalid recon_type. Should be 'smf' or 'sos', " +
                         f"received f{recon_type}")

    mask_vec = np.ravel(mask)
    # Indices of target voxels
    index_mask = np.atleast_1d(np.squeeze(np.argwhere(mask_vec)))
    n_v_true = len(index_mask)  # number of target voxels

    data_2d = np.zeros((n_v_true, n_grad), dtype=np.float32)
    for i in range(n_grad):
        data_2d[:, i] = np.ravel(
            data[:, :, :, i])[index_mask]  # only keep voxels of interest

    data_2d = data_2d.T
    fodf = np.tile(fodf0, (1, n_v_true))
    reblurred = np.matmul(kernel, fodf)

    # For use later
    kernel_t = kernel.T
    f_zero = 0

    # Initialize algorithm parameters
    sigma0 = 1 / 15
    sigma2 = sigma0**2
    tv_lambda = sigma2  # initial guess for TV regularization strength

    # Expand into matrix form for iterations
    sigma2 = sigma2 * np.ones(data_2d.shape, dtype=np.float32)
    tv_lambda_aux = np.zeros((n_v_tot), dtype=np.float32)

    reblurred_s = data_2d * reblurred / sigma2

    for i in range(n_iter):
        fodf_i = fodf
        ratio = mbessel_ratio(n_order, reblurred_s).astype(np.float32)
        rl_factor = np.matmul(kernel_t, data_2d * ratio) / \
            (np.matmul(kernel_t, reblurred) + _EPS)

        if use_tv:  # apply TV regularization
            tv_factor = np.ones(fodf_i.shape, dtype=np.float32)
            fodf_4d = _reshape_2d_4d(fodf_i.T, mask)
            # Compute gradient, divergence
            gr = _grad(fodf_4d)
            d_inv = 1 / np.sqrt(epsilon**2 + np.sum(gr**2, axis=3))
            gr_norm = (gr * d_inv[:, :, :, None, :])
            div_f = _divergence(gr_norm)
            g0 = np.abs(1 - tv_lambda * div_f)
            tv_factor_4d = 1 / (g0 + _EPS)

            for j in range(n_comp):
                tv_factor_1d = np.ravel(tv_factor_4d[:, :, :, j])[index_mask]
                tv_factor[j, :] = tv_factor_1d

            # Apply TV regularization to iteration factor
            rl_factor = rl_factor * tv_factor

        fodf = fodf_i * rl_factor  # result of iteration
        fodf = np.maximum(f_zero, fodf)  # positivity constraint

        # Update other variables
        reblurred = np.matmul(kernel, fodf)
        reblurred_s = data_2d * reblurred / sigma2

        # Iterate variance
        sigma2_i = (1 / (n_grad * n_order)) * \
            np.sum((data_2d**2 + reblurred**2) / 2 - (
                sigma2 * reblurred_s) * ratio, axis=0)
        sigma2_i = np.minimum((1 / 8)**2, np.maximum(sigma2_i, (1 / 80)**2))

        if verbose:
            logger.info("Iteration %d of %d", i + 1, n_iter)

            snr_mean = np.mean(1 / np.sqrt(sigma2_i))
            snr_std = np.std(1 / np.sqrt(sigma2_i))
            logger.info("Mean SNR (S0/sigma) estimated to be %.3f +/- %.3f",
                        snr_mean, snr_std)
        # Expand into matrix
        sigma2 = np.tile(sigma2_i[None, :], (data_2d.shape[0], 1))

        # Update TV regularization strength using the discrepancy principle
        if use_tv:
            if R == 1:
                tv_lambda = np.mean(sigma2_i)

                if tv_lambda < (1 / 30)**2:
                    tv_lambda = (1 / 30)**2
            else:  # different factor for each voxel
                tv_lambda_aux[index_mask] = sigma2_i
                tv_lambda = np.reshape(tv_lambda_aux, (*dim[:3], 1))

    fodf = fodf.astype(np.float64)
    fodf = fodf / (np.sum(fodf, axis=0)[None, ...] + _EPS)  # normalize fODF

    # Extract compartments
    fit_array = np.zeros((*dim_orig[:3], n_comp))
    _reshape_2d_4d(fodf.T,
                   mask,
                   out=fit_array[ixmin[0]:ixmax[0], ixmin[1]:ixmax[1],
                                 ixmin[2]:ixmax[2]])

    return fit_array