Feature#

class deeptrack.features.Feature(_input: Any | None = None, **kwargs: Any)#

Bases: DeepTrackNode

Base feature class.

Features define the data generation and transformation process.

All features operate on lists of data, often lists of images. Most features, such as noise, apply a tranformation to all data in the list. The transformation can be additive, such as adding some Gaussian noise or a background illumination to images, or non-additive, such as introducing Poisson noise or performing a low-pass filter. The transformation is defined by the .get(data, **kwargs) method, which all implementations of the Feature class need to define. This method operates on a single data at a time.

Whenever a feature is initialized, it wraps all keyword arguments passed to the constructor as Property objects, and stores them in the .properties attribute as a PropertyDict.

When a feature is resolved, the current value of each property is sent as input to the .get() method.

Computational Backends and Data Types

The Feature class also provides mechanisms for managing numerical types and computational backends.

Supported backends include NumPy and PyTorch. The active backend is determined at initialization and stored in the ._backend attribute, which is used internally to control how computations are executed. The backend can be switched using the .numpy() and .torch() methods.

Numerical types used in computation (float, int, complex, and bool) can be configured using the .dtype() method. The chosen types are retrieved via the properties .float_dtype, .int_dtype, .complex_dtype, and .bool_dtype. These are resolved dynamically using the backend’s internal type resolution system and are used in downstream computations.

The computational device (e.g., “cpu” or a specific GPU) is managed through the .to() method and accessed via the .device property. This is especially relevant for PyTorch backends, which support GPU acceleration.

Parameters#

data: Any, optional

The input data for the feature. If left empty, no initial input is set. It is most commonly a NumPy array, a PyTorch tensor, or a list of NumPy arrays or PyTorch tensors; however, it can be anything.

**kwargs: Any

Keyword arguments to configure the feature. Each keyword argument is wrapped as a Property and added to the properties attribute, allowing dynamic sampling and parameterization during the feature’s execution. These properties are passed to the get() method when a feature is resolved.

Attributes#

properties: PropertyDict

A dictionary containing all keyword arguments passed to the constructor, wrapped as instances of Property. The properties can dynamically sampled values during pipeline execution. A sampled copy of this dictionary is passed to the .get() function and appended to the properties of the output.

_input: DeepTrackNode

A node representing the input data for the feature. It is most commonly a NumPy array, PyTorch tensor, or a list of NumPy arrays or PyTorch tensors; however, it can be anything. It supports lazy evaluation and graph traversal.

_random_seed: DeepTrackNode

A node representing the feature’s random seed. This allows for deterministic behavior when generating random elements, and ensures reproducibility during evaluation.

arguments: Feature or None

An optional feature whose properties are bound to this feature. This allows dynamic property sharing and centralized parameter management in complex pipelines.

__list_merge_strategy__: int

Specifies how the output of .get(data, **kwargs) is merged with the current _input. Options include: - MERGE_STRATEGY_OVERRIDE (0, default): _input is replaced by the

new output.

  • MERGE_STRATEGY_APPEND (1): The output is appended to the end of _input.

__distributed__: bool

Determines whether .get(image, **kwargs) is applied to each element of the input list independently (__distributed__ = True) or to the list as a whole (__distributed__ = False).

__conversion_table__: ConversionTable

Defines the unit conversions used by the feature to convert its properties into the desired units.

float_dtype: np.dtype

The data type of the float numbers.

int_dtype: np.dtype

The data type of the integer numbers.

complex_dtype: np.dtype

The data type of the complex numbers.

bool_dtype: np.dtype

The data type of the boolean numbers.

device: str or torch.device

The device on which the feature is executed.

_backend: “numpy” or “torch”

The computational backend.

Methods#

get(data, **kwargs) -> Any

Abstract method that defines how the feature transforms the input data. The input is most commonly a NumPy array or a PyTorch tensor, but it can be anything.

__call__(data_list, _ID, **kwargs) -> Any

Executes the feature or pipeline on the input and applies property overrides from kwargs.

resolve(data_list, _ID, **kwargs) -> Any

Alias of __call__().

to_sequential(**kwargs) -> Feature

Converts a feature to be resolved as a sequence.

torch(device, recursive) -> Feature

