TACFitter ============================================= .. py:class:: petpal.kinetic_modeling.tac_fitting.TACFitter(pTAC: numpy.ndarray, tTAC: numpy.ndarray, weights: Union[None, float, numpy.ndarray] = None, tcm_func: Callable = None, fit_bounds: Union[numpy.ndarray, None] = None, resample_num: int = 512, aif_fit_thresh_in_mins: float = 30.0, max_iters: int = 2500) Bases: :py:obj:`object` A class used for fitting Tissue Compartment Models(TCM) to Time Activity Curves (TAC). It facilitates and simplifies the curve fitting process of TCM functions to TAC data. The class takes in raw TAC data for the plasma and tissue as input, and provides numerous utility methods to prepare data, set up fitting parameters, and perform the curve fitting. The resample method ensures data is appropriate for curve fitting by interpolating the TAC data over a regular time grid, and includes a time=0 data-point to the TACs if necessary. The class provides multiple options for setting up weights for the curve fitting residuals and for providing initial guesses and setting up bounds for the fitting parameters of the TCM function. Allows fitting on the basis of various TCM functions like one-tissue compartment model (1TCM), 2TCM, and others. :ivar resample_times: Times at which TACs are resampled. :vartype resample_times: np.ndarray :ivar resampled_t_tac: Tissue TAC values resampled at these times. :vartype resampled_t_tac: np.ndarray :ivar p_tac_vals: Plasma TAC values used for feeding to TCM function. :vartype p_tac_vals: np.ndarray :ivar raw_t_tac: Raw TAC times for tissue, fed at initialization. :vartype raw_t_tac: np.ndarray :ivar weights: Weights for handling residuals during the optimization process. :vartype weights: np.ndarray :ivar tgt_tac_vals: Tissue TAC values to fit TCM model. :vartype tgt_tac_vals: np.ndarray :ivar fit_param_number: Number of fitting parameters in the TCM function. :vartype fit_param_number: int :ivar initial_guesses: Initial guesses for all the parameters for curve fitting. :vartype initial_guesses: np.ndarray :ivar bounds_hi: Upper bounds for all the parameters for curve fitting. :vartype bounds_hi: np.ndarray :ivar fit_results: The results of the fit, including optimized parameters and covariance matrix. :vartype fit_results: np.optimize.OptimizeResult :ivar fit_param_names: Names of fitting parameters in the TCM function. :vartype fit_param_names: List[str] :ivar raw_p_tac: Raw TAC times for plasma, fed at initialization. :vartype raw_p_tac: np.ndarray :ivar resampled_p_tac: Plasma TAC values resampled on these times. :vartype resampled_p_tac: np.ndarray :ivar sanitized_t_tac: Sanitized version of tissue TAC times. :vartype sanitized_t_tac: np.ndarray :ivar bounds_lo: Lower bounds for all the parameters for curve fitting. :vartype bounds_lo: np.ndarray :ivar bounds: Bounds for each parameter for curve fitting. :vartype bounds: np.ndarray :ivar max_func_evals: Maximum number of function evaluations (iterations) for the optimization process. :vartype max_func_evals: int :ivar tcm_func: The tissue compartment model (TCM) function to fit. :vartype tcm_func: Callable :ivar sanitized_p_tac: Sanitized version of plasma TAC times. :vartype sanitized_p_tac: np.ndarray :ivar delta_t: Delta between the newly created time steps in resampled times. :vartype delta_t: float .. rubric:: Example In the following quick example, ``tTAC`` represents a tissue TAC (``[times, values]``) and ``pTAC`` represents the input function (``[times, values]``). Furthermore, we want to fit the provided ``tTAC`` with a 2TCM. .. code-block:: python import petpal.kinetic_modeling.tcms_as_convolutions as pet_tcm import petpal.kinetic_modeling.tac_fitting as pet_fit import numpy as np tcm_func = pet_tcm.gen_tac_2tcm_cpet_from_tac fit = pet_fit.TACFitter(pTAC=pTAC, tTAC=tTAC, tcm_func=tcm_func, resample_num=512) fit.run_fit() fit_params = fit.fit_results[0] print(fit_params.round(3)) In the following example, we use an FDG input function from the module-provided data, and simulate a noisy 1TCM TAC and fit it -- showing a plot of everything at the end. .. plot:: :include-source: :caption: Fitting a noisy simulated 1TCM TAC. import numpy as np import petpal.kinetic_modeling.tcms_as_convolutions as pet_tcm import petpal.kinetic_modeling.tac_fitting as pet_fit import matplotlib.pyplot as plt import petpal.utils.testing_utils as pet_tst import petpal.visualizations.tac_plots as tac_plots tcm_func = pet_tcm.gen_tac_1tcm_cpet_from_tac pTAC = np.asarray(np.loadtxt('../../../../../data/tcm_tacs/fdg_plasma_clamp_evenly_resampled.txt').T) tTAC = tcm_func(*pTAC, k1=1.0, k2=0.25, vb=0.05) tTAC[1] = pet_tst.add_gaussian_noise_to_tac_based_on_max(tTAC[1]) fitter = pet_fit.TACFitter(pTAC=pTAC, tTAC=tTAC, tcm_func=tcm_func) fitter.run_fit() fit_params = fitter.fit_results[0] fit_tac = pet_tcm.gen_tac_1tcm_cpet_from_tac(*pTAC, *fit_params) plotter = tac_plots.TacFigure() plotter.add_tac(*pTAC, label='Input TAC', color='black', ls='--') plotter.add_tac(*tTAC, label='Tissue TAC', color='blue', ls='', marker='o', mec='k') plotter.add_tac(*fit_tac, label='Fit TAC', color='red', ls='-', marker='', lw=2.5) plt.legend() plt.show() .. seealso:: * :class:`TACFitterWithoutBloodVolume` to assume :math:`V_B=0` and only fit the kinetic parameters. Initialize TACFitter with provided arguments. The init function performs several important operations: 1. It sets the maximum number of function evaluations (iterations) for the optimization process. 2. It sets the TCM function properties and initial bounds with the provided TCM function and fit bounds. 3. It loads the raw tissue and plasma TACs and then resamples them evenly over a regular time grid. 4. It determines the weights to be used for handling residuals during the optimization process. 5. It sets the plasma TAC values and tissue TAC values to fit the TCM model. :param pTAC: The plasma TAC, with the form ``[times, values]``. :type pTAC: np.ndarray :param tTAC: The tissue TAC to which we will fit a TCM, with the form ``[times, values]``. :type tTAC: np.ndarray :param weights: Weights for handling residuals during the optimization process. If None, all residuals are equally weighted. Defaults to None. :type weights: float, np.ndarray or None, optional :param tcm_func: The specific TCM function to be used for fitting. Defaults to None. :type tcm_func: Callable, optional :param fit_bounds: Bounds for each parameter for curve fitting. If None, they will be guessed. Defaults to None. :type fit_bounds: np.ndarray or None, optional :param resample_num: The number of time points used when resampling TAC data. Defaults to 512. :type resample_num: int, optional :param aif_fit_thresh_in_mins: The threshold in minutes when resampling. Defaults to 30.0. :type aif_fit_thresh_in_mins: float, optional :param max_iters: Maximum number of function evaluations (iterations) for the optimization process. Defaults to 2500. :type max_iters: int, optional .. py:method:: _validate_inputs(input_tac: numpy.ndarray, roi_tac: numpy.ndarray, tcm_func: Callable) .. py:method:: _setup_bounds(fit_bounds: numpy.ndarray | None) -> numpy.ndarray .. py:method:: resample_tacs_evenly(fit_thresh_in_mins: float, resample_num: int) -> None Resample pTAC and tTAC evenly with respect to time, and at the same times. The method takes a threshold in minutes and a resample number as inputs. It starts by sanitizing the pTAC and tTAC (prepending a :math:`f(t=0)=0` point to data if necessary). A regularly sampled time is then generated using the start, end, and number of samples dictated by resample_num. Following this, an interpolation object is created using the :class:`petpal.blood_input.BloodInputFunction` class for the pTAC. This allows both interpolation and extrapolation for times beyond the pTAC onto the new tTAC times. Finally, the method resamples the sanitized tTAC and pTAC across these new evenly distributed times to ensure that they are regularly spaced over time. These resampled values are stored for future computations. The :math:`\Delta t` for the regularly sampled times is also stored. :param fit_thresh_in_mins: Threshold in minutes used for defining how to fit half of the pTAC. The fitting time threshold determines the point at which the pTAC switches from interpolation to fitting. It should be a positive float value. :type fit_thresh_in_mins: float :param resample_num: Number of samples to generate when resampling the tTAC. This will be the total number of samples in tTAC after it has been resampled. It should be a positive integer. :type resample_num: int :returns: None Side Effects: - sanitized_t_tac (np.ndarray): Sanitized version of the original tTAC given during class initialization. - sanitized_p_tac (np.ndarray): Sanitized version of the original pTAC given during class initialization. - resample_times (np.ndarray): Regularly sampled time points generated from the start and end of sanitized tTAC, and the passed resample_num. - delta_t (float): Delta between the newly created time steps in resample_times. - resampled_t_tac (np.ndarray): tTAC resampled at the time points defined in resample_times. - resampled_p_tac (np.ndarray): pTAC resampled and extrapolated (if necessary) at the time points defined in resample_times. .. seealso:: - :class:`petpal.blood_input.BloodInputFunction` .. py:method:: set_weights(weights: Union[float, str, None]) -> None Sets the weights for handling the residuals in the optimization process. The ``weights`` parameter determines how weights will be used: - It can be a float which will generate the weights based on an exponential decay formula. We assume that the passed in float is the decay constant, :math:`\lambda=\ln(2)/T_{1/2}`, where the half-life is in minutes. The weights are generated as: :math:`\sigma_i=\sqrt{e^{-\lambda t_i}C(t_i)}`, to be used as the ``sigma`` parameter for :func:`scipy.optimize.curve_fit`. - If it's a numpy array, the weights are linearly interpolated on the calculated `resample_times`. - If no specific value or an array is given, a numpy array of ones is used (i.e., it assumes equal weight). The method asserts that ``resampled_t_tac`` has been computed, thus :meth:`resample_tacs_evenly` method should be run before this. :param weights: Determines how weights will be computed. If a float, it is used as the exponential decay constant. If a numpy array, the provided weights are linearly interpolated on the calculated resampled times. If None, equal weights are assumed. :type weights: Union[float, str, None] :returns: None Side Effects: weights (np.ndarray): Sets the weights attribute of the class based on logical conditions. Either they are based on an exponential decay function, directly supplied, or assumed as equal weights. .. py:method:: sanitize_tac(tac_times_in_minutes: numpy.ndarray, tac_vals: numpy.ndarray) -> numpy.ndarray :staticmethod: Makes sure that the Time-Activity Curve (TAC) starts from time zero. The method ensures that the TAC starts from time zero by checking the first timestamp. If it's not zero, a zero timestamp and value are prepended, otherwise, the first value is set to zero. This method assumes that `tac_times_in_minutes` and `tac_vals` arrays have the same shape. :param tac_times_in_minutes: The original times of the TAC. :type tac_times_in_minutes: numpy.ndarray :param tac_vals: The original values of the TAC. :type tac_vals: numpy.ndarray :returns: *numpy.ndarray* -- The sanitized TAC: ``[sanitized_times, sanitized_vals]``. .. py:method:: resample_tac_on_new_times(tac_times_in_minutes: numpy.ndarray, tac_vals: numpy.ndarray, new_times: numpy.ndarray) -> numpy.ndarray :staticmethod: Resamples the Time-Activity Curve (TAC) on given new time points by linear interpolation. The method performs a linear interpolation of `tac_vals` on `new_times` based on `tac_times_in_minutes`. :param tac_times_in_minutes: The original times of the TAC. :type tac_times_in_minutes: numpy.ndarray :param tac_vals: The original values of the TAC. :type tac_vals: numpy.ndarray :param new_times: The new times to resample the TAC on. :type new_times: numpy.ndarray :returns: *numpy.ndarray* -- The resampled TAC: the resampled times and values. ``[new_times, new_vals]``. .. seealso:: :func:`numpy.interp` .. py:method:: fitting_func(x: numpy.ndarray, *params) -> numpy.ndarray A wrapper function to fit the Tissue Compartment Model (TCM) using given parameters. It calculates the results of the TCM function with the given times and parameters using the resampled pTAC. :param x: The independent data (time-points for TAC) :type x: np.ndarray :param \*params: The parameters for the TCM function :returns: *np.ndarray* -- The values of the TCM function with the given parameters at the given x-values. .. py:method:: run_fit() -> None Runs the optimization/fitting process on the data, using previously defined function and parameters. This method runs the curve fitting process on the TAC data, starting with the initial guesses for the parameters and the preset bounds for each. ``fitting_func``, initial guesses and bounds should have been set prior to calling this method. Optimized fit results and fit covariances are stored in ``fit_results``. :returns: None Side Effects: - fit_results (OptimizeResult): The results of the fit, including optimized parameters and covariance matrix. Fitted values can be extracted using fit_results.x, among other available attributes (refer to :func:`scipy.optimize.curve_fit` documentation for more details).