Source code for mriqc.qc.anatomical

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
# pylint: disable=no-member
#
# @Author: oesteban
# @Date:   2016-01-05 11:29:40
# @Email:  code@oscaresteban.es
# @Last modified by:   oesteban
# @Last Modified time: 2016-02-29 11:40:36
"""
Computation of the quality assessment measures on structural MRI



"""

import numpy as np
from six import string_types
import scipy.ndimage as nd

FSL_FAST_LABELS = {'csf': 1, 'gm': 2, 'wm': 3, 'bg': 0}

[docs]def snr(img, seg, fglabel, bglabel='bg'): r""" Calculate the :abbr:`SNR (Signal-to-Noise Ratio)` .. math:: \text{SNR} = \frac{\mu_F}{\sigma_B} where :math:`\mu_F` is the mean intensity of the foreground and :math:`\sigma_B` is the standard deviation of the background, where the noise is computed. :param numpy.ndarray img: input data :param numpy.ndarray seg: input segmentation :param str fglabel: foreground label in the segmentation data. :param str bglabel: background label in the segmentation data. :return: the computed SNR for the foreground segmentation """ if isinstance(fglabel, string_types): fglabel = FSL_FAST_LABELS[fglabel] if isinstance(bglabel, string_types): bglabel = FSL_FAST_LABELS[bglabel] fg_mean = img[seg == fglabel].mean() bg_std = img[seg == bglabel].std() return fg_mean / bg_std
[docs]def cnr(img, seg, lbl=None): r""" Calculate the :abbr:`CNR (Contrast-to-Noise Ratio)` .. math:: \text{CNR} = \frac{|\mu_\text{GM} - \mu_\text{WM} |}{\sigma_B} :param numpy.ndarray img: input data :param numpy.ndarray seg: input segmentation :return: the computed CNR """ if lbl is None: lbl = FSL_FAST_LABELS return np.abs(img[seg == lbl['gm']].mean() - img[seg == lbl['wm']].mean()) / \ img[seg == lbl['bg']].std()
[docs]def fber(img, seg, fglabel=None, bglabel=0): r""" Calculate the :abbr:`FBER (Foreground-Background Energy Ratio)` .. math:: \text{FBER} = \frac{E[|F|^2]}{E[|B|^2]} :param numpy.ndarray img: input data :param numpy.ndarray seg: input segmentation """ if fglabel is not None: fgdata = img[seg == FSL_FAST_LABELS[fglabel]] else: fgdata = img[seg != bglabel] fg_mu = (np.abs(fgdata) ** 2).mean() bg_mu = (np.abs(img[seg == bglabel]) ** 2).mean() return fg_mu / bg_mu
[docs]def efc(img): """ Calculate the :abbr:`EFC (Entropy Focus Criterion)` [Atkinson1997]_ The original equation is normalized by the maximum entropy, so that the :abbr:`EFC (Entropy Focus Criterion)` can be compared across images with different dimensions. :param numpy.ndarray img: input data """ # Calculate the maximum value of the EFC (which occurs any time all # voxels have the same value) efc_max = 1.0 * np.prod(img.shape) * (1.0 / np.sqrt(np.prod(img.shape))) * \ np.log(1.0 / np.sqrt(np.prod(img.shape))) # Calculate the total image energy b_max = np.sqrt((img**2).sum()) # Calculate EFC (add 1e-16 to the image data to keep log happy) return (1.0 / efc_max) * np.sum((img / b_max) * np.log((img + 1e-16) / b_max))
[docs]def artifacts(img, seg, calculate_qi2=False, bglabel=0): """ Detect artifacts in the image using the method described in [Mortamet2009]_. Calculates QI1, the fraction of total voxels that within artifacts. Optionally, it also calculates QI2, the distance between the distribution of noise voxel (non-artifact background voxels) intensities, and a Rician distribution. :param numpy.ndarray img: input data :param numpy.ndarray seg: input segmentation """ bg_mask = np.zeros_like(img, dtype=np.uint8) bg_mask[seg == bglabel] = 1 bg_img = img * bg_mask # Find the background threshold (the most frequently occurring value # excluding 0) hist, bin_edges = np.histogram(bg_img[bg_img > 0], bins=256) bg_threshold = np.mean(bin_edges[np.argmax(hist)]) # Apply this threshold to the background voxels to identify voxels # contributing artifacts. bg_img[bg_img <= bg_threshold] = 0 bg_img[bg_img != 0] = 1 # Create a structural element to be used in an opening operation. struct_elmnt = np.zeros((3, 3, 3)) struct_elmnt[0, 1, 1] = 1 struct_elmnt[1, 1, :] = 1 struct_elmnt[1, :, 1] = 1 struct_elmnt[2, 1, 1] = 1 # Perform an opening operation on the background data. bg_img = nd.binary_opening(bg_img, structure=struct_elmnt).astype(np.uint8) # Count the number of voxels that remain after the opening operation. # These are artifacts. artifact_qi1 = bg_img.sum() / float(bg_mask.sum()) if calculate_qi2: raise NotImplementedError return artifact_qi1, None
[docs]def volume_fraction(pvms): """ Computes the :abbr:`ICV (intracranial volume)` fractions corresponding to the (partial volume maps). :param list pvms: list of :code:`numpy.ndarray` of partial volume maps. """ tissue_vfs = {} total = 0 for k, lid in list(FSL_FAST_LABELS.items()): if lid == 0: continue tissue_vfs[k] = pvms[lid - 1].sum() total += tissue_vfs[k] for k in tissue_vfs.keys(): tissue_vfs[k] /= total return tissue_vfs
[docs]def rpve(pvms, seg): """ Computes the :abbr:`rPVe (residual partial voluming error)` of each tissue class. """ pvfs = {} for k, lid in list(FSL_FAST_LABELS.items()): if lid == 0: continue pvmap = pvms[lid - 1][seg == lid] pvmap[pvmap < 0.] = 0. pvmap[pvmap >= 1.] = 0. upth = np.percentile(pvmap[pvmap > 0], 98) loth = np.percentile(pvmap[pvmap > 0], 2) pvmap[pvmap < loth] = 0 pvmap[pvmap > upth] = 0 pvfs[k] = pvmap[pvmap > 0].sum() return pvfs
[docs]def summary_stats(img, pvms): r""" Estimates the mean, the standard deviation, the 95\% and the 5\% percentiles of each tissue distribution. """ mean = {} stdv = {} p95 = {} p05 = {} if np.array(pvms).ndim == 4: pvms.insert(0, np.array(pvms).sum(axis=0)) elif np.array(pvms).ndim == 3: bgpvm = np.ones_like(pvms) pvms = [bgpvm - pvms, pvms] else: raise RuntimeError('Incorrect image dimensions (%d)' % np.array(pvms).ndim) if len(pvms) == 4: labels = list(FSL_FAST_LABELS.items()) elif len(pvms) == 2: labels = zip(['bg', 'fg'], range(2)) for k, lid in labels: im_lid = pvms[lid] * img mean[k] = im_lid[im_lid > 0].mean() stdv[k] = im_lid[im_lid > 0].std() p95[k] = np.percentile(im_lid[im_lid > 0], 95) p05[k] = np.percentile(im_lid[im_lid > 0], 5) return mean, stdv, p95, p05