Sets the backend to PyTorch.

numpy(recursice) -> Feature

Sets the backend to NumPy.

get_backend() -> “numpy” or “torch”

Returns the current backend of the feature.

dtype(float, int, complex, bool) -> Feature

Sets the dtype to be used during evaluation.

to(device) -> Feature

Sets the device to be used during evaluation.

batch(batch_size) -> tuple

Batches the feature for repeated execution.

action(_ID) -> Any or list[Any]

Implements the core logic to create or transform the input(s).

update() -> Feature

Refreshes the feature to create a new output.

new(data_list, _ID, **kwargs) -> Any

Resets and recomputes the feature output.

add_feature(feature) -> Feature

Adds a feature to the dependency graph of this one.

seed(updated_seed, _ID) -> int

Sets the random seed for the feature, ensuring deterministic behavior.

bind_arguments(arguments) -> Feature

Binds another feature’s properties as arguments to this feature.

plot(input_image, resolve_kwargs, interval, **kwargs) -> Any

Visualizes the output of the feature when it is an image.

Private and internal methods. _normalize(**properties) -> dict[str, Any]

Normalizes the properties of the feature.

_process_properties(propertydict) -> dict[str, Any]

Preprocesses the input properties before calling the get method.

_format_input(data_list, **kwargs) -> list[Any]

Formats the input data for the feature.

_process_and_get(data_list, **kwargs) -> list[Any]

Calls the .get() method according to the __distributed__ attribute.

_activate_sources(x) -> None

Activates sources in the input data.

__getattr__(key) -> Any

Provides custom attribute access for the Feature class.

__iter__() -> Feature

Returns an iterator for the feature.

__next__() -> Any

Return the next element iterating over the feature.

__rshift__(other) -> Feature

Allows chaining of features.

__rrshift__(other) -> Feature

Allows right chaining of features.

__add__(other) -> Feature

Overrides add operator.

__radd__(other) -> Feature

Overrides right add operator.

__sub__(other) -> Feature

Overrides subtraction operator.

__rsub__(other) -> Feature

Overrides right subtraction operator.

__mul__(other) -> Feature

Overrides multiplication operator.

__rmul__(other) -> Feature

Overrides right multiplication operator.

__truediv__(other) -> Feature

Overrides division operator.

__rtruediv__(other) -> Feature

Overrides right division operator.

__floordiv__(other) -> Feature

Overrides floor division operator.

__rfloordiv__(other) -> Feature

Overrides right floor division operator.

__pow__(other) -> Feature

Overrides power operator.

__rpow__(other) -> Feature

Overrides right power operator.

__gt__(other) -> Feature

Overrides greater than operator.

__rgt__(other) -> Feature

Overrides right greater than operator.

__lt__(other) -> Feature

Overrides less than operator.

__rlt__(other) -> Feature

Overrides right less than operator.

__le__(other) -> Feature

Overrides less than or equal to operator.

__rle__(other) -> Feature

Overrides right less than or equal to operator.

__ge__(other) -> Feature

Overrides greater than or equal to operator.

__rge__(other) -> Feature

Overrides right greater than or equal to operator.

__xor__(other) -> Feature

Overrides XOR operator.

__and__(other) -> Feature

Overrides and operator.

__rand__(other) -> Feature

Overrides right and operator.

__getitem__(key) -> Feature

Allows direct slicing of the data.

Examples#

>>> import deeptrack as dt

Define and evaluate a simple feature

>>> import numpy as np
>>>
>>> feature = dt.Value(np.array([1, 2, 3]))
>>> result = feature()
>>> result
array([1, 2, 3])

Chain features using ‘>>’

>>> pipeline = dt.Value(np.array([1, 2, 3])) >> dt.Add(2)
>>> pipeline()
array([3, 4, 5])

Use arithmetic operators

>>> feature = dt.Value(np.array([1, 2, 3]))
>>> result = (feature + 1) * 2 - 1
>>> result()
array([3, 5, 7])

This is equivalent to chaining with Add, Multiply, and Subtract.

Evaluate a dynamic feature using `.update()` or `.new()`

