Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
a7a9be9
pgo: publish pose graph (nodes + odom/loop edges) over LCM + top-down…
jeff-hykin May 12, 2026
ba0e17e
pgo: lift pgo_graph_{nodes,edges} via agentic_debug visual override
jeff-hykin May 12, 2026
1801759
pgo: drop second rerun pane; revert to single 3D view
jeff-hykin May 12, 2026
82d7799
pgo: publish per-keyframe delta on every loop-closure event + slow test
jeff-hykin May 12, 2026
3c90a7f
pgo: scan-context-based loop-closure search
jeff-hykin May 12, 2026
3e33003
pgo: synthetic drift test — proves Scan Context catches loops positio…
jeff-hykin May 13, 2026
5f64131
pgo tests: replace _log() print wrappers with the dimos logger
jeff-hykin May 14, 2026
6880ab6
pgo: expand short variable names introduced on this branch
jeff-hykin May 14, 2026
c7fd631
pgo: KITTI-360 benchmark scaffolding (loader + groundtruth + runner)
jeff-hykin May 15, 2026
e45b228
merge main into jeff/feat/better_pgo
jeff-hykin May 15, 2026
4480966
Merge branch 'main' into jeff/feat/better_pgo
jeff-hykin May 16, 2026
5b828e2
fix: mypy errors in pgo benchmark and demo viz
jeff-hykin May 16, 2026
59b751c
fix: drop dead positive-yaw wrap in yaw_from_shift
jeff-hykin May 16, 2026
4329aac
fix: drop empty pgo benchmark __init__.py
jeff-hykin May 16, 2026
a88f712
Merge branch 'main' into jeff/feat/better_pgo
jeff-hykin May 16, 2026
b28cd00
important PGO optimizations
jeff-hykin May 16, 2026
a0fafbb
fix: annotate pgo smoke_test lcm callback to satisfy mypy
jeff-hykin May 16, 2026
4479d8d
fix bug in cmu stack
jeff-hykin May 16, 2026
0ffa39a
add additional check
jeff-hykin May 16, 2026
6901d71
fix: ignore sklearn import for mypy (no stubs available)
jeff-hykin May 16, 2026
1c34ab9
nits + greptile fixes on PGO benchmark + reverse-loop test
jeff-hykin May 16, 2026
80adfb6
Merge branch 'main' into jeff/feat/better_pgo
jeff-hykin May 16, 2026
4a0de4d
nits: rename msg/msg_cls/msg_type → message/message_class/message_type
jeff-hykin May 16, 2026
6de903a
nits: lift scipy import to module top in run_kitti360_benchmark
jeff-hykin May 16, 2026
fbd00dc
nits: C++ rename mod→native_module, cp→cloud_with_pose, fix tresh typo
jeff-hykin May 16, 2026
e9f8356
generic-ize PGO output topic names
jeff-hykin May 16, 2026
5fe55c8
WIP: move KITTI-360 benchmark out of pgo, refactor into Modules + Blu…
jeff-hykin May 16, 2026
44f41f9
fix: annotate runner.get_results local to satisfy mypy
jeff-hykin May 16, 2026
3015232
flatten pgo/benchmark/ — drop the dir, files live at pgo/
jeff-hykin May 16, 2026
c1308e2
fix: regenerate all_blueprints.py for new pose_graph_kitti360 modules
jeff-hykin May 16, 2026
ff2ccb1
rename
jeff-hykin May 16, 2026
8afcf97
nits: expand fid/src/dst/tp/fp/fn/p/r in loop_groundtruth.py
jeff-hykin May 16, 2026
91cbdca
benchmark_kitti_smoke_test: rewrite on top of Modules + Blueprint
jeff-hykin May 16, 2026
b07f9ac
test_pgo_synthetic_drift: rewrite via Modules + Blueprint
jeff-hykin May 16, 2026
7f12682
cleaning
jeff-hykin May 16, 2026
73669bc
benchmark_place_recognition: humanise top-of-file docstring
jeff-hykin May 16, 2026
999a961
-
jeff-hykin May 16, 2026
caaad9e
fix: drop ASCII section markers from test_pgo_synthetic_drift
jeff-hykin May 16, 2026
5bcf724
test_pgo_loop_closure: rewrite via Modules + Blueprint
jeff-hykin May 16, 2026
410b7c9
fix: drop ASCII section markers from test_pgo_loop_closure
jeff-hykin May 16, 2026
2bdb6e1
test_pgo_rosbag + rosbag_fixtures: drop hardcoded LCM topics, use Mod…
jeff-hykin May 16, 2026
0a9b92d
remove empty configs + inline _message_to_dict + pydantic Field
jeff-hykin May 16, 2026
74e2c43
fix: cover playback-vs-PGO-startup race + section markers + regen all…
jeff-hykin May 16, 2026
f4a8e2a
fix: stop the host-side RPC client even when proxy.stop() raises
jeff-hykin May 16, 2026
bcc422d
test: mark PGO synthetic_drift + loop_closure as self_hosted
jeff-hykin May 16, 2026
3eb80b8
test: add skipif_no_nix and apply it to PGO C++-binary tests
jeff-hykin May 16, 2026
ed64f25
fix: query-level loop scoring + surface playback errors
jeff-hykin May 16, 2026
158f3eb
fix: RosbagScanOdomPlaybackModule surfaces playback errors
jeff-hykin May 16, 2026
6dccb2d
fix: SyntheticDriftPlaybackModule surfaces playback errors
jeff-hykin May 16, 2026
c01756d
fix: query-level FP in scoring (TP + FP now dimensionally consistent)
jeff-hykin May 16, 2026
e376930
Merge remote-tracking branch 'origin/main' into jeff/feat/better_pgo
jeff-hykin May 16, 2026
eff5254
test: rename benchmark_kitti_smoke_test.py -> demo_benchmark_kitti_sm…
jeff-hykin May 16, 2026
9e42096
fix: regenerate all_blueprints.py for TopicCounterModule
jeff-hykin May 16, 2026
9af58da
revert
jeff-hykin May 16, 2026
c7dc341
simplify
jeff-hykin May 16, 2026
d45adb2
-
jeff-hykin May 16, 2026
5d95b34
nav: add LoopClosure spec and use it in run_benchmark
jeff-hykin May 16, 2026
026adba
docs
jeff-hykin May 16, 2026
beba4dd
-
jeff-hykin May 16, 2026
e0fecd7
pgo: ready handshake — block start() until C++ binary is subscribed
jeff-hykin May 17, 2026
be7412b
Revert "pgo: ready handshake — block start() until C++ binary is subs…
jeff-hykin May 17, 2026
90e062a
make test reliable
jeff-hykin May 17, 2026
5e1e225
fixup start
jeff-hykin May 17, 2026
1584fe6
-
jeff-hykin May 17, 2026
a7af8ac
remove flakey test
jeff-hykin May 17, 2026
5697835
fixup on macos
jeff-hykin May 17, 2026
547205b
fixup LoopClosure spec
jeff-hykin May 17, 2026
ab133a0
-
jeff-hykin May 17, 2026
df16d46
fix timeout
jeff-hykin May 17, 2026
c138f66
Merge remote-tracking branch 'origin/main' into jeff/feat/better_pgo
jeff-hykin May 17, 2026
29dfafe
-
jeff-hykin May 17, 2026
cf6772e
Merge remote-tracking branch 'origin/main' into HEAD
jeff-hykin May 17, 2026
5769d48
Merge remote-tracking branch 'origin/main' into HEAD
jeff-hykin May 17, 2026
124c4e3
build native modules in the build step
jeff-hykin May 17, 2026
c152325
feat(msgs): GraphNodes3D Kaitai Struct schema
jeff-hykin May 17, 2026
04d73a3
feat(msgs): expose LineSegments3D public attrs + per-segment timestamps
jeff-hykin May 17, 2026
3564a77
feat(msgs): C++ LineSegments3D typed publisher header
jeff-hykin May 17, 2026
75b4e8d
feat(msgs): Graph3D pose-graph message type
jeff-hykin May 17, 2026
9ead549
refactor(far_planner): switch graph_nodes+graph_edges to graph: Out[G…
jeff-hykin May 17, 2026
6185f62
refactor(pgo): pose_graph_nodes+edges → pose_graph: Out[Graph3D]; dro…
jeff-hykin May 17, 2026
f97e1bd
chore(pgo): rename demo_benchmark_kitti_smoke → benchmark_kitti360_smoke
jeff-hykin May 17, 2026
be9f2ca
remove .ksy
jeff-hykin May 17, 2026
edb0584
test(pgo): restore filename test_pgo_loop_closure.py
jeff-hykin May 17, 2026
7311034
Merge remote-tracking branch 'origin/main' into jeff/feat/better_pgo
jeff-hykin May 17, 2026
bf3c570
Merge branch 'jeff/feat/better_pgo' of github.com:dimensionalOS/dimos…
jeff-hykin May 17, 2026
1c59f23
chore(blueprints): regenerate all_blueprints.py for smoke-test rename
jeff-hykin May 17, 2026
e4dd71a
feat(msgs): GraphDelta3D + wire PGO's loop_closure_event to it
jeff-hykin May 17, 2026
446fa16
clean comments
jeff-hykin May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
run: ./bin/run-doc-codeblocks --ci --no-cache

tests:
timeout-minutes: 20
timeout-minutes: 25
strategy:
matrix:
pyver: ['3.10', '3.11', '3.12', '3.13', '3.14']
Expand Down
7 changes: 7 additions & 0 deletions dimos/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import hashlib
import os
import platform
import shutil
import tempfile
import threading

Expand Down Expand Up @@ -74,6 +75,10 @@ def _has_ros() -> bool:
return False


def _has_nix() -> bool:
return shutil.which("nix") is not None


def _is_macos() -> bool:
return platform.system() == "Darwin"

Expand All @@ -90,6 +95,7 @@ def pytest_configure(config):
config.addinivalue_line("markers", "skipif_no_openai: skip when OPENAI_API_KEY is not set")
config.addinivalue_line("markers", "skipif_no_alibaba: skip when ALIBABA_API_KEY is not set")
config.addinivalue_line("markers", "skipif_no_ros: skip when ROS dependencies are not present")
config.addinivalue_line("markers", "skipif_no_nix: skip when the `nix` binary is not on PATH")
config.addinivalue_line("markers", "skipif_macos_bug: skip known-buggy tests on macOS")
config.addinivalue_line("markers", "skipif_macos: skip tests not intended to run on macOS")

Expand Down Expand Up @@ -122,6 +128,7 @@ def pytest_collection_modifyitems(config, items):
"skipif_no_openai": (not os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY not set"),
"skipif_no_alibaba": (not os.getenv("ALIBABA_API_KEY"), "ALIBABA_API_KEY not set"),
"skipif_no_ros": (not _has_ros(), "ROS dependencies are not present"),
"skipif_no_nix": (not _has_nix(), "nix binary is not on PATH"),
"skipif_macos_bug": (_is_macos(), "Some tests are buggy on Mac OS"),
"skipif_macos": (_is_macos(), "Not intended to run on macOS"),
}
Expand Down
7 changes: 5 additions & 2 deletions dimos/core/native_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ def __init__(self, **kwargs: Any) -> None:
if not Path(self.config.executable).is_absolute() and self.config.cwd is not None:
self.config.executable = str(Path(self.config.cwd) / self.config.executable)

@rpc
def build(self) -> None:
Comment thread
jeff-hykin marked this conversation as resolved.
super().build()
self._maybe_build()

@rpc
def start(self) -> None:
super().start()
Expand All @@ -190,8 +195,6 @@ def start(self) -> None:
)
return

