Skip to content

Commit

Permalink
Support Saving Long-term Memory (#463)
Browse files Browse the repository at this point in the history
* Added LTM utils. (#462)

* Added config `use_ltm`. (#462)

* Migrated `distance_between()` to `leads.data`. (#462)

* Supported odometer under GPS-only mode. (#462)

* Merged submodules.
  • Loading branch information
ATATC authored Jan 31, 2025
1 parent 57f99c1 commit be17996
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 20 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ Note that a purely empty file could cause an error.
| `comm_stream_port` | `bool` | Port on which the streaming system runs on | Main, Remote | `16901` |
| `data_dir` | `str` | Directory for the data recording system | Main, Remote | `"data"` |
| `save_data` | `bool` | `True`: save data; `False`: discard data | Remote | `False` |
| `use_ltm` | `bool` | `True`: use long-term memory; `False`: short-term memory only | Main | `False` |

For device-related implicit configurations, please see the [devices module](leads_vec/devices.py).

Expand Down
1 change: 1 addition & 0 deletions leads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from leads.event import *
from leads.leads import *
from leads.logger import Level, L
from leads.ltm import *
from leads.plugin import *
from leads.registry import *
from leads.sft import SFT, mark_device, read_device_marker
1 change: 1 addition & 0 deletions leads/_ltm/core
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
14 changes: 13 additions & 1 deletion leads/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from time import time as _time
from typing import override as _override, Any as _Any

from numpy import radians as _radians, degrees as _degrees, cos as _cos
from numpy import radians as _radians, degrees as _degrees, cos as _cos, sqrt as _sqrt


class Serializable(object):
Expand Down Expand Up @@ -161,6 +161,18 @@ def meters2dlon(meters: float, lat: float) -> float:
return _degrees(meters / 6378137 / _cos(_radians(lat)))


def distance_between(lat_0: float, lon_0: float, lat: float, lon: float) -> float:
"""
Calculate the distance between two locations on the Earth.
:param lat_0: the latitude of the first location
:param lon_0: the longitude of the first location
:param lat: the latitude of the second location
:param lon: the longitude of the second location
:return:
"""
return _sqrt(dlon2meters(lon - lon_0, .5 * (lat_0 + lat)) ** 2 + dlat2meters(lat - lat_0) ** 2)


def format_duration(duration: float) -> str:
"""
Wrap the duration into a formatted string.
Expand Down
4 changes: 3 additions & 1 deletion leads/data_persistence/analyzer/inference.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from abc import ABCMeta as _ABCMeta, abstractmethod as _abstractmethod
from typing import Any as _Any, override as _override, Generator as _Generator, Literal as _Literal

from leads.data import distance_between
from leads.data_persistence.analyzer.utils import time_invalid, speed_invalid, acceleration_invalid, \
mileage_invalid, latitude_invalid, longitude_invalid, distance_between
mileage_invalid, latitude_invalid, longitude_invalid
from leads.data_persistence.core import CSVDataset, DEFAULT_HEADER, VISUAL_HEADER_ONLY


Expand Down Expand Up @@ -239,6 +240,7 @@ class VisualDataRealignmentByLatency(Inference):
Offset the delay introduced by camera recording and video encoding so that the sensor data and the picture of the
same frame match.
"""

def __init__(self, *channels: _Literal["front", "left", "right", "rear"]) -> None:
super().__init__((0, 1), VISUAL_HEADER_ONLY)
self._channels: tuple[_Literal["front", "left", "right", "rear"], ...] = channels if channels else (
Expand Down
15 changes: 0 additions & 15 deletions leads/data_persistence/analyzer/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from typing import Any as _Any

from leads.data import dlat2meters, dlon2meters
from .._computational import sqrt as _sqrt


def time_invalid(o: _Any) -> bool:
return not isinstance(o, int)
Expand Down Expand Up @@ -30,15 +27,3 @@ def longitude_invalid(o: _Any) -> bool:

def latency_invalid(o: _Any) -> bool:
return not isinstance(o, int | float)


def distance_between(lat_0: float, lon_0: float, lat: float, lon: float) -> float:
"""
Calculate the distance between two locations on the Earth.
:param lat_0: the latitude of the first location
:param lon_0: the longitude of the first location
:param lat: the latitude of the second location
:param lon: the longitude of the second location
:return:
"""
return _sqrt(dlon2meters(lon - lon_0, .5 * (lat_0 + lat)) ** 2 + dlat2meters(lat - lat_0) ** 2)
32 changes: 32 additions & 0 deletions leads/ltm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from json import loads as _loads, dumps as _dumps
from os.path import abspath as _abspath

from leads.types import SupportedConfigValue as _SupportedConfigValue

_ltm: dict[str, _SupportedConfigValue] = {}


def _load_ltm() -> None:
global _ltm
with open(f"{_abspath(__file__)[:-6]}_ltm/core", "r") as f:
ltm_content = f.read()
if not (ltm_content.startswith("{") and ltm_content.endswith("}")):
ltm_content = "{}"
_ltm = _loads(ltm_content)


def _sync_ltm() -> None:
with open(f"{_abspath(__file__)[:-6]}_ltm/core", "w") as f:
f.write(_dumps(_ltm))


def ltm_get(key: str) -> _SupportedConfigValue:
return _ltm[key]


def ltm_set(key: str, value: _SupportedConfigValue) -> None:
_ltm[key] = value
_sync_ltm()


_load_ltm()
1 change: 1 addition & 0 deletions leads_vec/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ def __init__(self, base: dict[str, _Any]) -> None:
self.comm_stream: bool = False
self.comm_stream_port: int = 16901
self.data_dir: str = "data"
self.use_ltm: bool = False
super().__init__(base)
17 changes: 14 additions & 3 deletions leads_vec/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Controller, CENTER_REAR_WHEEL_SPEED_SENSOR, require_config, mark_device, ODOMETER, GPS_RECEIVER, \
ConcurrentOdometer, LEFT_INDICATOR, RIGHT_INDICATOR, VOLTAGE_SENSOR, DataContainer, has_device, \
FRONT_VIEW_CAMERA, LEFT_VIEW_CAMERA, RIGHT_VIEW_CAMERA, REAR_VIEW_CAMERA, VisualDataContainer, BRAKE_INDICATOR, \
SFT, read_device_marker, has_controller, POWER_CONTROLLER, WHEEL_SPEED_CONTROLLER, ACCELEROMETER, require_context
SFT, read_device_marker, has_controller, POWER_CONTROLLER, WHEEL_SPEED_CONTROLLER, ACCELEROMETER, require_context, \
ltm_get, ltm_set, distance_between
from leads_arduino import ArduinoMicro, WheelSpeedSensor, VoltageSensor, Accelerometer, Acceleration
from leads_comm_serial import SOBD
from leads_gpio import NMEAGPSReceiver, LEDGroup, LED, LEDGroupCommand, LEDCommand, Entire, Transition, Button, \
Expand Down Expand Up @@ -54,16 +55,19 @@ def initialize(self, *parent_tags: str) -> None:
@override
def read(self) -> DataContainer:
general = {
"mileage": self.device(ODOMETER).read(),
"gps_valid": (gps := self.device(GPS_RECEIVER).read())[0],
"gps_ground_speed": gps[1],
"latitude": gps[2],
"longitude": gps[3],
**self.device(POWER_CONTROLLER).read()
}
wsc = self.device(WHEEL_SPEED_CONTROLLER).read()
odometer = self.device(ODOMETER)
if GPS_ONLY:
wsc["speed"] = gps[1]
prev = require_context().data()
odometer.write(odometer.read() + distance_between(prev.latitude, prev.longitude, gps[2], gps[3]))
general["mileage"] = odometer.read()
visual = {}
if has_device(FRONT_VIEW_CAMERA):
cam = get_camera(FRONT_VIEW_CAMERA, Base64Camera)
Expand Down Expand Up @@ -138,10 +142,17 @@ class AverageOdometer(ConcurrentOdometer):
def initialize(self, *parent_tags: str) -> None:
mark_device(self, "WSC")
super().initialize(*parent_tags)
if config.use_ltm:
self.write(ltm_get("mileage"))

@override
def write(self, payload: float) -> None:
super().write(payload)
ltm_set("mileage", payload)

@override
def read(self) -> float:
return super().read() / 3
return super().read() if GPS_ONLY else super().read() / 3


@device(*(((LEFT_FRONT_WHEEL_SPEED_SENSOR, RIGHT_FRONT_WHEEL_SPEED_SENSOR, CENTER_REAR_WHEEL_SPEED_SENSOR),
Expand Down

0 comments on commit be17996

Please sign in to comment.