TimeActivityCurve#

class petpal.utils.time_activity_curve.TimeActivityCurve#

Class to store time activity curve (TAC) data.

Variables:
  • times (np.ndarray) – Frame times for the TAC stored in an array.

  • activity (np.ndarray) – Activity values at each frame time stored in an array.

  • uncertainty (np.ndarray) – Uncertainty in the measurement of activity values stored in an array.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve

my_tac = TimeActivityCurve.from_tsv('/path/to/tac.tsv')
print(f"Frame times: {my_tac.times}")
print(f"Activity: {my_tac.activity}")
print(f"Uncertainty: {my_tac.uncertainty}")

my_tac.times = my_tac.times / 60  # convert time units to hours
my_tac.to_tsv(filename='/path/to/new_tac.tsv')  # save as new file
__len__() int#

Returns the number of time points in the time-activity curve (TAC).

This method provides the length of the times attribute, representing the number of discrete time points associated with the TAC.

Returns:

int – The number of time points in the TAC.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve

# Create a TimeActivityCurve object
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.2, 2.3, 3.4, 4.5])
)

# Get the number of time points
print(len(my_tac))  # Output: 4
__post_init__()#
validate_activity()#

Validates that the activity attribute is defined correctly.

self.activity must have the following properties: 1) It must exist and not be None 2) It must be a numpy array 3) It must have dtype float 4) It must be 1D

This function raises a ValueError if self.activity does not meet the first criteria, and attempts to coerce self.activity into a 1D, numeric numpy array with dtype float if criteria 2-4 are not met.

classmethod from_tsv(filename: str)#

Load an instance of TimeActivityCurve object from a TSV TAC file.

Reads a TSV file using safe_load_tac() and instantiates a TimeActivityCurve.

Parameters:

filename (str) – Path to the TSV TAC file.

Returns:

(TimeActivityCurve) – A TimeActivityCurve object loaded from a TSV TAC file.

property tac: numpy.ndarray#

Get the TAC array, not including uncertainties.

Returns:

(np.ndarray)

The TAC as a contiguous array, with the first index being time and the

second index being activity.

property tac_werr: numpy.ndarray#

Get the TAC array, including uncertainties.

Returns:

(np.ndarray)

The TAC as a contiguous array, with the first index being time and the

second index being activity, and the third index being uncertainty.

property times_in_mins: numpy.ndarray[float]#

Returns the TAC measured times in minutes. Validates values by checking if the final frame value is greater than 200: if so, then assumes values are in seconds and divides by 60.

to_tsv(filename: str, col_names: list[str] = None)#

Writes the TAC object to file, including measurement times, activity, and uncertainty.

Parameters:
  • filename (str) – Path to the file that will be written to.

  • col_names (list[str]) – Custom names for time, activity, and uncertainty columns respectively. See safe_write_tac(). Default None.

set_activity_non_negative() TimeActivityCurve#

Ensures that the time-activity curve (TAC) data is physically consistent.

This method modifies the TAC object in place by setting uncertainty to NaN and activity to 0 for time points where the activity values are negative. Such adjustments help maintain data consistency for downstream analysis.

Returns:

TimeActivityCurve – The updated TAC object with sanitized data.

Note

The method returns self to allow for .-chaining.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve

# Create a TimeActivityCurve object with negative activity
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.2, -2.3, 3.4, -4.5]),
    uncertainty=np.array([0.1, 0.2, 0.3, 0.4])
)

# Sanitize the TAC
my_tac.set_activity_non_negative()

print(my_tac.activity)  # Output: [1.2, 0.0, 3.4, 0.0]
print(my_tac.uncertainty)  # Output: [0.1, NaN, 0.3, NaN]
evenly_resampled_tac(num_samples: int = 8192) TimeActivityCurve#

Generates a time-activity curve (TAC) resampled at evenly spaced time points.

This method uses linear interpolation to recreate the TAC with a specified number of evenly spaced samples between the initial and final time points. The resulting TAC is sanitized to ensure physical consistency. Uses scipy.interpolate.interp1d for the interpolation with kind=='linear' and fill_value='extrapolate'.

Important

If the TAC will be used for convolution later, prefer powers of two for the number of samples.

Parameters:

num_samples (int, optional) – The number of time points in the resampled TAC. Must be greater than 2. Defaults to 4096.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with evenly spaced time points and resampled activity values.

Raises:

AssertionError – If num_samples is less than or equal to 2.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve

# Create a TimeActivityCurve object
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.0, 2.0, 3.0, 4.0])
)

# Resample the TAC with evenly spaced time points
resampled_tac = my_tac.evenly_resampled_tac(num_samples=10)

