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 test_headers(self): x, y, b = _generate_monoexp_data((10, 10, 20)) t = 1 / np.abs(b) for idx, _y in enumerate(y): _y._headers = util.build_dummy_headers( (1, 1) + _y.shape[2:], fields={"StudyDescription": "Sample study", "EchoNumbers": idx}, ) fitter = MonoExponentialFit(decimal_precision=8) t_hat = fitter.fit(x, y)[0] assert np.allclose(t_hat.volume, t) assert t_hat.headers() is not None assert t_hat.headers().shape == (1, 1, 20) for h in t_hat.headers().flatten(): assert h.get("StudyDescription") == "Sample study"
def test_matches_monoexponential_fit(self): """Match functionality of ``MonoexponentialFit`` using ``CurveFitter``.""" x, y, _ = _generate_monoexp_data((10, 10, 20)) fitter = MonoExponentialFit(tc0=30.0, bounds=(0, 100), decimal_precision=8) t_hat_mef = fitter.fit(x, y)[0] fitter = CurveFitter( monoexponential, p0=(1.0, -1 / 30), out_ufuncs=[None, lambda x: 1 / np.abs(x)], out_bounds=(0, 100), nan_to_num=0, ) t_hat_cf = fitter.fit(x, y)[0][..., 1] t_hat_cf = np.round(t_hat_cf, decimals=8) assert np.allclose(t_hat_mef.volume, t_hat_cf.volume)
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 test_mask(self): x, y, b = _generate_monoexp_data((10, 10, 20)) mask_arr = np.random.rand(*y[0].shape) > 0.5 t = 1 / np.abs(b) mask = MedicalVolume(mask_arr, np.eye(4)) fitter = MonoExponentialFit(decimal_precision=8) t_hat = fitter.fit(x, y, mask)[0] mask = mask.volume assert np.allclose(t_hat.volume[mask != 0], t[mask != 0]) fitter2 = MonoExponentialFit(decimal_precision=8) t_hat2 = fitter2.fit(x, y, mask_arr)[0] assert np.allclose(t_hat2.volume, t_hat.volume) with self.assertWarns(UserWarning): fitter3 = MonoExponentialFit(mask=mask, decimal_precision=8) t_hat3 = fitter3.fit(x, y)[0] assert np.allclose(t_hat3.volume, t_hat.volume)
def test_polyfit_initialization(self): x, y, b = _generate_monoexp_data((10, 10, 20)) t = 1 / np.abs(b) fitter = MonoExponentialFit(tc0="polyfit", decimal_precision=8) t_hat = fitter.fit(x, y)[0] assert np.allclose(t_hat.volume, t) # Test fitting still works even if some values are 0. # The values will not be accurate, but other pixel values should be. x, y, b = _generate_monoexp_data((10, 10, 20)) t = 1 / np.abs(b) mask_arr = np.zeros(y[0].shape, dtype=np.bool) mask_arr[:5, :5] = 1 y[0][mask_arr] = 0 fitter = MonoExponentialFit(tc0="polyfit", decimal_precision=8) t_hat = fitter.fit(x, y)[0] assert np.allclose(t_hat.volume[mask_arr == 0], t[mask_arr == 0])
def test_basic(self): x, y, b = _generate_monoexp_data((10, 10, 20)) t = 1 / np.abs(b) fitter = MonoExponentialFit(decimal_precision=8) t_hat = fitter.fit(x, y)[0] assert np.allclose(t_hat.volume, t) with self.assertWarns(UserWarning): fitter = MonoExponentialFit(x, y, decimal_precision=8) t_hat = fitter.fit(x, y)[0] assert np.allclose(t_hat.volume, t) with self.assertRaises(ValueError): fitter = MonoExponentialFit(list(x) + [5], y) with self.assertRaises(TypeError): fitter = MonoExponentialFit(x, [_y.A for _y in y]) with self.assertRaises(ValueError): fitter = MonoExponentialFit(x, y, tc0="a value")