Skip to content

Guidance for developers

TumorTwin is designed to be a foundation for you to build on top of and extend!

The core workflow in TumorTwin is as follows:

TumorTwin Workflow

Extending the package to your own use-case or research task could involve modifying one or more of these core features - For each we provide a brief discussion about how you might think about and approach development: 1. Simulating a new Patients 2. Modifying the PatientData object 3. Modifying or creating a new TumorGrowthModel 4. Introducing new treatment models 5. Experimenting with different Solvers 6. Experimenting with different Optimizers

Simulating a new Patient

We store information about each patient in a .json configuration file (HGG Example, TNBC Example)

These files contain all the treatment and imaging data (via pointers to image files) for a specific patient, and are designed to be loaded into their respective PatientData objects, via their .from_file() method. Under the hood, PatientData objects are pydantic data models, and we use their json parsing to perform data validation. Thus, you should make sure that your json file is compatible with the PatientData object you intend to use.

Filepaths in a patient configuration can be written out in full. As a convenience, we also support the use of an environment variable called image_dir to specify a parent directory for your medical imaging data. Any instance of {$image_dir} in a configuration file will be replaced by either the corresponding image_url variable in a .env file, or by the contents of the image_dir keyword argument specified when calling the .from_file() method. This convenience can allow different users to use the same configuration files by specifying an image_dir that is specific to their file system.

Modifying the PatientData object

If you want to modify or extend the data sources that feed in to a patient-specific digital twin (e.g., additional treatment parameters, additional imaging types, additional non-imaging data about the patient, etc.), then you will likely need to create a new PatientData object for your use-case.

We provide a BasePatientData class which provides a basic structure and some convenient helper functions. We also provide two examples that inherit from this base class: HGGPatientData and TNBCPatientData. We suggest studying these examples in order to understand how you might extend them to your needs.

Modifying or creating a new TumorGrowthModel

We provide a basic 3D reaction-diffusion model of tumor growth (for details see the Theory page).

This model inherits from a base TumorGrowth3D class. At it's core, the TumorGrowth3D interface defines an explicit ordinary differential equation (ODE) model. The forward(t, u) method evaluates the right-hand-side of the equation, i.e., the time derivative du/dt. In our reaction-diffusion example, the forward() method performs spatial discretization of the PDE model, and evaluates the time-derivative du/dt for the vector of voxel-wise cellularities, u.

To implement your own model, you might consider implementing your own subclass of TumorGrowthModel. The core task here is to implement the forward() method that evaluates the right-hand side of the equation for your particular model, leveraging whatever PatientData and parameters you wish to provide the model.

Note that this model will be used in the torchdiffeq solver, and as such it supports discrete events via the callback_step and callback_step_adjoint methods which allow you to modify the ODE solution at each timestep of the forward and backward passes, respectively (see torchdiffeq documentation for more details).

Important: if you wish to leverage pytorch-based automatic differentiation, you will need to ensure that your forward() method only leverages differentiable operations

Introducing new treatment models

Treatment effects are handled entirely within the TumorGrowthModel. Treatments effects that appear in the right-hand side of the model (i.e., they affect the du/dt term) can be implemented directly into the model's forward() method. Treatments that involve a discrete/instantaneous effect on the solution value can be integrated via the model's callback_step() method. Data specifying the treatment (e.g. dosages, times) would typically be provided in the patient configuration file, and passed into the model via the PatientData object, while parameters in the treatment model (e.g., treatment sensitivities) would appear directly in the model.

Experimenting with different Solver algorithms

We provide an interface to the torchdiffeq library via the TorchDiffEqSolver object. This allows you to use any of the solvers and solver options supported by torchdiffeq (see documentation here)).

If you wish to implement your own solver, you may wish to create a subclass of the ForwardSolver class and implement the solve() method.

Experimenting with different Optimizer strategies

A TumorGrowth3D model should be directly compatible with pytorch.optim Optimizer objects. This provides access to a variety of different optimization algorithms. Alternatively, you might wish to refer to our custom Levenberg-Marquardt optimizer to see how you might implement a custom optimizer.

Need further guidance?

If you are still unsure about how you might extend TumorTwin to suit your needs, feel free to reach out to one of the authors. The best way to do this is by opening a GitHub Issue.