self._maybe_build()

topics = self._collect_topics()

cmd = [self.config.executable]
Expand Down
175 changes: 175 additions & 0 deletions dimos/msgs/nav_msgs/Graph3D.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright 2026 Dimensional Inc.
// SPDX-License-Identifier: Apache-2.0
//
// Typed C++ helper mirroring the Python `dimos.msgs.nav_msgs.Graph3D`.
// Canonical schema lives in `dimos/msgs/nav_msgs/Graph3D.ksy` — keep
// encode() in sync with that file (and with Graph3D.py.lcm_decode).
//
// Wire format (big-endian):
//
// uint64 edge_count
// uint64 node_count
// double timestamp // seconds since epoch
// per node (node_count):
// pose_stamped:
// double ts
// uint32 frame_id_len
// bytes frame_id (utf-8, no terminator)
// 7×double pos_x, pos_y, pos_z, quat_x, quat_y, quat_z, quat_w
// uint64 id
// uint64 metadata_id
// per edge (edge_count):
// uint64 start_id
// uint64 end_id
// double timestamp
// uint64 metadata_id
//
// Edges reference nodes by `id`, not by index.

#pragma once

#include <cstdint>
#include <cstring>
#include <string>
#include <utility>
#include <vector>

#include <lcm/lcm-cpp.hpp>

