Code Monkey home page Code Monkey logo

flygym's Introduction

Simulating embodied sensorimotor control with NeuroMechFly 2.0

Documentation | Preprint | Discussion Board

Python: 3.7–3.11 License: MPL 2.0 PyPI version Repo Size

overview_video

This repository contains the source code for FlyGym, the Python library for NeuroMechFly 2.0, a digital twin of the adult fruit fly Drosophila melanogaster that can see, smell, walk over challenging terrain, and interact with the environment (see our NeuroMechFly 2.0 paper).

NeuroMechFly consists of the following components:

  • Biomechanical model: The biomechanical model is based on a micro-CT scan of a real adult female fly (see our original NeuroMechFly publication). We have adjusted several body segments (in particular in the antennae) to better reflect the biological reality.
  • Vision: The fly has compound eyes consisting of individual units called ommatidia arranged on a hexagonal lattice. We have simulated the visual inputs on the fly’s retinas.
  • Olfaction: The fly has odor receptors in the antennae and the maxillary palps. We have simulated the odor inputs experienced by the fly by computing the odor/chemical intensity at these locations.
  • Hierarchical control: The fly’s Central Nervous System consists of the brain and the Ventral Nerve Cord (VNC), a hierarchy analogous to our brain-spinal cord organization. The user can build a two-part model — one handling brain-level sensory integration and decision making and one handling VNC-level motor control — with a interface between the two consisting of descending (brain-to-VNC) and ascending (VNC-to-brain) representations.
  • Leg adhesion: Insects have evolved specialized adhesive structures at the tips of the legs that enable locomotion vertical walls and overhanging ceilings. We have simulated these structures in our model. The mechanism by which the fly lifts the legs during locomotion despite adhesive forces is not well understood; to abstract this, adhesion can be turned on/off during leg stance/swing.
  • Mechanosensory feedback: The user has access to the joint angles, forces, and contact forces experienced by the fly.

NeuroMechFly formulates the control of the simulated fly as a partially observable Markov Decision Process (MDP) and implements the Gym interface. This allows the user to use a wide range of reinforcement learning algorithms to train the fly to perform tasks. The standardized interface also allows the user to easily implement their own premotor computation and/or sensory preprocessing processes.

This package is developed at the Neuroengineering Laboratory, EPFL.

Installation and dependencies

In brief:

pip install "flygym[mujoco]"

See our website for details, especially if you plan to install FlyGym in the developer mode (ie. if you plan to make changes to the code). Dependencies are specified in setup.py and will be installed automatically upon installation using pip. Installation should take no more than a few minutes. NeuroMechFly has been tested on Linux (Ubuntu 22.04.3) and macOS (13.5.2). All commits are tested automatically using the latest Ubuntu version on Python versions 3.7 through 3.11 (see CI workflow for exact test specification). The PyPI version of the current release of FlyGym is indicated on the shield at the top of this page. No special or paid software is required to use FlyGym.

Demos

See our website for tutorials, including expected outputs. For code blocks that take more than a few seconds to run, the running time (on a 2020 MacBook Pro with M1 processor running macOS 13.5.2) is indicated, typically in the form of a progress bar.

Reproducing results in the paper

We are constantly working on expanding the package and improving its usability; therefore the package is subject to change. To reproduce the exact results demonstrated in our preprint, use FlyGym 0.1.0 and analysis code here.

Citation

If you use NeuroMechFly in your work, please cite the following papers:

