Skip to content

Commit

Permalink
Add instructions on implementing new kinetic models
Browse files Browse the repository at this point in the history
  • Loading branch information
bilgelm committed Aug 8, 2024
1 parent 8c9397f commit a91416e
Showing 1 changed file with 109 additions and 0 deletions.
109 changes: 109 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ For example, invoke the unit test suite like this:
$ nox --session=tests
```

The full test suite currently runs tests in Python 3.11 and 3.12.
You can also run a specific Python version only like this:

```console
$ nox -p=3.12
```

Unit tests are located in the _tests_ directory,
and are written using the [pytest] testing framework.

Expand Down Expand Up @@ -110,6 +117,108 @@ This will allow a chance to talk it over with the owners and validate your appro

[pull request]: https://github.com/bilgelm/dynamicpet/pulls

## Contributing a new kinetic model implementation to the toolbox

The `.py` file implementing your kinetic model should be located under the
`src/dynamicpet/kineticmodel/` directory.
`kineticmodel.py` in this directory implements the abstract base class that all
kinetic model implementations should inherit from:

```python
from .kineticmodel import KineticModel

class MyKineticModelImplementation(KineticModel):
...
```

Your kinetic model class needs to implement the following functions:

1. `get_param_names`

This is a class method that returns a list of the names of kinetic model
parameters.

```python
@classmethod
get_param_names(cls) -> list[str]:
return ["parameter1", "parameter2"]
```

In the `fit` function below, all model parameters generated
and stored in the kinetic model class should be listed here.

2. `fit`

This method performs kinetic model fitting (within `mask`, if provided)
and stores estimated parameters using the`set_parameter` function.

`mask` is a
1-D (for `TemporalMatrix` time activity curves, or TACs) or
3-D (for `TemporalImage` TACs)
`NumpyRealNumberArray` and should match the dimensions of the
`TemporalMatrix` or `TemporalImage` provided for the `tacs` attribute of
the kinetic model. For zero values in `mask`, the time activity curve of the
corresponding element of `tac` will be ignored when fitting the kinetic
model. When `mask = None`, all `tac` elements will be used.

This method does not return anything.

```python
from ..temporalobject.temporalmatrix import TemporalMatrix
from ..typing_utils import NumpyRealNumberArray

def fit(self, mask: NumpyRealNumberArray | None = None) -> None:
# perform kinetic model fitting here
tacs: TemporalMatrix = self.tacs.timeseries_in_mask(mask)

# in an actual implementation, you would estimate parameters
# in this toy example, we set param1 and param2 to all 1's and 2's
# (tacs.num_elements is the number of regions or voxels in tacs)
param1 = np.ones((tacs.num_elements, 1))
param2 = 2 * np.ones((tacs.num_elements, 1))

# save the parameters
self.set_parameter("parameter1", param1, mask)
self.set_parameter("parameter2", param2, mask)
```

3. `fitted_tacs`

This method computes fitted time activity curves using the estimated
parameters. The output of this method should match the dimensions and type
of TACs supplied (`TemporalImage` or `TemporalMatrix`).

This method is optional.

```python
from ..temporalobject.temporalimage import TemporalImage
from ..temporalobject.temporalimage import image_maker

def fitted_tacs(self) -> TemporalMatrix | TemporalImage:
"""Get fitted TACs based on estimated model parameters."""
# in an actual implementation, you would calculate fitted values from
# estimated parameters
# in this toy example, we just set it to an empty array
fitted_tacs_dataobj = np.empty_like(self.tacs.dataobj)

if isinstance(self.tacs, TemporalImage):
img = image_maker(fitted_tacs_dataobj, self.tacs.img)
ti = TemporalImage(img, self.tacs.frame_start, self.tacs.frame_duration)
return ti
else:
tm = TemporalMatrix(
fitted_tacs_dataobj, self.tacs.frame_start, self.tacs.frame_duration
)
return tm
```

You will also need to write tests with sufficient coverage to verify that your
implementation runs correctly.
For some examples, see `tests/test_kineticmodel.py`.

Finally, you'll need to edit the `kineticmodel` function in `__main__.py` so
that your model implementation can be executed using the command line interface.

<!-- github-only -->

[code of conduct]: CODE_OF_CONDUCT.md

0 comments on commit a91416e

Please sign in to comment.