Skip to content

memory2 tf service#2707

Open
leshy wants to merge 11 commits into
mainfrom
feat/ivan/memtf
Open

memory2 tf service#2707
leshy wants to merge 11 commits into
mainfrom
feat/ivan/memtf

Conversation

@leshy

@leshy leshy commented Jul 3, 2026

Copy link
Copy Markdown
Member

we converted recordings to sensor frame, mem now needs a tf service for global mapping

leshy added 4 commits July 3, 2026 00:32
Unify replay tf with the live service: StreamTF(MultiTBuffer, TFSpec)
mirrors PubSubTF, pulling windows from a recorded tf stream on demand
instead of receiving pushed messages. Lookups span buffer_size backward
(or an explicit time_tolerance) plus forward_tolerance ahead; a cache
miss prefetches cache_span past the query window and evicts everything
first, so chronological replay costs one db query per cache_span.

- TFLookup protocol (read side) + mypy conformance checks
- get_pose hoisted from PubSubTF to TFSpec
- MultiTBuffer: None tolerance resolves to buffer_size explicitly
- map global: registration via tf stream (never Observation poses),
  --frame auto-detects world/map/odom via probe lookups, fail-fast
  when the cloud frame can't be resolved
- tf lookup tests consolidated into a grid: live MultiTBuffer vs
  StreamTF over memory/sqlite stores
@mintlify

mintlify Bot commented Jul 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 3, 2026, 12:34 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.00000% with 40 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/mapping/utils/cli/map.py 34.54% 26 Missing and 10 partials ⚠️
dimos/memory2/tf.py 96.07% 1 Missing and 1 partial ⚠️
dimos/protocol/tf/tf.py 81.81% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #2707      +/-   ##
==========================================
+ Coverage   72.05%   72.07%   +0.01%     
==========================================
  Files         893      894       +1     
  Lines       80728    80867     +139     
  Branches     7310     7332      +22     