print(resampled_tac.times)  # Output: Array of 10 evenly spaced times
print(resampled_tac.activity)  # Output: Interpolated activity values
evenly_resampled_tac_given_dt(dt: float = 0.1 / 60.0) TimeActivityCurve#

Generates a time-activity curve (TAC) resampled at evenly spaced time intervals.

This method calculates the number of samples required to achieve the specified time interval (dt) and then resamples the TAC using linear interpolation. The resulting TAC is sanitized to ensure physical consistency.

Parameters:

dt (float, optional) – The desired time interval between consecutive resampled time points (in the same unit as times). Must be greater than 0. Defaults to 0.1 / 60.0 (approximately 0.00167).

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with evenly spaced time intervals and resampled activity values.

Raises:

AssertionError – If dt is less than or equal to 0.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve

# Create a TimeActivityCurve object
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.0, 2.0, 3.0, 4.0])
)

# Resample the TAC with a given time interval (dt)
resampled_tac = my_tac.evenly_resampled_tac_given_dt(dt=0.1)

print(resampled_tac.times)  # Output: Evenly spaced time points with interval dt
print(resampled_tac.activity)  # Output: Interpolated activity values
resampled_tac_on_times(new_times: numpy.ndarray) TimeActivityCurve#

Resamples the time-activity curve (TAC) on specified time points.

This method uses linear interpolation to compute activity values at the provided time points (new_times). The resulting TAC is sanitized to ensure physical consistency.

Parameters:

new_times (np.ndarray) – An array of time points where the TAC should be resampled. Must be a 1D array of monotonically increasing values.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with the specified time points and interpolated activity values.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve
import numpy as np

# Create a TimeActivityCurve object
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.0, 2.0, 3.0, 4.0])
)

# New time points for resampling
new_times = np.array([5, 15, 25])

# Resample TAC on new time points
resampled_tac = my_tac.resampled_tac_on_times(new_times=new_times)

print(resampled_tac.times)  # Output: [5, 15, 25]
print(resampled_tac.activity)  # Output: Interpolated activity values at [5, 15, 25]
add_zero_time_and_activity()#

Ensures the time-activity curve (TAC) starts at time 0 with zero activity.

If the first time point in the TAC is not 0.0, this method prepends a time point of 0.0 and assigns it an activity value of 0. The associated uncertainty for this time point is set to NaN. The method modifies the TAC in place and returns the updated instance.

Returns:

TimeActivityCurve – The updated TimeActivityCurve instance with 0.0 prepended to time, activity, and uncertainty arrays (if needed).

Example

from petpal.utils.time_activity_curve import TimeActivityCurve
import numpy as np

# Create a TimeActivityCurve object
my_tac = TimeActivityCurve(
    times=np.array([10, 20, 30]),
    activity=np.array([2.0, 3.0, 4.0]),
    uncertainty=np.array([0.1, 0.2, 0.3])
)

# Add 0 time and activity if missing
my_tac = my_tac.add_zero_time_and_activity()

print(my_tac.times)       # Output: [ 0, 10, 20, 30 ]
print(my_tac.activity)   # Output: [ 0, 2.0, 3.0, 4.0 ]
print(my_tac.uncertainty)  # Output: [ NaN, 0.1, 0.2, 0.3 ]
shifted_tac(shift_in_mins: float = 10.0 / 60.0, dt: float | None = None) TimeActivityCurve#

Returns a time-activity curve (TAC) shifted in time by a specified amount.

If the shift is positive, the TAC is left-shifted; if negative, the TAC is right-shifted. The shift can be applied at the existing time points of the TAC or at oversampled points, depending on the value of dt.

Parameters:
  • shift_in_mins (float, optional) – The amount (in minutes) to shift the TAC. Positive values indicate a left shift; negative values indicate a right shift. Defaults to 10.0 / 60.0 (10 seconds).

  • dt (float | None, optional) – The time interval for oversampling. If None, the TAC is resampled at its original time points. If a value is provided, the TAC is resampled at an evenly spaced interval of dt.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with the shifted activity values.

Raises:

AssertionError – If dt is equal to 0.

Example

# Left-shifting the TAC
shifted_tac = my_tac.shifted_tac(shift_in_mins=5.0 / 60.0)

# Right-shifting the TAC with oversampling
shifted_tac = my_tac.shifted_tac(shift_in_mins=-2.0 / 60.0, dt=0.1 / 60.0)
static left_shifted_tac(tac: TimeActivityCurve, shift_in_mins: float = 10.0 / 60.0, dt: float | None = 0.1 / 60.0) TimeActivityCurve#

Produces a TAC left-shifted by a specified amount of time.

The left shift moves activity values earlier in time and fills the trailing values with interpolated data. The shift can either be applied on the original time points or on a time grid determined by dt.

Parameters:
  • tac (TimeActivityCurve) – The original TAC to be shifted.

  • shift_in_mins (float, optional) – The amount (in minutes) to left-shift the TAC. Must be larger than 0. Defaults to 10.0 / 60.0 (10 seconds).

  • dt (float | None, optional) – The time interval for oversampling. If None, the TAC is resampled at its original time points. If a value is provided, the TAC is resampled at an evenly spaced interval of dt.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with the left-shifted activity values.

Raises:

AssertionError – If shift_in_mins is not greater than 0.

Example

# Perform a left shift on the TAC
shifted_tac = TimeActivityCurve.left_shifted_tac(tac=my_tac, shift_in_mins=5.0 / 60.0)

# Perform a left shift with oversampling
shifted_tac = TimeActivityCurve.left_shifted_tac(tac=my_tac, shift_in_mins=2.0 / 60.0, dt=0.05 / 60.0)
static right_shifted_tac(tac: TimeActivityCurve, shift_in_mins: float = 10.0 / 60.0, dt: float | None = 0.1 / 60.0) TimeActivityCurve#

Produces a TAC right-shifted by a specified amount of time.

The right shift moves activity values later in time and fills the leading values with interpolated data. The shift can either be applied on the original time points or on a time grid determined by dt.

Parameters:
  • tac (TimeActivityCurve) – The original TAC to be shifted.

  • shift_in_mins (float, optional) – The amount (in minutes) to right-shift the TAC. Must be larger than 0. Defaults to 10.0 / 60.0 (10 seconds).

  • dt (float | None, optional) – The time interval for oversampling. If None, the TAC is resampled at its original time points. If a value is provided, the TAC is resampled at an evenly spaced interval of dt.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with the right-shifted activity values.

Raises:

AssertionError – If shift_in_mins is not greater than 0.

Example

# Perform a right shift on the TAC
shifted_tac = TimeActivityCurve.right_shifted_tac(tac=my_tac, shift_in_mins=5.0 / 60.0)

# Perform a right shift with oversampling
shifted_tac = TimeActivityCurve.right_shifted_tac(tac=my_tac, shift_in_mins=2.0 / 60.0, dt=0.05 / 60.0)
static tac_dispersion(tac: TimeActivityCurve, disp_func: Callable[Ellipsis, numpy.ndarray], disp_kwargs: dict, num_samples: int = 4096)#

Applies a dispersion function to a time-activity curve (TAC) and returns the convolved TAC.

This method evaluates the specified dispersion function disp_func at supersampled time points. It performs convolution (using scipy.signal.convolve()) of the supersampled TAC with the dispersion function, and the result is sampled back at the original TAC time points to form the new convolved TAC.

Note

We perform the supersampling to ensure that the TACs are sampled evenly before performing the convolution. Convolving non-evenly sampled arrays produces nonsense values.

Parameters:
  • tac (TimeActivityCurve) – The original time-activity curve to be convolved.

  • disp_func (Callable[..., np.ndarray]) – The dispersion function to be applied. This function must accept an array of times as its first argument, followed by any additional arguments specified in disp_kwargs.

  • disp_kwargs (dict) – Additional keyword arguments to pass to disp_func.

  • num_samples (int, optional) – The number of evenly spaced samples for supersampling the TAC before convolution. Defaults to 4096.

Returns:

TimeActivityCurve – A new TimeActivityCurve instance with the convolved activity values, resampled at the original TAC time points.

Example

from petpal.utils.time_activity_curve import TimeActivityCurve
import numpy as np

# Define a sample dispersion function
def monoexponential_kernel(t:np.ndarray, tau:float) -> np.ndarray:
    return (1.0/tau) * np.exp(-t / tau)

# Original TAC
my_tac = TimeActivityCurve(
    times=np.array([0, 10, 20, 30]),
    activity=np.array([1.0, 2.0, 3.0, 4.0])
)

# Apply dispersion
convolved_tac = TimeActivityCurve.tac_dispersion(
    tac=my_tac,
    disp_func=monoexponential_kernel,
    disp_kwargs={'tau': 5},
    num_samples=1024
)

print(convolved_tac.times)     # Output: Original TAC time points
print(convolved_tac.activity)  # Output: Activity after applying dispersion

See also

  • evenly_resample_tac()

property contains_any_nan#

Return True if TAC has any NaN activity values.