>>> feature = dt.Value(lambda: np.random.rand())
>>> output1 = feature()
>>> output1
0.9938966963707441
>>> output2 = feature()  # Cached result
>>> output2
0.9938966963707441
>>> feature.update()
>>> output3 = feature()  # New sample
>>> output3
0.3874078815170007
>>> output4 = feature.new()  # Combine update and resolve
>>> output4
0.28477040978587476

Generate a batch of outputs

>>> feature = dt.Value(lambda: np.random.rand()) + 1
>>> batch = feature.batch(batch_size=3)
>>> batch
(array([1.6888222 , 1.88422131, 1.90027316]),)

Switch computational backend to torch

>>> import torch
>>>
>>> feature = dt.Add(b=5).torch()
>>> input_tensor = torch.tensor([1.0, 2.0])
>>> feature(input_tensor)
tensor([6., 7.])

Use `.seed()` for reproducibility

>>> feature = dt.Value(lambda: np.random.randint(0, 100))
>>> seed = feature.seed()
>>> v1 = feature.new()
>>> v1
76
>>> feature.seed(seed)
>>> v2 = feature.new()
>>> v2
76

Sequential feature with evolving property

>>> def rotate(sequence_length, previous_value):
...     return previous_value + 2 * np.pi / sequence_length
>>> rotating = dt.Ellipse(
...     position=(16, 16),
...     radius=(1.5e-6, 1e-6),
...     rotation=0,
... ).to_sequential(rotation=rotate)
>>> frames = dt.Sequence(rotating, sequence_length=5).update()
>>> images = frames()
>>> len(images)
5

Bind dynamic arguments across multiple features

>>> arguments = dt.Arguments(frequency=1, amplitude=2)
>>> wave = (
...     dt.Value(
...         value=lambda freq: np.linspace(0, 2 * np.pi * freq, 100),
...         freq=arguments.frequency,
...     )
...     >> np.sin
...     >> dt.Multiply(
...         b=lambda amp: amp,
...         amp=arguments.amplitude,
...     )
... )
>>> wave.bind_arguments(arguments)
>>> from matplotlib import pyplot as plt
>>>
>>> plt.plot(wave())
>>> plt.show()
>>> plt.plot(wave(frequency=2, amplitude=1))
>>> plt.show()

Attributes Summary

bool_dtype

The dtype of the boolean numbers.

complex_dtype

The dtype of the complex numbers.

device

The device to be used during evaluation.

float_dtype

The dtype of the float numbers.

int_dtype

The dtype of the integer numbers.

Methods Summary

__call__([data_list, _ID])

Execute the feature or pipeline.

add_feature(feature)

Add a feature to the dependecy graph of this one.

batch([batch_size])

Batch the feature.

bind_arguments(arguments)

Bind another feature’s properties as arguments to this feature.

dtype([float, int, complex, bool])

Set the dtypes to be used during evaluation.

get(data[, _ID])

Transform input data (abstract method).

get_backend()

Get the current backend of the feature.

new([data_list, _ID])

Reset and recompute the feature output for the given _ID.

numpy([recursive])

Set the backend to numpy.

plot([input_image, resolve_kwargs, interval])

Visualize the output of the feature.

resolve([data_list, _ID])

Execute the feature or pipeline.

seed([updated_seed, _ID])

Seed all random number generators for reproducibility.

to(device)

Set the device to be used during evaluation.

to_sequential(**kwargs)

Convert a feature to be resolved as a sequence.

torch([device, recursive])

Set the backend to torch.

update(**global_arguments)

Refresh the feature to generate a new output.

Attributes Documentation

bool_dtype#

The dtype of the boolean numbers.

complex_dtype#

The dtype of the complex numbers.

device#

The device to be used during evaluation.

float_dtype#

The dtype of the float numbers.

int_dtype#

The dtype of the integer numbers.

Methods Documentation

__call__(data_list: Any | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) Any#

Execute the feature or pipeline.

The .__call__() method executes the feature or pipeline on the provided input data and updates the computation graph if necessary. It overrides properties using the keyword arguments.

The actual computation is performed by calling the parent .__call__() method in the DeepTrackNode class, which manages lazy evaluation and caching.

Parameters#

data_list: Any, optional

The input data to the feature or pipeline. It is most commonly a list of NumPy arrays or PyTorch tensors, but it can be anything. Defaults to None, in which case the feature uses the previous set of input values or propagates properties.

**kwargs: Any

Additional parameters passed to the pipeline. These override properties with matching names. For example, calling feature(x, value=4) executes feature on the input x while setting the property value to 4. All features in a pipeline are affected by these overrides.

Returns#

Any

The output of the feature or pipeline after execution. This is typically a list of NumPy arrays or PyTorch tensors, but it can be anything.

Examples#

>>> import deeptrack as dt

Define a feature:

>>> feature = dt.Add(b=2)

Call this feature with an input:

>>> import numpy as np
>>>
>>> feature(np.array([1, 2, 3]))
array([3, 4, 5])

Execute the feature with previously set input:

>>> feature()  # Uses stored input
array([3, 4, 5])

Execute the feature with new input:

>>> feature(np.array([10, 20, 30]))  # Uses new input
array([12, 22, 32])

Override a property:

>>> feature(np.array([10, 20, 30]), b=1)
array([11, 21, 31])
add_feature(feature: Feature) Feature#

Add a feature to the dependecy graph of this one.

This method establishes a dependency relationship by registering the provided feature as a dependency of the current feature. This ensures that its evaluation and property resolution are included in the current feature’s computation graph.

Internally, it calls feature.add_child(self), which automatically handles graph integration and triggers recomputation if necessary.

This is often used to define explicit data dependencies or to ensure side-effect features are computed when this feature is resolved.

Parameters#

feature: Feature

The feature to add as a dependency.

Returns#

Feature

The newly added feature (for chaining).

Examples#

>>> import deeptrack as dt

Define the main feature that adds a constant to the input:

>>> feature = dt.Add(b=2)

Define a side-effect feature:

>>> dependency = dt.Value(value=42)

Register the dependency so its state becomes part of the graph:

>>> feature.add_feature(dependency)

Execute the main feature on an input array:

>>> import numpy as np
>>>
>>> result = feature(np.array([1, 2, 3]))
>>> result
array([3, 4, 5])

Note that the dependency does not affect the result directly, but it will be tracked and updated as part of the pipeline’s evaluation graph. This can be useful if the dependency affects any parameters of the main feature.

batch(batch_size: int = 32) tuple#

Batch the feature.

This method produces a batch of outputs by repeatedly calling .new().

Parameters#

batch_size: int, optional

The number of times to sample or generate data. Defaults to 32.

Returns#

tuple

A tuple where each element corresponds to one component of the output. If the outputs are NumPy arrays or PyTorch tensors, each element is a stacked array.

Examples#

>>> import deeptrack as dt

Define a feature that adds a random value to a fixed array:

>>> import numpy as np
>>>
>>> feature = (
...     dt.Value(value=np.array([[-1, 1]]))
...     >> dt.Add(b=lambda: np.random.rand())
... )

Evaluate the feature once:

>>> output = feature()
>>> output
array([[-0.77378939,  1.22621061]])

Generate a batch of outputs:

>>> batch = feature.batch(batch_size=3)
>>> batch
(array([[-0.2375814 ,  1.7624186 ],
        [-0.65764878,  1.34235122],
        [-0.87449525,  1.12550475]]),)
bind_arguments(arguments: Arguments | Feature) Feature#

Bind another feature’s properties as arguments to this feature.

This method allows properties of arguments to be dynamically linked to this feature, enabling shared configurations across multiple features. It is commonly used in advanced feature pipelines.

This method is often used in combination with the Arguments feature, which provides a utility that helps manage and propagate feature arguments efficiently.

The values from arguments override the corresponding properties during feature evaluation (call-time), without permanently modifying the feature’s own properties.

Parameters#

arguments: Arguments or Feature

A feature whose properties will be used as call-time arguments for this feature. Typically an Arguments feature.

Returns#

Feature

The current feature instance with bound arguments for chaining.

Examples#

>>> import deeptrack as dt

Create an Arguments feature:

>>> arguments = dt.Arguments(scale=2.0)

Bind it with a pipeline:

>>> pipeline = dt.Value(value=3) >> dt.Add(b=1 * arguments.scale)
>>> pipeline.bind_arguments(arguments)
>>> result = pipeline()
>>> result
5.0

