Ejemplo n.º 1
0
    def segment(self, model: SegModel, tissue: Tissue, use_rms: bool = False):
        """Segment tissue in scan.

        Args:
            model (SegModel): Model to use for segmenting scans.
            tissue (Tissue): The tissue to segment.
            use_rms (`bool`, optional): Use root-mean-square of echos for segmentation (preferred). Defaults to `False`.

        Returns:
            MedicalVolume: Binary mask for segmented region.
        """
        # Use first echo for segmentation.
        logging.info("Segmenting {}...".format(tissue.FULL_NAME))

        if use_rms:
            segmentation_volume = self.calc_rms()
        else:
            segmentation_volume = self.volumes[0]

        # Segment tissue and add it to list.
        mask = model.generate_mask(segmentation_volume)
        tissue.set_mask(mask)

        self.__add_tissue__(tissue)

        return mask
Ejemplo n.º 2
0
    def __fitting_helper(self, qv_type: QuantitativeValueType,
                         echo_inds: Sequence[int], tissue: Tissue, bounds, tc0,
                         decimal_precision, mask_path):
        echo_info = [(self.echo_times[i], self.volumes[i]) for i in echo_inds]

        # sort by echo time
        echo_info = sorted(echo_info, key=lambda x: x[0])

        xs = [et for et, _ in echo_info]
        ys = [vol for _, vol in echo_info]

        # only calculate for focused region if a mask is available, this speeds up computation
        mask = tissue.get_mask()
        if not mask and mask_path:
            mask = fio_utils.generic_load(mask_path, expected_num_volumes=1)
            if tuple(np.unique(mask.volume)) != (0, 1):
                raise ValueError(
                    'mask_filepath must reference binary segmentation volume')

        mef = MonoExponentialFit(xs,
                                 ys,
                                 mask=mask,
                                 bounds=bounds,
                                 tc0=tc0,
                                 decimal_precision=decimal_precision)
        qv_map, r2 = mef.fit()

        quant_val_map = qv_type(qv_map)
        quant_val_map.add_additional_volume('r2', r2)

        tissue.add_quantitative_value(quant_val_map)

        return quant_val_map
Ejemplo n.º 3
0
def handle_segmentation(vargin, scan: ScanSequence, tissue: Tissue):
    segment_weights_path = vargin[SEGMENTATION_WEIGHTS_DIR_KEY][0]
    tissue.find_weights(segment_weights_path)

    # Load model
    dims = scan.get_dimensions()
    input_shape = (dims[0], dims[1], 1)
    model = get_model(vargin[SEGMENTATION_MODEL_KEY],
                      input_shape=input_shape,
                      weights_path=tissue.weights_file_path)
    model.batch_size = vargin[SEGMENTATION_BATCH_SIZE_KEY]

    return model
Ejemplo n.º 4
0
    def generate_t2_star_map(self,
                             tissue: Tissue,
                             mask_path: str = None,
                             num_workers: int = 0):
        """
        Generate 3D :math:`T_2^* map and r-squared fit map using mono-exponential fit
        across subvolumes acquired at different echo times.

        :math:`T_2^* map is also added to the tissue.

        Args:
            tissue (Tissue): Tissue to generate quantitative value for.
            mask_path (:obj:`str`, optional): File path to mask of ROI to analyze.
                If specified, only voxels specified by mask will be fit.
                This can considerably speed up computation.
            num_workers (int, optional): Number of subprocesses to use for fitting.
                If `0`, will execute on the main thread.

        Returns:
            qv.T2Star: :math:`T_2^* fit for tissue.

        Raises:
            ValueError: If ``mask_path`` corresponds to non-binary volume.
        """
        # only calculate for focused region if a mask is available, this speeds up computation
        mask = tissue.get_mask()
        if mask_path is not None:
            mask = (fio_utils.generic_load(mask_path, expected_num_volumes=1)
                    if isinstance(mask_path,
                                  (str, os.PathLike)) else mask_path)

        spin_lock_times = self.echo_times
        subvolumes_list = self.volumes

        mef = MonoExponentialFit(
            bounds=(__T2_STAR_LOWER_BOUND__, __T2_STAR_UPPER_BOUND__),
            tc0="polyfit",
            decimal_precision=__T2_STAR_DECIMAL_PRECISION__,
            num_workers=num_workers,
            verbose=True,
        )

        t2star_map, r2 = mef.fit(spin_lock_times, subvolumes_list, mask=mask)

        quant_val_map = qv.T2Star(t2star_map)
        quant_val_map.add_additional_volume("r2", r2)

        tissue.add_quantitative_value(quant_val_map)

        return quant_val_map