@article{WangChen2023,
  author = {Sibo Wang-Chen and Victor Alfred Stimpfling and Pembe Gizem \"{O}zdil and Louise Genoud and Femke Hurtak and Pavan Ramdya},
  title = {NeuroMechFly 2.0, a framework for simulating embodied sensorimotor control in adult Drosophila},
  year = {2023},
  doi = {10.1101/2023.09.18.556649},
  URL = {https://www.biorxiv.org/content/early/2023/09/18/2023.09.18.556649},
  journal = {bioRxiv}
}

@article{LobatoRios2022,
  doi = {10.1038/s41592-022-01466-7},
  url = {https://doi.org/10.1038/s41592-022-01466-7},
  year = {2022},
  month = may,
  volume = {19},
  number = {5},
  pages = {620--627},
  author = {Victor Lobato-Rios and Shravan Tata Ramalingasetty and Pembe Gizem \"{O}zdil and Jonathan Arreguit and Auke Jan Ijspeert and Pavan Ramdya},
  title = {{NeuroMechFly}, a neuromechanical model of adult {Drosophila} melanogaster},
  journal = {Nature Methods}
}

flygym's People

Contributors

gizemozd avatar lou-42 avatar sibocw avatar stepanove avatar stimpfli avatar tkclam avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

flygym's Issues

Increase modularity and facilitate inheritance

It would be great to add more modular functions such as add_sensors, add_actuators, add_cameras, ... to make it easier for the user to find the right function to overwrite when writing a new class inheriting from NMF dedicated to a specific behavior.

MuJoCo breaking change

Looks like the fly model is not compatible with MuJoCo 3.0.0 and above because of a breaking change:

  1. Removed mjOption.collision and the associated option/collision attribute.

See the changelog here.

ValueError when `actuated_joints` does not contain all controllable limb joints

Upon creation of the NeuroMechFly instance, if the actuated_joints parameter does not contain all the controllable limb joints (when controlling a subset of joints is desired) a ValueError is raised in the line below. This variable seems to only be used for leg adhesion, so wrapping it under a conditional on whether leg adhesion is enabled fixes it, but I believe it should handle the case of unactuated leg joints? Or is there a better way to control a subset of joints?

self._leglift_ref_jnt_id = [
self.actuated_joints.index("joint_" + tarsus_joint[:2] + joint)
for tarsus_joint, joint in zip(
self._last_tarsalseg_names, leglift_reference_joint
)
]

MuJoCo 3.0.0 incompatibility

MuJoCo released its 3.0.0 version last week. There are a few API-breaking changes that made it incompatible with the MJCF file provided by FlyGym.

If you encounter an error that looks like the following upon loading the fly model, this is the reason:

self = MJCF Element: <option timestep="0.0001" gravity="0 0 -9810" integrator="Euler" solver="Newton" iterations="1000" tolerance="9.9999999999999998e-13" noslip_iterations="100" noslip_tolerance="1e-08" mpr_iterations="100"/>
attribute_name = 'collision'

    def _check_valid_attribute(self, attribute_name):
      if attribute_name not in self._spec.attributes:
>       raise AttributeError(
            '{!r} is not a valid attribute for <{}>'.format(
                attribute_name, self._spec.name))
E       AttributeError: Line 4: error while parsing element <option>: 'collision' is not a valid attribute for <option>

../miniconda3/envs/nmf/lib/python3.11/site-packages/dm_control/mjcf/element.py:534: AttributeError

Generally, I prefer to keep FlyGym compatible with the most up-to-date versions of its core dependencies such as MuJoCo. However, since this is a major update (2.x.x -> 3.x.x), I would like to spend more time verifying the compatibility and defer this effort for now.

A new release is made to address this issue.

Remove randomness in visual renderinng

I think it's a good idea to either

  • remove the composite texture (is this how it's called?) in some body parts, especially legs, or
  • find out how to set MuJoCo's random seed for generating these textures

They make the rendering results and vision-related things just slightly different. In visual navigation tasks this makes the whole behavior stochastic (because the slightly different visual input leads to slightly different descending drive and the difference just accumulates).

What do you think? @stimpfli

Format of `.step()`'s return values

Gym actually specifies the format of .step()'s return values more strictly than I thought: https://gymnasium.farama.org/api/env/#gymnasium.Env.step

Namely it requires the following:

  • observation (ObsType) – An element of the environment’s observation_space as the next observation due to the agent actions. An example is a numpy array containing the positions and velocities of the pole in CartPole.
  • reward (SupportsFloat) – The reward as a result of taking the action.
  • terminated (bool) – Whether the agent reaches the terminal state (as defined under the MDP of the task) which can be positive or negative. An example is reaching the goal state or moving into the lava from the Sutton and Barton, Gridworld. If true, the user needs to call reset().
  • truncated (bool) – Whether the truncation condition outside the scope of the MDP is satisfied. Typically, this is a timelimit, but could also be used to indicate an agent physically going out of bounds. Can be used to end the episode prematurely before a terminal state is reached. If true, the user needs to call reset().
  • info (dict) – Contains auxiliary diagnostic information (helpful for debugging, learning, and logging). This might, for instance, contain: metrics that describe the agent’s performance state, variables that are hidden from observations, or individual reward terms that are combined to produce the total reward. In OpenAI Gym <v26, it contains “TimeLimit.truncated” to distinguish truncation and termination, however this is deprecated in favour of returning terminated and truncated variables.

There are two solutions:

  1. Keep doing it our way, and if we need to interface it with some existing framework/library (eg. stable-baselines3 for RL), we write and adapter.
  2. Do it as specified above by Gym. This requires an API change but I think it's worth it. Concretely, this means:
  • Currently, for the base environment, we are doing return obs, {} where the empty dict can be extended to contain arbitrary info.
  • We need to change it to return obs, 0, False, False, {}. As before, the user can extend this class to implement different reward/termination criteria if desired.

If we opt for option 2 (which I personally prefer), we should do it ASAP (but perhaps after COBAR) before it becomes even more annoying. What do you think @stimpfli ?

For Isaac Gym I'm definitely going to stick with Gym's specified API above.

Refactor flygym/examples

  • CPG, rule-based controller, hybrid controller, and turning can go into a single "locomotion" module
  • Simple vision and connectome-based vision model can go into a single "vision" module
  • Olfaction be its own thing (put it into a submodule for consistency)

Add a README for each submodule that describes what the files do.

Let's enforce a coding style for this repo!

  1. Is the default setting of Black ok for everyone? I slightly prefer 80-column limit instead of 88, but maybe I should just buy a wider monitor.
  2. Let's go through the demos before the next offering and make sure we stick with a good coding style. Maybe put some things into util modules.

In the long term, should we move away from dm_control and use just the mujoco binding for the core simulation?

In the long term, should we move away from dm_control and use just the mujoco Python binding, at least for the core simulation?

This would allow us to use GPU/TPU for the physics simulation using MJX that's been available since MuJoCo 3.0.0 (there's no plan on the dm_control side to retrofit MJX into dm_control). I doubt a morphologically complex model like NeuroMechFly can be run efficiently on the GPU (esp. given my experience with Isaac Gym), but I'd be curious to find out.

On the other hand, we can keep using dm_control's nice camera class for projecting from xyz coordinates to row-column coordinates on rendered image, or dm_control.mjcf for modifying the XML files, or add an interactive viewer with dm_control.viewer.

Make `_get_observation` public

It seems generally useful to be able to access the observation/state without having to supply an action and step the physics simulation. Let's make_get_observation a public method.

Bug in mask indicating whether visual input is refereshed at each step.

NeuroMechFLy.vision_update_mask is supposed to be a 1D binary array of length num_sim_steps, indicating whether the visual input is updated at each physics step.

However, currently the list that this mask array is generated from is appended to every time get_observation is called. Therefore the length of the array is the number of times the observation was queried, not the number of times the physics engine was advanced.

Find a better way to convert notebooks to tutorial pages

I've been using jupyter nbconvert to convert the demo notebooks to RST files for the website. This works OK but some manual finetuning of the RST files are required:

  1. Video outputs are not embedded into the notebooks (otherwise they easily get way to large). I'm uploading the output media (videos, large images) to a separate repo so as not to clog this package, and adding them to the RST pages by linking to the "raw" URLs of these files on the separate repo. This needs to be done manually.
  2. Sometimes image sizes need to be adjusted so that they display nicely on the website.
  3. I added language specifiers to the code blocks to ensure proper Python syntax highlighting.
  4. Something else? It's very human-in-the-loop...

This is quite annoying. Maybe we can automate this somehow? How about jupyter book? This is low priority but let's leave this issue here.

Refactor/clean the xml

  • Explore the usage of multi dog joints
  • Clarify the relationship between dof sequence and inverse kinematics:
    • Either make it flexible to using any sequence
    • Or write script to change the order
  • Remove actuators and cameras and initialise them in init()
  • Check that simulation parameters are fully relevant
  • Check that body, joint, inertial and geom parameters make sense

TODO: Main tasks for v1.0.0

Hi, now that we have merged #134 (various recent changes into new-api) and renamed new-api to dev-v1.0.0, I want to make a laundry list of major tasks code-wise that we should take care of before releasing v1.0.0 and resubmitting the paper. Feel free to check these boxes as they are completed.

@sibocw:

  • Test head stabilization in closed loop over complex terrain
  • Improve fly following algorithm using not just T4/T5 for DN control; then merge head-stabilization-complex-terrain into dev-v1.0.0: #139
    • Follow-up: #150
      • Further follow-up: #153
  • Merge dev-v1.0.0 into path-integration, test path integration over complex terrain using updated hybrid controller, and merge path-integration back into dev-v1.0.0 #141
  • Merge dev-cdf into dev-v1.0.0 #110 #151

@tkclam:

  • Update RL stuff
  • #140
  • Merge or remove cobar-week4? not until COBAR is over

@stimpfli:

  • Merge new-api-grooming into dev-v1.0.0
  • Is new_steps already fully merged? If so, remove it
  • Merge or remove fix_collisions_arena
  • Is automated_adhesion already fully merged? If so, remove it

Return contact force as a copy

Contact force reading is currently passed as a pointer and not a copy, so the readings at past steps change unless the user made a copy explicitly. We should probably just return a copy.

Furthermore, it could be useful to return the contact forces as a (6, 3) array (6 legs, xyz) instead of an (18,) array.

Discrepancies in contact force visualization

This is a post hoc documentation of a little investigation.

I noticed that before and after #143, the contact force time series looks quite different (in magnitude at least).

Before (blue/orange = left/right legs):
image

After (note different y range):
image

@stimpfli failed to obtain the same results in a different notebook that he had been preparing for COBAR teaching. I looked into what might be going on.

Turns out our simulation configurations are a little different:

  1. Calculation of total force: You summed forces experienced in each leg component in x, y, z separately before taking the norm. I took the norm experienced by each leg component first before summing them.
  2. Simulation not long enough: The time series looks more like mine 10s into the simulation.
  3. Spawn height of the fly: I changed it to the one I used but the impact of this is probably extremely minor in the end.
  4. Correction weights: As discussed some weeks ago, I used the following for the hybrid turning controller, so that it’s essentially a CPG-only turning controller: correction_rates={"retraction": (0, 0), "stumbling": (0, 0)}

After unifying all of these, the new controller gives result like this (blue: @stimpfli's, orange, mine).
image
image

And the old ones (70b8b76) are like this:
image
image

Still not exactly the same, as I plotted my original time series (first figure) in the path integration simulation which is different from running forward walking for 10s. But I'm pretty confident that this is not an indication of an underlying bug now.

Make sure existing tutorials are consistent with the new API

... or is this already the case?

  • Interacting with NeuroMechFly
  • Controlling locomotion with CPGs
  • Rule-based controller
  • Overcome complex terrain with a hybrid controller
  • Encapsulating custom code into Gym environments: A turning controller
  • Vision
  • Olfaction

Please double check but only after #154 is done.

Fly-to-fly tracking: leading fly sinks underground

Problem the leading fly disappears under the floor after a while?? This is in the context of #147.

Video: https://github.com/NeLy-EPFL/flygym/assets/23410765/2320eac9-1bd1-4d9b-ad08-c3df03ecfa79
(The following fly starts from the wrong side of the arena in this example, but you can see the leading fly disappearing anyway.)

To reproduce:

git checkout fly-following-circle
git pull

# Establish baseline visual system activities
# Note: i'm running this in parallel, but doing so will lead to GPU out-of-memory error.
# Therefore I disabled the GPU by setting CUDA_VISIBLE_DEVICES to "".
CUDA_VISIBLE_DEVICES="" python flygym/examples/vision_connectome_model/response_to_fly.py

# Run closed-loop tracking script
python python flygym/examples/vision_connectome_model/follow_fly_closed_loop.py

Notes on follow_fly_closed_loop.py:

  • It might be helpful to shorten the simulation, but you don't really see the problem unless it's 3s or longer
  • Replace the last line (process_trial("blocks", True, (-5, -10))) with the line before that's commented out (Parallel(n_jobs=8)(delayed(process_trial)(*config) for config in configs)) to run multiple simulations in parallel. On my machine (126GB RAM), 8 processes was as high as I could reach before OOM. Set CUDA_VISIBLE_DEVICES too.
  • If you don't want to run response_to_fly.py which establishes baseline activities of visual system neurons, you can download them here, decompress, and copy them under outputs/connectome_constrained_vision/baseline_response/:
    response_stats.zip

Feel free to work on fly-following-circle directly.

Release checklist

  • Merge branch into main
  • Update doc/source/changelog.rst
  • Update version number in setup.py
  • Make a release on GitHub
  • Push to PyPI

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.