Override the argument dynamically:

>>> result = pipeline(scale=1.0)
>>> result
4.0

Without binding, overriding scale at call-time would have no effect, and the result would remain 5.0.

dtype(float: Literal['float32', 'float64', 'default'] | None = None, int: Literal['int16', 'int32', 'int64', 'default'] | None = None, complex: Literal['complex64', 'complex128', 'default'] | None = None, bool: Literal['bool', 'default'] | None = None) Feature#

Set the dtypes to be used during evaluation.

It alters the dtypes used for array creation, but does not automatically cast the type.

Parameters#

float: str, optional

The float dtype to set. Can be “float32”, “float64”, “default”, or None. Defaults to None.

int: str, optional

The int dtype to set. Can be “int16”, “int32”, “int64”, “default”, or None. Defaults to None.

complex: str, optional

The complex dtype to set. Can be “complex64”, “complex128”, “default”, or None. Defaults to None.

bool: str, optional

The bool dtype to set. Can be “bool”, “default”, or None. Defaults to None.

Returns#

Feature

self

Examples#

>>> import deeptrack as dt

Set float and int data types for a feature:

>>> feature = dt.Multiply(b=2)
>>> feature.dtype(float="float32", int="int16")
>>> feature.float_dtype
dtype('float32')
>>> feature.int_dtype
dtype('int16')

Use complex numbers in the feature:

>>> feature.dtype(complex="complex128")
>>> feature.complex_dtype
dtype('complex128')

Reset float dtype to default:

>>> feature.dtype(float="default")
>>> feature.float_dtype  # resolved from config
dtype('float64')  # Depends on backend config
get(data: Any, _ID: tuple[int, ...] = (), **kwargs: Any) Any#

Transform input data (abstract method).

Abstract method that defines how the feature transforms the input data. The current values of all properties are passed as keyword arguments.

Parameters#

data: Any

The input data to be transformed, most commonly a NumPy array or a PyTorch tensor, but it can be anything.

_ID: tuple[int, …], optional

The unique identifier for the current execution. Defaults to ().

**kwargs: Any

The current value of all properties in the properties attribute, as well as any global arguments passed to the feature.

Returns#

Any

The transformed data.

Raises#

NotImplementedError

Raised if this method is not overridden by subclasses.

get_backend() Literal['numpy', 'torch']#

Get the current backend of the feature.

Returns#

“numpy” or “torch”

The backend of this feature.

Examples#

>>> import deeptrack as dt

Create a feature:

>>> feature = dt.Add(b=5)

Set the feature’s backend to NumPy and check it:

>>> feature.numpy()
>>> feature.get_backend()
'numpy'

Set the feature’s backend to PyTorch and check it:

>>> feature.torch()
>>> feature.get_backend()
'torch'
new(data_list: Any | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) Any#

Reset and recompute the feature output for the given _ID.

This method invalidates the cached data (via .update()), then immediately evaluates the feature using the same input and keyword override semantics as .__call__().

Parameters#

data_list: Any, optional

The input data passed to .__call__(). Defaults to None.

_ID: tuple[int, …], optional

The identifier for which the value should be recomputed. Defaults to an empty tuple.

**kwargs: Any

Keyword arguments forwarded to .__call__(), overriding properties.

Returns#

Any

The newly computed output.

numpy(recursive: bool = True) Feature#

Set the backend to numpy.

The NumPy backend does not support non-CPU devices. Calling .numpy() resets the feature’s device to “cpu”.

Parameters#

recursive: bool, optional

If True (default), also converts all dependent features.

Returns#

Feature

self

Examples#

>>> import deeptrack as dt
>>> import numpy as np

Create a feature and ensure it uses the NumPy backend:

>>> feature = dt.Add(b=5)
>>> feature.numpy()

Evaluate the feature on a NumPy array:

>>> output = feature(np.array([1, 2, 3]))
>>> output
array([6, 7, 8])

Apply recursively in a pipeline:

>>> f1 = dt.Multiply(b=2)
>>> f2 = dt.Subtract(b=1)
>>> pipeline = f1 >> f2
>>> pipeline.numpy()
>>> output = pipeline(np.array([1, 2, 3]))
>>> output
array([1, 3, 5])
plot(input_image: ndarray | list[ndarray] | Tensor | list[Tensor] | None = None, resolve_kwargs: dict[str, Any] | None = None, interval: float | None = None, **kwargs: Any) Any#

Visualize the output of the feature.

The .plot() method resolves the feature and visualizes the result: - If the output is a single image (NumPy array or PyTorch tensor), it

is displayed using pyplot.imshow(). Any parameters in kwargs are passed to pyplot.imshow().

  • If the output is a list or a tuple, an animation is created. In Jupyter notebooks, the animation is played inline using .to_jshtml(). In scripts, the animation is displayed using the matplotlib backend.

Parameters#

input_image: array or tensor, or list[array] or list[tensor], optional

The input image or list of images passed as an argument to the .resolve() call. If None, uses previously set input values or propagates properties.

resolve_kwargs: dict[str, Any], optional

Additional keyword arguments passed to the .resolve() call.

interval: float, optional

The time between frames in the animation, in milliseconds. Defaults to ~33 ms (30 fps).

**kwargs: Any

Additional keyword arguments passed to pyplot.imshow().

Returns#

matplotlib.axes.Axes or matplotlib.animation.ArtistAnimation or Any

For single images, returns the current axes. For videos, returns the animation. In notebook fallback mode, may return an interactive widget.

Examples#

>>> import deeptrack as dt

Create an instance of a dummy feature that returns the input:

>>> feature = dt.DummyFeature()

Generate and plot a grayscale image:

>>> import numpy as np
>>>
>>> img = np.random.randint(0, 256, (64, 64))
>>> feature.plot(img, cmap="gray");

Generate and plot a grayscale video:

>>> video = [np.random.randint(0, 256, (64, 64)) for _ in range(10)]
>>> feature.plot(video, interval=100, cmap="gray");

Generate a grayscale image using torch and plot it:

>>> import torch
>>>
>>> img = torch.randint(0, 256, size=(64, 64))
>>> feature.plot(img, cmap="gray");

Generate a simulated image of a point particle visualized using brightfield microscopy and plot it:

>>> particle = dt.PointParticle(intensity=100)
>>> optics = dt.Fluorescence()
>>> imaged_particle = optics(particle)
>>> imaged_particle.plot(cmap="gray");
resolve(data_list: Any | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) Any#

Execute the feature or pipeline.

The .__call__() method executes the feature or pipeline on the provided input data and updates the computation graph if necessary. It overrides properties using the keyword arguments.

The actual computation is performed by calling the parent .__call__() method in the DeepTrackNode class, which manages lazy evaluation and caching.

Parameters#

data_list: Any, optional

The input data to the feature or pipeline. It is most commonly a list of NumPy arrays or PyTorch tensors, but it can be anything. Defaults to None, in which case the feature uses the previous set of input values or propagates properties.

**kwargs: Any

Additional parameters passed to the pipeline. These override properties with matching names. For example, calling feature(x, value=4) executes feature on the input x while setting the property value to 4. All features in a pipeline are affected by these overrides.

Returns#

Any

The output of the feature or pipeline after execution. This is typically a list of NumPy arrays or PyTorch tensors, but it can be anything.

Examples#

>>> import deeptrack as dt

Define a feature:

>>> feature = dt.Add(b=2)

Call this feature with an input:

>>> import numpy as np
>>>
>>> feature(np.array([1, 2, 3]))
array([3, 4, 5])

Execute the feature with previously set input:

>>> feature()  # Uses stored input
array([3, 4, 5])

Execute the feature with new input:

>>> feature(np.array([10, 20, 30]))  # Uses new input
array([12, 22, 32])

Override a property:

>>> feature(np.array([10, 20, 30]), b=1)
array([11, 21, 31])
seed(updated_seed: int | None = None, _ID: tuple[int, ...] = ()) int#

Seed all random number generators for reproducibility.

This method sets the global random seed for Python’s random module, NumPy, and (if available) PyTorch. If updated_seed is provided, it replaces the value of the internal _random_seed node before resolution.

This method sets the following: - random.seed(seed) for Python’s RNG - np.random.seed(seed) for NumPy - torch.manual_seed(seed) and torch.cuda.manual_seed_all(seed)

The same seed will lead to deterministic behavior within each backend (e.g., random, NumPy or PyTorch), but not across them. NumPy and PyTorch use different RNG algorithms, so identical seeds will not generate the same random numbers across backends.

Parameters#

updated_seed: int or None, optional

If provided, sets a fixed value for the internal _random_seed. Defaults to None.

_ID: tuple[int, …], optional

Unique identifier used to resolve the seed value. Defaults to ().

Returns#

int

The resolved seed value used for all RNGs.

Examples#

>>> import deeptrack as dt

Using `random`

Define a feature that samples a random integer from 0 to 10 using the Python standard library’s random module:

>>> import random
>>>
>>> feature = dt.Value(lambda: random.randint(0, 10))
>>>
>>> for _ in range(3):
...     print(f"output={feature.new()} seed={feature.seed()}")
output=3 seed=355549663
output=5 seed=119234165
output=9 seed=1956541335

Each time .update() is called, the internal _random_seed is re-sampled and used to reseed the Python random module. This produces a new deterministic seed, but different output values.

Fix the seed to reuse it later for reproducibility:

>>> seed = feature.seed()
>>> seed
1956541335

Now reseed the feature with the same value before each update, to make the output deterministic and repeatable.

>>> for _ in range(3):
...    feature.seed(seed)
...    print(f"output={feature.new()} seed={feature.seed()}")
output=5 seed=1933964715
output=5 seed=1933964715
output=5 seed=1933964715

Since the random seed is fixed before each sample, the output is the same every time. Note: the seed reported after sampling may differ if it’s re-sampled internally, but the output remains stable.

Using NumPy

Similar observations can be made with NumPy:

>>> import numpy as np
>>>
>>> feature = dt.Value(lambda: np.random.randint(0, 10))

Using PyTorch

Similar observations can be made with PyTorch:

>>> import torch
>>>
>>> feature = dt.Value(lambda: torch.randint(0, 10, (1,)).item())
to(device: str | device) Feature#

Set the device to be used during evaluation.

Parameters#

device: str or torch.device

The device to use. If the backend is numpy, this can only be “cpu”.

Returns#

Feature

self

Examples#

>>> import deeptrack as dt
>>> import torch

Create a feature and assign a device (for torch backend):

>>> feature = dt.Add(b=1)
>>> feature.torch()
>>> feature.to(torch.device("cpu"))
>>> feature.device
device(type='cpu')

Move the feature to GPU (if available):

>>> if torch.cuda.is_available():
...     feature.to(torch.device("cuda"))
...     feature.device
device(type='cuda')

Use Apple MPS device on Apple Silicon (if supported):

>>> if (torch.backends.mps.is_available()
...     and torch.backends.mps.is_built()):
...     feature.to(torch.device("mps"))
...     feature.device
device(type='mps')
to_sequential(**kwargs: Any) Feature#

Convert a feature to be resolved as a sequence.

Should be called on individual features, not combinations of features. All keyword arguments will be treated as sequential properties and will be passed to the parent feature.

If a property from the keyword argument already exists in the feature, the existing property will be used to initialize the passed property (that is, it will be used for the first timestep).

Parameters#

self: Feature

Feature to make sequential.

kwargs: Any

Keyword arguments mapping property names to sequential sampling rules.

Returns#

Feature

The feature itself (returned for chaining), now configured to resolve sequentially.

Examples#

>>> import deeptrack as dt

Sequentially evaluate a feature.

This example shows how to_sequential() can be used together with __distributed__ = False to create a feature that generates values over time, rather than transforming input data.

Define a feature that returns a position value and does not depend on any inputs:

>>> class PositionFeature(dt.Feature):
...     __distributed__ = False
...
...     def __init__(self, position, **kwargs):
...         super().__init__(position=position, **kwargs)
...
...     def get(self, input_list, position, **kwargs):
...         return position

Convert the position property into a sequential property that increments at each time step:

>>> feature = PositionFeature(position=0)
>>> feature.to_sequential(
...     position=lambda previous_value: 0
...     if previous_value is None
...     else previous_value + 1
... )

Wrap the feature in a Sequence and evaluate it:

>>> sequence = dt.Sequence(feature, sequence_length=5)
>>> sequence()
[0, 1, 2, 3, 4]

Sequentially evaluate a rotating ellipse.

Create the optics:

>>> optics = dt.Fluorescence(
...     NA=0.6,
...     magnification=10,
...     resolution=1e-6,
...     wavelength=633e-9,
...     output_region=(0, 0, 32, 32),
... )

Create the scatterer:

>>> ellipse = dt.Ellipse(
...     position_unit="pixel",
...     position=(16, 16),
...     intensity=1,
...     radius=(1.5e-6, 1e-6),
...     rotation=0,  # Initial rotation at time step 0
... )

Implement a function to increment the rotation:

>>> from numpy import pi
>>>
>>> def get_rotation(sequence_length, previous_value):
...     delta = 2 * pi / sequence_length
...     return previous_value + delta

Call to_sequential() to resolve the feature sequentially:

>>> rotating_ellipse = ellipse.to_sequential(rotation=get_rotation)

Image the scatterer with the optics:

>>> imaged_rotating_ellipse = optics(rotating_ellipse)

Encapsulate as a Sequence object and specify the sequence length:

>>> imaged_rotating_ellipse_sequence = dt.Sequence(
...     imaged_rotating_ellipse,
...     sequence_length=10,
... )

Finally observe the scatterer rotate:

>>> imaged_rotating_ellipse_sequence.update().plot();
torch(device: device | None = None, recursive: bool = True) Feature#

Set the backend to torch.

Parameters#

device: torch.device, optional

The device to use during evaluation (e.g. CPU, CUDA, or MPS). If provided, the feature’s device is updated via .to(device). Defaults to None.

recursive: bool, optional

If True (default), it also converts all dependent features. If False, it does not.

Returns#

Feature

self

Examples#

>>> import deeptrack as dt
>>> import torch

Create a feature and switch to the PyTorch backend:

>>> feature = dt.Multiply(b=2)
>>> feature.torch()

Call the feature on a torch tensor:

>>> input_tensor = torch.tensor([1.0, 2.0, 3.0])
>>> output = feature(input_tensor)
>>> output
tensor([2., 4., 6.])

Switch to GPU if available (CUDA):

>>> if torch.cuda.is_available():
...     device = torch.device("cuda")
...     feature.torch(device=device)
...     output = feature(torch.tensor([1.0, 2.0, 3.0], device=device))
...     output.device.type
'cuda'

Switch to GPU if available (MPS):

>>> if (torch.backends.mps.is_available()
...     and torch.backends.mps.is_built()):
...     device = torch.device("mps")
...     feature.torch(device=device)
...     output = feature(torch.tensor([1.0, 2.0, 3.0], device=device))
...     output.device.type
'mps'

Apply recursively in a pipeline:

>>> f1 = dt.Add(b=1)
>>> f2 = dt.Multiply(b=2)
>>> pipeline = f1 >> f2
>>> pipeline.torch()
>>> output = pipeline(torch.tensor([1.0, 2.0]))
>>> output
tensor([4., 6.])
update(**global_arguments: Any) Feature#

Refresh the feature to generate a new output.

By default, when a feature is called multiple times, it returns the same value, which is cached.

Calling .update() forces the feature to recompute and return a new value the next time it is evaluated.

Calling .new() is equivalent to calling .update() plus evaluation.

Parameters#

**global_arguments: Any

DEPRECATED. Has no effect. Previously used to inject values during update. Use Arguments or call-time overrides instead.

Returns#

Feature

The updated feature instance, ensuring the next evaluation produces a fresh result.

Examples#

>>> import deeptrack as dt

Create and resolve a feature:

>>> import numpy as np
>>>
>>> feature = dt.Value(lambda: np.random.rand())
>>> output1 = feature()
>>> output1
0.9173610765203623

When resolving it again, it returns the same value:

>>> output2 = feature()
>>> output2  # Same as before
0.9173610765203623

Using .update() forces re-evaluation when resolved:

>>> feature.update()  # Feature updated
>>> output3 = feature()
>>> output3
0.13917950359184617

Using .new() both updates and resolves the feature:

>>> output4 = feature.new()
>>> output4
0.006278518685428169