Skip to content
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
f73997e
Begin cleaning up metadata type
contagon Sep 21, 2025
97e4ab2
Write new pipeline and dataset registration methods
contagon Sep 26, 2025
2e2363a
Small tweaks to clean up changes
contagon Sep 27, 2025
7073739
Major changes to underlying types and serialization code
contagon Sep 29, 2025
d4ffdd0
Finalizing all parsing code
contagon Sep 30, 2025
261f165
Finish adding tests for new trajectory and metadata classes
contagon Sep 30, 2025
419194d
Officially moved over to new experiment class
contagon Sep 30, 2025
7148ee8
Fix a handful of small bugs when playing around with things
contagon Sep 30, 2025
eeebc53
Add default params to parser
contagon Sep 30, 2025
ebf4e5d
Clean up stats command A LOT
contagon Oct 1, 2025
8226800
Fix some of pipeline parsing
contagon Oct 1, 2025
a4b45f2
Add generics to Trajectory to help with metadata types
contagon Oct 1, 2025
8935da9
Remove a bunch of type: ignores
contagon Oct 1, 2025
0fb6924
Update stats command for more flexible window specifying
contagon Oct 1, 2025
87198ce
Clean up stats window nomenclature
contagon Oct 1, 2025
e36b05b
Add EVALIO_CUSTOM hooks
contagon Oct 1, 2025
e5669e9
Update all documentation of rewrite
contagon Oct 1, 2025
312e8fa
Fix pipeline docs
contagon Oct 1, 2025
760ed7c
Clean up rest of pipeline docs
contagon Oct 1, 2025
7f82c94
Clean up some minor bugs
contagon Oct 2, 2025
12b2420
Some stats optimizations
contagon Oct 2, 2025
12424a7
preprocess stamp style in csv loading
contagon Oct 2, 2025
a8d8dea
Shorten readme, add in citations
contagon Oct 2, 2025
20bd7e3
Try with cff file
contagon Oct 2, 2025
400b933
Fix citation.. hopefully
contagon Oct 2, 2025
e875cfd
Move to bib for citation
contagon Oct 2, 2025
642a7aa
Clean up stats options
contagon Oct 3, 2025
7af2459
Add in faster csv parser
contagon Oct 3, 2025
aa0fcce
Switch SE3 distance to cpp for speed
contagon Oct 3, 2025
6793019
Add in copy constructors
contagon Oct 3, 2025
9b78587
Speedup using c yaml loader
contagon Oct 3, 2025
936be9b
Move _check_overstep to cpp
contagon Oct 3, 2025
c18bbf2
Some niceties for output in stats
contagon Oct 3, 2025
21a1419
Bump rerun to 0.25
contagon Oct 3, 2025
2ce5f6d
Some misc cleanups throughout
contagon Oct 13, 2025
bce6d10
Begin switch from typer to cyclopts
contagon Oct 14, 2025
b9c3c34
Merge branch 'master' into feature/cyclopts
contagon Oct 15, 2025
dc78587
Custom help spec
contagon Oct 16, 2025
7848203
More migration to typer
contagon Oct 17, 2025
35fb91b
Add in global options
contagon Oct 17, 2025
53e0152
Big cleanup across all cli
contagon Oct 25, 2025
d23f6e5
Finalize init
contagon Oct 25, 2025
f65ec05
Cleanup run
contagon Oct 25, 2025
67bee0d
Clean up ls
contagon Oct 25, 2025
29332be
Clean up stats
contagon Oct 25, 2025
077d83c
Remove old Param call
contagon Oct 25, 2025
643413f
Clean up dataset autocompletion
contagon Oct 25, 2025
0dc9f52
Fix meta calling and parsing test
contagon Oct 25, 2025
4c4006f
Merge branch 'master' into feature/cyclopts
contagon Oct 26, 2025
5d16a83
Fix new tests
contagon Oct 26, 2025
998afea
Clean up some typing issues
contagon Oct 27, 2025
f09f7bf
Merge branch 'master' into feature/cyclopts
contagon Oct 27, 2025
8748e84
Merge branch 'main' into feature/cyclopts
contagon Nov 12, 2025
ee41cb5
Convert to cylcopts mkdocs cli plugin
contagon Nov 13, 2025
46a6a51
Update cyclopts version
contagon Mar 16, 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
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ description = "Evaluate Lidar-Inertial Odometry on public datasets"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"cyclopts==4.0",
"asteval>=1.0.6",
"distinctipy>=1.3.4",
"gdown>=5.2.0",
"joblib>=1.5.2",
"numpy",
"polars>=1.33.1",
"pyyaml>=6.0",
"rapidfuzz>=3.12.2",
"rapidfuzz>=3.14.1",
"rosbags>=0.10.10",
"tqdm>=4.66",
"typer>=0.15.3",
]
keywords = [
"lidar",
Expand All @@ -37,7 +37,7 @@ requires = ["scikit-build-core>=0.8", "nanobind>=2.9.2", "numpy"]
build-backend = "scikit_build_core.build"

[project.scripts]
evalio = "evalio.cli:app"
evalio = "evalio.cli:app.meta"

# -------------- Tools -------------- #
# building
Expand Down
251 changes: 173 additions & 78 deletions python/evalio/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,106 +1,201 @@
from inspect import isclass
from pathlib import Path
from typing import Annotated, Any, Optional

import typer
from cyclopts import App, Group, Parameter
from cyclopts.completion import detect_shell
from cyclopts.help import DefaultFormatter, ColumnSpec, HelpEntry, PanelSpec, TableSpec
from enum import Enum
from rich.console import Console, ConsoleOptions
from typing import Any, Union, get_args, get_origin, Literal, Annotated, Optional


# ------------------------- Prettier Helper Pages ------------------------- #
# Define custom column renderers
def names_long(entry: HelpEntry) -> str:
"""Combine parameter names and shorts."""
if not entry.names:
return ""
# If positional with single name, just return it
if len(entry.names) == 1:
return entry.names[0]
# If multiples, skip ALL_CAPS
if entry.names[0].isupper():
names = entry.names[1:]
else:
names = entry.names

return " ".join(names).strip()


def render_type(type_: Any) -> str:
"""Show the parameter type."""
if type_ is bool:
return ""
elif type_ is str:
return "text"
elif type_ is int:
return "int"
elif type_ is float:
return "float"
elif type_ is Path:
return "path"
elif type_ is None:
return ""
elif (origin := get_origin(type_)) is Literal:
args = get_args(type_)
if len(args) > 5:
return "text"
return "|".join(str(a) for a in args)
elif origin is Union:
# handle Optional
args = get_args(type_)
if len(args) == 2 and type(None) in args:
type_ = args[0] if args[1] is type(None) else args[1]
return render_type(type_)
else:
return " | ".join(render_type(t) for t in args)
elif origin is list:
return render_type(get_args(type_)[0])
elif isclass(type_) and issubclass(type_, Enum):
return "|".join(e.name for e in type_)

raise ValueError(f"Unsupported type for rendering: {type_}")


def columns(
console: Console, options: ConsoleOptions, entries: list[HelpEntry]
) -> list[ColumnSpec]:
columns: list[ColumnSpec] = []

if any(e.required for e in entries):
columns.append(
ColumnSpec(
renderer=lambda e: "*" if e.required else " ", # type: ignore
width=1,
style="red bold",
)
)

columns.extend(
[
ColumnSpec(
renderer=names_long,
style="cyan",
),
ColumnSpec(
renderer=lambda e: " ".join(e.shorts).strip() if e.shorts else "", # type: ignore
style="green",
max_width=30,
),
ColumnSpec(
renderer=lambda e: render_type(e.type), # type: ignore
style="yellow",
justify="center",
),
ColumnSpec(
renderer="description", # Use attribute name
overflow="fold",
),
]
)

return columns


# Create custom columns
spec = DefaultFormatter(
table_spec=TableSpec(show_header=False),
column_specs=columns, # type: ignore
panel_spec=PanelSpec(border_style="bright_black"),
)

import evalio
from evalio.datasets import set_data_dir

# import typer apps
from .dataset_manager import app as app_dl
from .ls import app as app_ls
from .run import app as app_run
from .stats import app as app_stats
# ------------------------- Make CLI App ------------------------- #
mg = Group("Misc", sort_key=1)
gg = Group("Global Options", sort_key=100)

app = typer.Typer(
app = App(
help="Tool for evaluating Lidar-Inertial Odometry pipelines on open-source datasets",
rich_markup_mode="rich",
no_args_is_help=True,
pretty_exceptions_enable=False,
help_formatter=spec,
help_on_error=True,
default_parameter=Parameter(negative=""),
version_flags=[],
)

app.add_typer(app_dl)
app.add_typer(app_ls)
app.add_typer(app_run)
app.add_typer(app_stats)
# Register commands
app.register_install_completion_command(add_to_startup=True) # type: ignore
app.command("evalio.cli.ls:ls")
app.command("evalio.cli.dataset_manager:dl")
app.command("evalio.cli.dataset_manager:rm")
app.command("evalio.cli.dataset_manager:filter")
app.command("evalio.cli.stats:evaluate_cli", name="stats")
app.command("evalio.cli.run:run_from_cli", name="run")

# Assign groups
app["--install-completion"].group = mg
app["--help"].group = gg

def version_callback(value: bool):
"""
Show version and exit.
"""
if value:
print(evalio.__version__)
raise typer.Exit()
for c in app:
if c in ["--help", "-h"]:
continue
app[c]["--help"].group = gg


def data_callback(value: Optional[Path]):
@app.command(name="--show-completion", group=mg)
def show_completion():
"""
Set the data directory.
Show shell completion script.
"""
if value is not None:
set_data_dir(value)
comp = app.generate_completion()
# zsh needs an extra line
if detect_shell() == "zsh":
comp += "compdef _evalio evalio"
# Fix wildcard completions
comp = comp.replace("/*", "/\\*")
print(comp)


def module_callback(value: Optional[list[str]]) -> list[Any]:
@app.command(name="--version", alias="-V", group=mg)
def version():
"""
Set the module to use.
Show version and exit.
"""
if value is not None:
for module in value:
evalio._register_custom_modules(module)
import evalio

return []
print(evalio.__version__)


@app.callback()
@app.meta.default
def global_options(
# Marking this as a str for now to get autocomplete to work,
# Once this fix is released (hasn't been as of 0.15.2), we can change it to a Path
# https://github.com/fastapi/typer/pull/1138
data_dir: Annotated[
Optional[Path],
typer.Option(
"-D",
"--data-dir",
help="Directory to store downloaded datasets.",
show_default=False,
rich_help_panel="Global options",
callback=data_callback,
),
] = None,
custom_modules: Annotated[
Optional[list[str]],
typer.Option(
"-M",
"--module",
help="Custom module to load (for custom datasets or pipelines). Can be used multiple times.",
show_default=False,
rich_help_panel="Global options",
callback=module_callback,
),
] = None,
version: Annotated[
bool,
typer.Option(
"--version",
"-V",
help="Show version and exit.",
is_eager=True,
show_default=False,
callback=version_callback,
),
] = False,
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
module: Annotated[Optional[list[str]], Parameter(alias="-M", group=gg)] = None,
data_dir: Annotated[Optional[Path], Parameter(alias="-D", group=gg)] = None,
):
"""Define a number of global options that are set before any command is run.

Args:
module (list[str]): Custom module to import. Can be repeated.
data_dir (Optional[Path]): Directory to store downloaded datasets.
"""
Global options for the evalio CLI.
"""
pass
# Register custom modules
if module is not None:
from evalio import _register_custom_modules

for m in module:
_register_custom_modules(m)

# Set data directory
if data_dir is not None:
from evalio.datasets import set_data_dir

set_data_dir(data_dir)

app(tokens)


def launch():
app.meta()


__all__ = [
"app",
]

if __name__ == "__main__":
app()
Loading