Olfaction

Note

For API references of the complex plume tracking task, see the Advanced Olfaction page.

Olfaction is the sense of smell. Olfactory experience is simulated by calculating odor intensities at the locations of the antennae and maxillary palps. More precisely, this is accomplished by adding position sensors to the relevant body segments and calculating the distance between these sensors and the odor sources. Intensities are then emulated through a diffusion relationship.

To enable this calculation, the BaseArena has the following methods. The user does not have to specifically implement them if an odor is not enabled.

BaseArena.get_olfaction(sensor_pos: ndarray) ndarray

Get the odor intensity readings from the environment.

Parameters:
sensor_posnp.ndarray

The Cartesian coordinates of the antennae of the fly as a (n, 3) NumPy array where n is the number of sensors (usually n=4: 2 antennae + 2 maxillary palps), and the second dimension gives the coordinates in (x, y, z).

Returns:
np.ndarray

The odor intensity readings from the environment as a (k, n) NumPy array where k is the dimension of the odor signal and n is the number of odor sensors (usually n=4: 2 antennae + 2 maxillary palps).

BaseArena.odor_dimensions

The dimension of the odor signal. This can be used to emulate multiple monomolecular chemical concentrations or multiple composite odor intensities.

Returns:
int

The dimension of the odor space.

A useful implementation to refer to is the built-in OdorArena:

class flygym.arena.OdorArena(size: tuple[float, float] = (300, 300), friction: tuple[float, float, float] = (1, 0.005, 0.0001), num_sensors: int = 4, odor_source: ndarray = np.array([[10, 0, 0]]), peak_odor_intensity: ndarray = np.array([[1]]), diffuse_func: Callable = lambda x: ..., marker_colors: list[tuple[float, float, float, float]] | None = None, marker_size: float = 0.25)

Bases: BaseArena

Flat terrain with an odor source.

Parameters:
sizetuple[float, float], optional

The size of the arena in mm, by default (300, 300).

frictiontuple[float, float, float], optional

The sliding, torsional, and rolling friction coefficients of the ground, by default (1, 0.005, 0.0001).

num_sensorsint, optional

The number of odor sensors, by default 4: 2 antennae + 2 maxillary palps.

odor_sourcenp.ndarray, optional

The position of the odor source in (x, y, z) coordinates. The shape of the array is (n_sources, 3).

peak_odor_intensitynp.ndarray, optional

The peak intensity of the odor source. The shape of the array is (n_sources, n_dimensions). Note that the odor intensity can be multidimensional.

diffuse_funcCallable, optional

The function that, given a distance from the odor source, returns the relative intensity of the odor. By default, this is an inverse square relationship.

marker_colorslist[tuple[float, float, float, float]], optional

A list of n_sources RGBA values (each as a tuple) indicating the colors of the markers indicating the positions of the odor sources. The RGBA values should be given in the range [0, 1]. By default, the matplotlib color cycle is used.

marker_sizefloat, optional

The size of the odor source markers, by default 0.25.

Attributes:
root_elementmjcf.RootElement

The root MJCF element of the arena.

frictiontuple[float, float, float]

The sliding, torsional, and rolling friction coefficients of the ground, by default (1, 0.005, 0.0001)

num_sensorsint

The number of odor sensors, by default 4: 2 antennae + 2 maxillary palps.

odor_sourcenp.ndarray

The position of the odor source in (x, y, z) coordinates. The shape of the array is (n_sources, 3).

peak_odor_intensitynp.ndarray

The peak intensity of the odor source. The shape of the array is (n_sources, n_dimensions). Note that the odor intensity can be multidimensional.

num_odor_sourcesint

Number of odor sources.

odor_dimensionsint

The dimension of the odor signal.

diffuse_funcCallable

The function that, given a distance from the odor source, returns the relative intensity of the odor. By default, this is an inverse square relationship.

birdeye_camdm_control.mujoco.Camera

MuJoCo camera that gives a birdeye view of the arena.

birdeye_cam_zoomdm_control.mujoco.Camera

MuJoCo camera that gives a birdeye view of the arena, zoomed in toward the fly.

friction = (100.0, 0.005, 0.0001)
get_olfaction(antennae_pos: ndarray) ndarray

Notes

w = 4: number of sensors (2x antennae + 2x max. palps) 3: spatial dimensionality k: data dimensionality n: number of odor sources

Input - odor source position: [n, 3] Input - sensor positions: [w, 3] Input - peak intensity: [n, k] Input - diffusion function: f(dist)

Reshape sources to S = [n, k*, w*, 3] (* means repeated) Reshape sensor position to A = [n*, k*, w, 3] (* means repeated) Subtract, getting a Delta = [n, k, w, 3] array of rel difference Calculate Euclidean distance: D = [n, k, w]

Apply pre-integrated diffusion function: S = f(D) -> [n, k, w] Reshape peak intensities to P = [n, k, w*] Apply scaling: I = P * S -> [n, k, w] element wise

Output - Sum over the first axis: [k, w]

get_spawn_position(rel_pos: ndarray, rel_angle: ndarray) tuple[ndarray, ndarray]

Given a relative entity spawn position and orientation (as if it was a simple flat terrain), return the adjusted position and orientation. This is useful for environments that have complex terrain (e.g. with obstacles) where the entity’s spawn position needs to be shifted accordingly.

For example, if the arena has flat terrain, this method can simply return rel_pos, rel_angle unchanged (as is the case by default). If there is are features on the ground that are 0.1 mm in height, then this method should return rel_pos + [0, 0, 0.1], rel_angle.

Parameters:
rel_posnp.ndarray

(x, y, z) position of the entity in mm as supplied by the user (before any transformation).

rel_anglenp.ndarray

Euler angle (rotation along x, y, z in radian) of the fly’s orientation as supplied by the user (before any transformation).

Returns:
np.ndarray

Adjusted (x, y, z) position of the entity.

np.ndarray

Adjusted euler angles (rotations along x, y, z in radian) of the fly’s orientation.

init_lights()
property odor_dimensions: int

The dimension of the odor signal. This can be used to emulate multiple monomolecular chemical concentrations or multiple composite odor intensities.

Returns:
int

The dimension of the odor space.

post_visual_render_hook(physics: dm_control.mjcf.Physics, *args, **kwargs) None

Make necessary changes (e.g. make certain visualization markers opaque) after rendering the visual inputs. By default, this does nothing.

pre_visual_render_hook(physics: dm_control.mjcf.Physics, *args, **kwargs) None

Make necessary changes (e.g. make certain visualization markers transparent) before rendering the visual inputs. By default, this does nothing.

spawn_entity(entity: Any, rel_pos: ndarray, rel_angle: ndarray) None

Add the fly to the arena.

Parameters:
entitymjcf.RootElement

The entity to be added to the arena (this should be the fly).

rel_posnp.ndarray

(x, y, z) position of the entity.

rel_anglenp.ndarray

euler angle representation (rot around x, y, z) of the entity’s orientation if it were spawned on a simple flat terrain.

step(dt: float, physics: dm_control.mjcf.Physics, *args, **kwargs) None

Advance the arena by one step. This is useful for interactive environments (e.g. moving object). Typically, this method is called from the core simulation class (e.g. NeuroMechFly).

Parameters:
dtfloat

The time step in seconds since the last update. Typically, this is the same as the time step of the physics simulation (provided that this method is called by the core simulation every time the simulation steps).

physicsmjcf.Physics

The physics object of the simulation. This is typically provided by the core simulation class (e.g. NeuroMechFly.physics) when the core simulation calls this method.

*args

User defined arguments and keyword arguments.

**kwargs

User defined arguments and keyword arguments.