==========================================
+ Hits        58165    58281     +116     
- Misses      20614    20628      +14     
- Partials     1949     1958       +9     
Flag Coverage Δ
OS-ubuntu-24.04-arm 64.16% <77.20%> (+0.02%) ⬆️
OS-ubuntu-latest 66.84% <77.20%> (+0.02%) ⬆️
Py-3.10 66.82% <77.20%> (+0.01%) ⬆️
Py-3.11 66.83% <77.20%> (+0.01%) ⬆️
Py-3.12 66.83% <77.20%> (+0.02%) ⬆️
Py-3.13 66.82% <77.20%> (+<0.01%) ⬆️
Py-3.14 66.84% <77.20%> (+0.01%) ⬆️
Py-3.14t 66.82% <77.20%> (+<0.01%) ⬆️
SelfHosted-Large 30.15% <21.60%> (+<0.01%) ⬆️
SelfHosted-Linux 38.00% <29.60%> (+0.01%) ⬆️
SelfHosted-macOS 36.32% <29.60%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/protocol/tf/test_tf.py 100.00% <100.00%> (ø)
dimos/memory2/tf.py 96.07% <96.07%> (ø)
dimos/protocol/tf/tf.py 88.68% <81.81%> (+1.13%) ⬆️
dimos/mapping/utils/cli/map.py 37.03% <34.54%> (-1.86%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@leshy leshy marked this pull request as ready for review July 3, 2026 00:52
@leshy leshy changed the title Feat/ivan/memtf memory2 tf service Jul 3, 2026
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces StreamTF, a read-only TF service backed by a recorded tf stream, and wires it into the mapping CLI so that sensor-frame pointclouds are registered into a world frame via per-frame tf lookups instead of stored per-frame poses.

  • dimos/memory2/tf.py: new StreamTF class with a bounded sliding-window cache (_ensure / _load) over any Stream[TFMessage]; _covered tracks the loaded range and evicts on miss.
  • dimos/mapping/utils/cli/map.py: replaces --use-tf / world_frame bool with auto-detected world frame (_detect_world), a _position helper for dedup/path (tf with pose fallback), and a register closure for cloud accumulation; the _accumulate API changes from world_frame: bool to register: Callable | None.
  • dimos/protocol/tf/tf.py: adds TFLookup runtime-checkable protocol, moves get_pose to TFSpec, and fixes time_tolerance=None to fall back to buffer_size instead of passing None through to TBuffer.

Confidence Score: 4/5

Safe to merge for recordings where the tf stream has no temporal gaps; recordings with tf gaps during flight may silently produce holes in the accumulated map due to the dedup/registration inconsistency.

The _position fallback to obs.pose (used for dedup) is inconsistent with _accumulate's tf-only registration: a pose-fallback frame at time T2 can overwrite an earlier tf-valid frame T1 in the same dedup cell, and then get skipped by register() itself — causing a silent empty cell in the map. The overall architecture is sound and well-tested; the main risk is bounded to recordings with tf gaps.

dimos/mapping/utils/cli/map.py — specifically the interaction between _position's pose fallback and _accumulate's tf-only register callable.

Important Files Changed

Filename Overview
dimos/memory2/tf.py New StreamTF replay service wrapping a recorded tf stream; introduces bounded cache with prefetch and eviction. _ensure has a stale-_covered bug after buffers.clear() (flagged previously) and the _position / accumulate inconsistency noted below surfaces here.
dimos/mapping/utils/cli/map.py Refactors cloud registration from per-frame pose to tf-stream lookup; adds _detect_world, _position with pose fallback, and a register closure. The _position fallback can silently displace tf-valid frames in dedup (see comment), and the --use-tf flag is replaced by auto-detection plus --frame/--tf-tolerance.
dimos/protocol/tf/tf.py Adds TFLookup runtime-checkable protocol, moves get_pose up to TFSpec base class, and fixes the time_tolerance=None fallback in MultiTBuffer.get_transform to use buffer_size instead of passing None through.
dimos/protocol/tf/test_tf.py Test suite restructured into a parametrized make_tf fixture covering live MultiTBuffer, MemoryStore-backed StreamTF, and SqliteStore-backed StreamTF. 3-hop chain composition test removed without equivalent replacement.
docs/usage/transforms.md Minor doc fix: corrects the TF service file path reference.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as map.py (main)
    participant STF as StreamTF
    participant Store as Stream[TFMessage]
    participant Acc as _accumulate

    CLI->>Store: stream(lidar_stream, PointCloud2)
    CLI->>STF: StreamTF.from_store(store)
    CLI->>STF: get(world, cloud_frame, first_obs.ts) [probe]
    STF->>STF: _ensure(tp-back, tp+fwd)
    STF->>Store: stream.at(center, radius)
    Store-->>STF: observations with TFMessages
    STF->>STF: "receive_transform(*transforms)"
    STF-->>CLI: Transform or None [probe result]

    loop dedup frames
        CLI->>STF: _position(obs) → tf lookup
        STF-->>CLI: (x,y,z) or None
        Note over CLI: fallback to obs.pose if tf=None
    end

    CLI->>Acc: "_accumulate(kept, register=_register)"
    loop kept frames
        Acc->>STF: register(obs) → buf.get(world, frame, ts)
        STF-->>Acc: Transform or None
        alt Transform available
            Acc->>Acc: apply transform to cloud
        else None
            Acc->>Acc: skip frame (continue)
        end
    end
    Acc-->>CLI: PointCloud2 (global map)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as map.py (main)
    participant STF as StreamTF
    participant Store as Stream[TFMessage]
    participant Acc as _accumulate

    CLI->>Store: stream(lidar_stream, PointCloud2)
    CLI->>STF: StreamTF.from_store(store)
    CLI->>STF: get(world, cloud_frame, first_obs.ts) [probe]
    STF->>STF: _ensure(tp-back, tp+fwd)
    STF->>Store: stream.at(center, radius)
    Store-->>STF: observations with TFMessages
    STF->>STF: "receive_transform(*transforms)"
    STF-->>CLI: Transform or None [probe result]

    loop dedup frames
        CLI->>STF: _position(obs) → tf lookup
        STF-->>CLI: (x,y,z) or None
        Note over CLI: fallback to obs.pose if tf=None
    end

    CLI->>Acc: "_accumulate(kept, register=_register)"
    loop kept frames
        Acc->>STF: register(obs) → buf.get(world, frame, ts)
        STF-->>Acc: Transform or None
        alt Transform available
            Acc->>Acc: apply transform to cloud
        else None
            Acc->>Acc: skip frame (continue)
        end
    end
    Acc-->>CLI: PointCloud2 (global map)
Loading

Reviews (2): Last reviewed commit: "Merge branch 'feat/ivan/memtf' of github..." | Re-trigger Greptile

Comment thread dimos/memory2/tf.py
Comment thread dimos/memory2/tf.py
@leshy leshy added the PlzReview label Jul 3, 2026
Comment thread dimos/memory2/tf.py
if stream is not None:
kwargs["stream"] = stream
TFSpec.__init__(self, **kwargs)
MultiTBuffer.__init__(self, buffer_size=math.inf)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why infinity? Is it wise to never evict? Was this meant to be `self.config.cache_span?

Comment thread dimos/memory2/tf.py
def _load(self, lo: float, hi: float) -> None:
for obs in self.stream.at((lo + hi) / 2, (hi - lo) / 2):
self.receive_transform(*obs.data.transforms)
self._covered = (lo, hi)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need to use self._cv for modifying self._covered too?

Comment thread dimos/memory2/tf.py

if TYPE_CHECKING:
# mypy conformance check: StreamTF satisfies the read-side tf protocol.
_lookup_impl: TFLookup = cast("StreamTF", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

Comment thread dimos/protocol/tf/tf.py

if TYPE_CHECKING:
# mypy conformance checks: the live services satisfy the read-side protocol.
_lookup_impls: tuple[type[TFLookup], ...] = (MultiTBuffer, PubSubTF)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere?

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PlzReview ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants