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
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
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
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
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
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
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
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
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
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