Ejemplo n.º 5
0
    def generate_t2_star_map(self, tissue: Tissue, mask_path: str = None):
        """Generate 3D T2-star map and r-squared fit map using mono-exponential fit across subvolumes acquired at
            different echo times.

        T2-star map is also added to the tissue.

        Args:
            tissue (Tissue): Tissue to generate quantitative value for.
            mask_path (:obj:`str`, optional): File path to mask of ROI to analyze. If specified, only voxels specified
                by mask will be fit. Speeds up computation. Defaults to `None`.

        Returns:
            qv.T2Star: T2-star fit for tissue.

        Raises:
            ValueError: If `mask_path` specifies volume with values other than `0` or `1` (i.e. not binary).
        """
        # only calculate for focused region if a mask is available, this speeds up computation
        mask = tissue.get_mask()
        if (not mask or np.sum(mask.volume) == 0) and mask_path:
            mask = fio_utils.generic_load(mask_path, expected_num_volumes=1)
            if tuple(np.unique(mask.volume)) != (0, 1):
                raise ValueError(
                    "`mask_path` must reference binary segmentation volume")

        spin_lock_times = []
        subvolumes_list = []

        for echo_time in self.subvolumes.keys():
            spin_lock_times.append(echo_time)
            subvolumes_list.append(self.subvolumes[echo_time])

        mef = MonoExponentialFit(
            spin_lock_times,
            subvolumes_list,
            mask=mask,
            bounds=(__T2_STAR_LOWER_BOUND__, __T2_STAR_UPPER_BOUND__),
            tc0=__INITIAL_T2_STAR_VAL__,
            decimal_precision=__T2_STAR_DECIMAL_PRECISION__)

        t2star_map, r2 = mef.fit()

        quant_val_map = qv.T2Star(t2star_map)
        quant_val_map.add_additional_volume("r2", r2)

        tissue.add_quantitative_value(quant_val_map)

        return quant_val_map
Ejemplo n.º 6
0
    def __fitting_helper(
        self,
        qv_type: QuantitativeValueType,
        echo_inds: Sequence[int],
        tissue: Tissue,
        bounds,
        tc0,
        decimal_precision,
        mask_path,
        num_workers,
    ):
        echo_info = [(self.echo_times[i], self.volumes[i]) for i in echo_inds]

        # sort by echo time
        echo_info = sorted(echo_info, key=lambda x: x[0])

        xs = [et for et, _ in echo_info]
        ys = [vol for _, vol in echo_info]

        # only calculate for focused region if a mask is available, this speeds up computation
        mask = tissue.get_mask()
        if mask_path is not None:
            mask = (fio_utils.generic_load(mask_path, expected_num_volumes=1)
                    if isinstance(mask_path,
                                  (str, os.PathLike)) else mask_path)

        mef = MonoExponentialFit(
            bounds=bounds,
            tc0=tc0,
            decimal_precision=decimal_precision,
            num_workers=num_workers,
            verbose=True,
        )
        qv_map, r2 = mef.fit(xs, ys, mask=mask)

        quant_val_map = qv_type(qv_map)
        quant_val_map.add_additional_volume("r2", r2)

        tissue.add_quantitative_value(quant_val_map)

        return quant_val_map
Ejemplo n.º 7
0
    def segment(self, model: SegModel, tissue: Tissue, use_rss: bool = False):
        """Segment tissue in scan.

        Args:
            model (SegModel): Model to use for segmenting scans.
            tissue (Tissue): The tissue to segment.
            use_rss (`bool`, optional): If ``True``, use root-sum-of-squares (RSS) of
                echos for segmentation (preferred for built-in methods).
                If ``False``, use first echo for segmentation.

        Returns:
            MedicalVolume: Binary mask for segmented region.
        """
        tissue_names = (
            ", ".join([t.FULL_NAME for t in tissue])
            if isinstance(tissue, Sequence)
            else tissue.FULL_NAME
        )
        logging.info(f"Segmenting {tissue_names}...")

        if use_rss:
            segmentation_volume = self.calc_rss()
        else:
            # Use first echo for segmentation.
            segmentation_volume = self.volumes[0]

        # Segment tissue and add it to list.
        mask = model.generate_mask(segmentation_volume)
        if isinstance(mask, dict):
            if not isinstance(tissue, Sequence):
                tissue = [tissue]
            for abbreviation, tis in zip([t.STR_ID for t in tissue], tissue):
                tis.set_mask(mask[abbreviation])
                self.__add_tissue__(tis)
        else:
            assert isinstance(tissue, Tissue)
            tissue.set_mask(mask)
            self.__add_tissue__(tissue)

        return mask
Ejemplo n.º 8
0
def handle_segmentation(vargin, scan: ScanSequence, tissue: Tissue):
    if not vargin[SEGMENTATION_MODEL_KEY] and not vargin[SEGMENTATION_CONFIG_KEY]:
        raise ValueError(
            "Either `--{}` or `--{}` must be specified".format(
                SEGMENTATION_MODEL_KEY, SEGMENTATION_CONFIG_KEY
            )
        )

    segment_weights_path = vargin[SEGMENTATION_WEIGHTS_DIR_KEY][0]
    if isinstance(tissue, Sequence):
        weights = [t.find_weights(segment_weights_path) for t in tissue]
        assert all(weights_file == weights[0] for weights_file in weights)
        weights_path = weights[0]
    else:
        weights_path = tissue.find_weights(segment_weights_path)

    # Load model
    dims = scan.get_dimensions()
    # TODO: Input shape should be determined by combination of model + scan.
    # Currently fixed in 2D plane
    input_shape = (dims[0], dims[1], 1)
    if vargin[SEGMENTATION_MODEL_KEY]:
        # Use built-in model
        model = get_model(
            vargin[SEGMENTATION_MODEL_KEY], input_shape=input_shape, weights_path=weights_path
        )
    else:
        # Use config
        model = model_from_config(
            vargin[SEGMENTATION_CONFIG_KEY],
            weights_dir=segment_weights_path,
            input_shape=input_shape,
        )
    model.batch_size = vargin[SEGMENTATION_BATCH_SIZE_KEY]

    return model
Ejemplo n.º 9
0
    def generate_t2_map(
        self,
        tissue: Tissue = None,
        suppress_fat: bool = False,
        suppress_fluid: bool = False,
        beta: float = 1.2,
        gl_area: float = None,
        tg: float = None,
        tr: float = None,
        te: float = None,
        alpha: float = None,
        diffusivity: float = 1.25e-9,
        t1: float = None,
        nan_bounds: Tuple[float, float] = (0, 100),
        nan_to_num: float = 0.0,
        decimals: int = 1,
    ):
        """Generate 3D T2 map.

        Spoiler amplitude (``gl_area``) and duration (``tg``) must be specified if dicom header
        does not contain relevant private tags. If dicom header is unavailable, ``tr``, ``te``,
        and ``alpha`` must also be specified.

        All array-like arguments must be the same dimensions as the echo 1 and echo 2 volumes.

        Args:
            tissue (Tissue, optional): Tissue to generate T2 map for.
                If provided, the resulting quantitative value map will
                be added to the list of quantitative values for the tissue.
            suppress_fat (`bool`, optional): Suppress fat region in T2 computation.
                This can help reduce noise.
            suppress_fluid (`bool`, optional): Suppress fluid region in T2 computation.
                Fluid-nulled image is calculated as ``S1 - beta*S2``.
            beta (float, optional): Beta value used for suppressing fluid.
            gl_area (float, optional): Spoiler area.
                Defaults to value in dicom private tag '0x001910b6'.
                Required if dicom header unavailable or private tag missing.
            tg (float, optional): Spoiler duration (in microseconds).
                Defaults to value in dicom private tag ``0x001910b7``.
                Required if dicom header unavailable or private tag missing.
            tr (float, optional): Repitition time (in milliseconds).
                Required if dicom header unavailable.
            te (float, optional): Echo time (in milliseconds).
                Required if dicom header unavailable.
            alpha (float or array-like): Flip angle in degrees.
                Required if dicom header unavailable.
            diffusivity (float or array-like): Estimated diffusivity.
            t1 (float or array-like): Estimated t1 in milliseconds.
                Defaults to ``tissue.T1_EXPECTED`` if ``tissue`` provided.
            nan_bounds (float or Tuple[float, float], optional): The closed interval
                (``[a, b]``) for valid t2 values. All values outside of this interval will
                be set to ``nan``.
            nan_to_num (float): Value to be used to fill NaN values. If ``None``, values
                will not be replaced.
            decimals (int): Number of decimal places to round to. If ``None``, values
                will not be rounded.

        Returns:
            qv.T2: T2 fit map.
        """

        if self.volumes is None or self.ref_dicom is None:
            raise ValueError("volumes and ref_dicom fields must be initialized")

        if (
            self.get_metadata(self.__GL_AREA_TAG__, gl_area) is None
            or self.get_metadata(self.__TG_TAG__, tg) is None
        ):
            raise ValueError(
                "Dicom headers do not contain tags for `gl_area` and `tg`. Please input manually"
            )

        xp = self.volumes[0].device.xp
        ref_dicom = self.ref_dicom

        r, c, num_slices = self.volumes[0].volume.shape
        subvolumes = self.volumes

        # Split echos
        echo_1 = subvolumes[0].volume
        echo_2 = subvolumes[1].volume

        # All timing in seconds
        TR = (float(ref_dicom.RepetitionTime) if tr is None else tr) * 1e-3
        TE = (float(ref_dicom.EchoTime) if te is None else te) * 1e-3
        Tg = (float(ref_dicom[self.__TG_TAG__].value) if tg is None else tg) * 1e-6
        T1 = (float(tissue.T1_EXPECTED) if t1 is None else t1) * 1e-3

        # Flip Angle (degree -> radians)
        alpha = float(ref_dicom.FlipAngle) if alpha is None else alpha
        alpha = math.radians(alpha)
        if np.allclose(math.sin(alpha / 2), 0):
            warnings.warn("sin(flip angle) is close to 0 - t2 map may fail.")

        GlArea = float(ref_dicom[self.__GL_AREA_TAG__].value) if gl_area is None else gl_area

        Gl = GlArea / (Tg * 1e6) * 100
        gamma = 4258 * 2 * math.pi  # Gamma, Rad / (G * s).
        dkL = gamma * Gl * Tg

        # Simply math
        k = (
            math.pow((math.sin(alpha / 2)), 2)
            * (1 + math.exp(-TR / T1 - TR * math.pow(dkL, 2) * diffusivity))
            / (1 - math.cos(alpha) * math.exp(-TR / T1 - TR * math.pow(dkL, 2) * diffusivity))
        )

        c1 = (TR - Tg / 3) * (math.pow(dkL, 2)) * diffusivity

        # T2 fit
        mask = xp.ones([r, c, num_slices])

        ratio = mask * echo_2 / echo_1
        ratio = xp.nan_to_num(ratio)

        # have to divide division into steps to avoid overflow error
        t2map = -2000 * (TR - TE) / (xp.log(abs(ratio) / k) + c1)

        t2map = xp.nan_to_num(t2map)

        # Filter calculated T2 values that are below 0ms and over 100ms
        if nan_bounds is not None:
            lower, upper = nan_bounds
            t2map[(t2map < lower) | (t2map > upper)] = xp.nan
        if nan_to_num is not None:
            t2map = xp.nan_to_num(t2map, nan=nan_to_num)

        if decimals is not None:
            t2map = xp.around(t2map, decimals)

        if suppress_fat:
            t2map = t2map * (echo_1 > 0.15 * xp.max(echo_1))

        if suppress_fluid:
            vol_null_fluid = echo_1 - beta * echo_2
            t2map = t2map * (vol_null_fluid > 0.1 * xp.max(vol_null_fluid))

        t2_map_wrapped = subvolumes[0]._partial_clone(volume=t2map, headers=True)
        t2_map_wrapped = T2(t2_map_wrapped)

        if tissue is not None:
            tissue.add_quantitative_value(t2_map_wrapped)

        return t2_map_wrapped