namespace dimos {

namespace graph3d_detail {

// Host-order → big-endian byte writers. Avoid <endian.h> for portability
// (macOS uses different names) — write byte-by-byte from the top.

inline void write_u32_be(std::vector<uint8_t>& out, uint32_t v) {
out.push_back(static_cast<uint8_t>((v >> 24) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 16) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 8) & 0xFF));
out.push_back(static_cast<uint8_t>( v & 0xFF));
}

inline void write_u64_be(std::vector<uint8_t>& out, uint64_t v) {
for (int shift = 56; shift >= 0; shift -= 8) {
out.push_back(static_cast<uint8_t>((v >> shift) & 0xFF));
}
}

inline void write_double_be(std::vector<uint8_t>& out, double v) {
uint64_t bits;
std::memcpy(&bits, &v, sizeof(bits));
write_u64_be(out, bits);
}

inline void write_bytes(std::vector<uint8_t>& out, const std::string& s) {
out.insert(out.end(), s.begin(), s.end());
}

} // namespace graph3d_detail

class Graph3D {
public:
struct PoseStamped {
double ts = 0.0;
std::string frame_id;
double pos_x = 0.0, pos_y = 0.0, pos_z = 0.0;
double quat_x = 0.0, quat_y = 0.0, quat_z = 0.0, quat_w = 1.0;
};

struct Node3D {
PoseStamped pose;
uint64_t id = 0;
uint64_t metadata_id = 0;
};

struct Edge {
uint64_t start_id = 0;
uint64_t end_id = 0;
double timestamp = 0.0;
uint64_t metadata_id = 0;
};

Graph3D(std::string frame_id, double timestamp)
: frame_id_(std::move(frame_id)), timestamp_(timestamp) {}

void reserve_nodes(size_t capacity) { nodes_.reserve(capacity); }
void reserve_edges(size_t capacity) { edges_.reserve(capacity); }

// Add a node. The pose's frame_id defaults to the graph's frame_id —
// override per-node only if a node lives in a different frame.
void add_node(uint64_t id, uint64_t metadata_id, double pose_ts,
double pos_x, double pos_y, double pos_z,
double quat_x, double quat_y, double quat_z, double quat_w,
std::string node_frame_id = "") {
PoseStamped pose;
pose.ts = pose_ts;
pose.frame_id = node_frame_id.empty() ? frame_id_ : std::move(node_frame_id);
pose.pos_x = pos_x; pose.pos_y = pos_y; pose.pos_z = pos_z;
pose.quat_x = quat_x; pose.quat_y = quat_y; pose.quat_z = quat_z; pose.quat_w = quat_w;
nodes_.push_back({pose, id, metadata_id});
}

// Position-only convenience (orientation defaults to identity).
void add_node_xyz(uint64_t id, uint64_t metadata_id, double pose_ts,
double pos_x, double pos_y, double pos_z) {
add_node(id, metadata_id, pose_ts, pos_x, pos_y, pos_z, 0.0, 0.0, 0.0, 1.0);
}

void add_edge(uint64_t start_id, uint64_t end_id, double edge_ts,
uint64_t metadata_id = 0) {
edges_.push_back({start_id, end_id, edge_ts, metadata_id});
}

size_t node_count() const { return nodes_.size(); }
size_t edge_count() const { return edges_.size(); }
const std::string& frame_id() const { return frame_id_; }

std::vector<uint8_t> encode() const {
using namespace graph3d_detail;
std::vector<uint8_t> out;
// Conservative reservation: header + per-node fixed bytes + per-edge.
// frame_id strings add variable length on top — that just causes a
// realloc, not correctness issues.
out.reserve(24 + nodes_.size() * 84 + edges_.size() * 32);
write_u64_be(out, static_cast<uint64_t>(edges_.size()));
write_u64_be(out, static_cast<uint64_t>(nodes_.size()));
write_double_be(out, timestamp_);
for (const auto& n : nodes_) {
// pose_stamped first (per Graph3D.ksy)
write_double_be(out, n.pose.ts);
write_u32_be(out, static_cast<uint32_t>(n.pose.frame_id.size()));
write_bytes(out, n.pose.frame_id);
write_double_be(out, n.pose.pos_x);
write_double_be(out, n.pose.pos_y);
write_double_be(out, n.pose.pos_z);
write_double_be(out, n.pose.quat_x);
write_double_be(out, n.pose.quat_y);
write_double_be(out, n.pose.quat_z);
write_double_be(out, n.pose.quat_w);
// then id, metadata_id
write_u64_be(out, n.id);
write_u64_be(out, n.metadata_id);
}
for (const auto& e : edges_) {
write_u64_be(out, e.start_id);
write_u64_be(out, e.end_id);
write_double_be(out, e.timestamp);
write_u64_be(out, e.metadata_id);
}
return out;
}

int publish(lcm::LCM& lcm, const std::string& channel) const {
std::vector<uint8_t> bytes = encode();
return lcm.publish(channel, bytes.data(), static_cast<int>(bytes.size()));
}

private:
std::string frame_id_;
double timestamp_;
std::vector<Node3D> nodes_;
std::vector<Edge> edges_;
};

} // namespace dimos
Loading
Loading