Ejemplo n.º 10
0
    def generate_t2_map(self,
                        tissue: Tissue,
                        suppress_fat: bool = False,
                        suppress_fluid: bool = False,
                        beta: float = 1.2,
                        gl_area: float = None,
                        tg: float = None):
        """Generate 3D T2 map.

        Method is detailed in this `paper <https://www.ncbi.nlm.nih.gov/pubmed/28017730>`_.

        Args:
            tissue (Tissue): Tissue to generate T2 map for.
            suppress_fat (`bool`, optional): Suppress fat region in T2 computation. Helps reduce noise.
            suppress_fluid (`bool`, optional): Suppress fluid region in T2 computation. Fluid-nulled image is calculated
                as `S1 - beta*S2`.
            beta (`float`, optional): Beta value used for suppressing fluid. Defaults to 1.2.
            gl_area (`float`, optional): GL Area. Required if not provided in the dicom. Defaults to value in dicom
                tag '0x001910b6'.
            tg: tg value (in microseconds). Required if not provided in the dicom. Defaults to value in dicom tag
                '0x001910b7'.

        Returns:
            qv.T2: T2 fit for tissue.
        """

        if not self.__validate_scan__() and (not gl_area or not tg):
            raise ValueError(
                'dicoms in \'%s\' do not contain GL_Area and Tg tags. Please input manually'
                % self.dicom_path)

        if self.volumes is None or self.ref_dicom is None:
            raise ValueError(
                'volumes and ref_dicom fields must be initialized')

        ref_dicom = self.ref_dicom

        r, c, num_slices = self.volumes[0].volume.shape
        subvolumes = self.volumes

        # Split echos
        echo_1 = subvolumes[0].volume
        echo_2 = subvolumes[1].volume

        # All timing in seconds
        TR = float(ref_dicom.RepetitionTime) * 1e-3
        TE = float(ref_dicom.EchoTime) * 1e-3
        Tg = tg * 1e-6 if tg else float(
            ref_dicom[self.__TG_TAG__].value) * 1e-6
        T1 = float(tissue.T1_EXPECTED) * 1e-3

        # Flip Angle (degree -> radians)
        alpha = math.radians(float(ref_dicom.FlipAngle))

        GlArea = gl_area if gl_area else float(
            ref_dicom[self.__GL_AREA_TAG__].value)

        Gl = GlArea / (Tg * 1e6) * 100
        gamma = 4258 * 2 * math.pi  # Gamma, Rad / (G * s).
        dkL = gamma * Gl * Tg

        # Simply math
        k = math.pow(
            (math.sin(alpha / 2)),
            2) * (1 + math.exp(-TR / T1 - TR * math.pow(dkL, 2) * self.__D__)
                  ) / (1 - math.cos(alpha) *
                       math.exp(-TR / T1 - TR * math.pow(dkL, 2) * self.__D__))

        c1 = (TR - Tg / 3) * (math.pow(dkL, 2)) * self.__D__

        # T2 fit
        mask = np.ones([r, c, num_slices])

        ratio = mask * echo_2 / echo_1
        ratio = np.nan_to_num(ratio)

        # have to divide division into steps to avoid overflow error
        t2map = (-2000 * (TR - TE) / (np.log(abs(ratio) / k) + c1))

        t2map = np.nan_to_num(t2map)

        # Filter calculated T2 values that are below 0ms and over 100ms
        t2map[t2map <= self.__T2_LOWER_BOUND__] = np.nan
        t2map = np.nan_to_num(t2map)
        t2map[t2map > self.__T2_UPPER_BOUND__] = np.nan
        t2map = np.nan_to_num(t2map)

        t2map = np.around(t2map, self.__T2_DECIMAL_PRECISION__)

        if suppress_fat:
            t2map = t2map * (echo_1 > 0.15 * np.max(echo_1))

        if suppress_fluid:
            vol_null_fluid = echo_1 - beta * echo_2
            t2map = t2map * (vol_null_fluid > 0.1 * np.max(vol_null_fluid))

        t2_map_wrapped = MedicalVolume(t2map,
                                       affine=subvolumes[0].affine,
                                       headers=deepcopy(subvolumes[0].headers))
        t2_map_wrapped = T2(t2_map_wrapped)

        tissue.add_quantitative_value(t2_map_wrapped)

        return t2_map_wrapped