diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 710b6f8..592c0f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,10 @@ name: CI on: push: pull_request: + workflow_dispatch: + schedule: + # Run optional/heavier integration tests weekly and on demand. + - cron: "0 3 * * 1" jobs: test: @@ -32,7 +36,7 @@ jobs: run: ruff check . # Optional but recommended: verifies console scripts + dispatch without launching GUI. - # NOTE: calling plain `cosmo` (no args) attempts to start the GUI by default. [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + # NOTE: calling plain `cosmo` (no args) attempts to start the GUI by default. - name: CLI sanity check (no GUI) run: | cosmo --version @@ -74,4 +78,4 @@ jobs: - name: Run integration tests env: MPLBACKEND: Agg - run: pytest -q -m integration \ No newline at end of file + run: pytest -q -m integration diff --git a/README.md b/README.md index 8300192..4513c13 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ python -m pip install -e . --group dev > tools explicitly: `pip install -e . pytest ruff` (adding `betterosi mcap` for > MCAP integration tests). -> Tip 1: The **GUI** is launced with `cosmo` or `cosmo gui`. -> Tip 2: During development with downloaded repro, all `cosmo` commands can be replaced with `python run_cosmo.py ...`, `cosmo.cmd ...` or `cosmo.ps1 ...`. `python run_gui.py` always starts the **GUI**. +> Tip 1: The **GUI** is launched with `cosmo` or `cosmo gui`. +> Tip 2: In headless environments, prefer explicit subcommands such as `cosmo convert ...` rather than plain `cosmo`, which may start the GUI. Convert using [ORBIT](https://github.com/RI-SE/ORBIT) georef as the primary pixel→ground mapping (**Convert** tab in the **GUI**): @@ -81,7 +81,7 @@ Calibration outputs are written to outputs/ as: - _calibration.json - _homography_fit_summary.json - _homography_fit_residuals.png -- _overlay_markers_on_image.png (only if --image is provided) +- _overlay_markers_on_image.png (only if --image is provided) Use the calibration file for conversion fallback: diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index c40dfd5..5fe42e7 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -10,17 +10,16 @@ cosmo cosmo gui ``` -Development shortcut (either of): +Use the installed console script: ```bash -python run_gui.py # Start GUI -python run_cosmo.py gui # Same behaviour as cosmo (when installed) -cosmo.cmd gui # Same behaviour in Windows cmd termial as cosmo (when installed) -cosmo.ps1 gui # Same behaviour in Windows PowerShell termial as cosmo (when installed) +cosmo gui +# or simply: +cosmo ``` --- ## CLI -> During development `cosmo ...` can be replaced with `python run_cosmo ...`, `cosmo.exe ...` or `cosmo.ps1 ...` without needing to install cosmo, e.g., `python run_cosmo.py convert ...`. To install for development use `uv sync --group dev` or `pip install -e . --group dev`. +> For development, install the package first (for example `uv sync --group dev` or `pip install -e . --group dev`) and then use the `cosmo ...` console script. --- ### Convert (recommended: ORBIT georef) diff --git a/docs/how-to/calibration.md b/docs/how-to/calibration.md index 035403f..2eb7ba8 100644 --- a/docs/how-to/calibration.md +++ b/docs/how-to/calibration.md @@ -17,7 +17,7 @@ The calibration JSON is written as: Other helpful artifacts: - _homography_fit_summary.json - _homography_fit_residuals.png -- _overlay_markers_on_image.png (only if --image was supplied) +- _overlay_markers_on_image.png (only if --image was supplied) ## Step 3 — Convert using calibration @@ -31,4 +31,4 @@ cosmo convert scenario.json \ If results look wrong, check the calibration run’s run_summary.json for: - RMSE and inlier count -- notes about missing plots/overlays +- notes about missing plots/overlays diff --git a/docs/reference/inputs-openlabel.md b/docs/reference/inputs-openlabel.md index ac7e38f..e776e92 100644 --- a/docs/reference/inputs-openlabel.md +++ b/docs/reference/inputs-openlabel.md @@ -1,6 +1,6 @@ # Input: OpenLABEL (expected structure) -COSMO reads OpenLABEL JSON and extracts object metadata and per-frame rotated bounding boxes (rbbox). +COSMO reads OpenLABEL JSON and extracts object metadata and per-frame rotated bounding boxes (rbbox). ## Required content COSMO expects, per frame, objects that contain an `rbbox` with at least five values: diff --git a/docs/reference/outputs-omega-prime.md b/docs/reference/outputs-omega-prime.md index f4fefdb..a83041a 100644 --- a/docs/reference/outputs-omega-prime.md +++ b/docs/reference/outputs-omega-prime.md @@ -1,6 +1,6 @@ # Outputs: Omega‑Prime CSV + OSI/MCAP -COSMO writes all outputs inside a per-run folder under `runs/` by default. +COSMO writes all outputs inside a per-run folder under `runs/` by default. --- diff --git a/docs/user_guide/cli.md b/docs/user_guide/cli.md index 890e2f1..22e6a9d 100644 --- a/docs/user_guide/cli.md +++ b/docs/user_guide/cli.md @@ -61,7 +61,7 @@ cosmo convert scenario.json \ --xy-offset 1.2 -0.5 \ -o runs/ ``` -Available flags: `--swap-xy`, `--flip-x`, `--flip-y`, `--xy-offset DX DY`, `--yaw-offset-deg DEG`. +Available flags: `--swap-xy`, `--flip-x`, `--flip-y`, `--xy-offset DX DY`, `--yaw-offset-deg DEG`. ### Run naming & JSON output ```bash cosmo convert scenario.json --run-name saro_roundabout -o runs/ @@ -144,7 +144,7 @@ cosmo calibrate --inputs pixel_pairs.csv visual_markers.csv map.xodr \ ##### Useful knobs - --ransac-thresh-m (default 0.50) — RANSAC distance threshold in meters --fps, -- --image-width, --image-height +- --image-width, --image-height ##### Calibration outputs Calibration writes stem-based files under outputs/: @@ -152,5 +152,5 @@ Calibration writes stem-based files under outputs/: - _calibration.json - _homography_fit_summary.json - _homography_fit_residuals.png -- _overlay_markers_on_image.png (only if --image is provided) +- _overlay_markers_on_image.png (only if --image is provided) diff --git a/src/cosmo/converters/openlabel_to_omega.py b/src/cosmo/converters/openlabel_to_omega.py index d4e4cb9..baf713b 100644 --- a/src/cosmo/converters/openlabel_to_omega.py +++ b/src/cosmo/converters/openlabel_to_omega.py @@ -721,7 +721,7 @@ def _collect_all_dims() -> Dict[str, Tuple[float, float, float]]: # ---------------------------- # IMPORTANT: # Use betterosi.Writer as a context manager to ensure MCAP footer/summary are finalized properly. [1](https://deepwiki.com/ika-rwth-aachen/omega-prime/3.1-loading-and-saving-recordings)[4](https://ika-rwth-aachen.github.io/omega-prime/notebooks/tutorial/) - # Also write topic names without leading "/" to match common omega-prime usage. [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py)[2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # Also write topic names without leading "/" to match common omega-prime usage. def _write_map(writer_mcap): if odr_path and os.path.isfile(odr_path): @@ -752,7 +752,7 @@ def _write_ground_truth(writer_mcap, t_sec: float, total_nanos: int, moving_obje gt.proj_string = _gt_proj_string if _gt_country_code: gt.country_code = _gt_country_code - # Provide log_time explicitly; readers often build indices from it. [4](https://ika-rwth-aachen.github.io/omega-prime/notebooks/tutorial/)[3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + # Provide log_time explicitly; readers often build indices from it. [4](https://ika-rwth-aachen.github.io/omega-prime/notebooks/tutorial/) writer_mcap.add(gt, topic="ground_truth", log_time=total_nanos) # If MCAP enabled, write with context manager; else only build CSV rows diff --git a/src/cosmo/gui/main_window.py b/src/cosmo/gui/main_window.py index a462d4e..8e99ddc 100644 --- a/src/cosmo/gui/main_window.py +++ b/src/cosmo/gui/main_window.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import Optional -# Qt binding selection: PyQt5 → PySide6 → PyQt6 (same as your current GUI). [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_openlabel_to_omega.py) +# Qt binding selection: PyQt5 → PySide6 → PyQt6 (same as your current GUI). try: from PyQt5 import QtCore, QtGui, QtWidgets _QT_API = "PyQt5" diff --git a/src/cosmo/gui/workers.py b/src/cosmo/gui/workers.py index 16ad7a2..a884056 100644 --- a/src/cosmo/gui/workers.py +++ b/src/cosmo/gui/workers.py @@ -3,7 +3,7 @@ from typing import Callable -# Qt binding selection (same preference order as your current GUI) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_openlabel_to_omega.py) +# Qt binding selection (same preference order as your current GUI) try: from PyQt5 import QtCore _Signal = QtCore.pyqtSignal diff --git a/tests/cli_helpers.py b/tests/cli_helpers.py index 8576204..5d8643c 100644 --- a/tests/cli_helpers.py +++ b/tests/cli_helpers.py @@ -21,10 +21,10 @@ def run_cosmo(args: list[str], *, cwd: Path) -> subprocess.CompletedProcess[str] env.setdefault("MPLBACKEND", "Agg") if os.name == "nt": - cmd = [sys.executable, "-m", "cosmo.cli.main", *args] # real entrypoint logic [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + cmd = [sys.executable, "-m", "cosmo.cli.main", *args] # real entrypoint logic else: exe = shutil.which("cosmo") - cmd = [exe, *args] if exe else [sys.executable, "-m", "cosmo.cli.main", *args] # same entrypoint [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + cmd = [exe, *args] if exe else [sys.executable, "-m", "cosmo.cli.main", *args] # same entrypoint return subprocess.run(cmd, cwd=str(cwd), capture_output=True, text=True, env=env) diff --git a/tests/conftest.py b/tests/conftest.py index 68388c3..3f80e69 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,17 +16,17 @@ def run_cosmo(args: list[str], *, cwd: Path) -> subprocess.CompletedProcess[str] Fallback: if the console script isn't on PATH (rare in CI), run via module entrypoint: python -m cosmo.cli.main - NOTE: Never call plain `cosmo` with no args in CI, because cosmo.cli.main defaults to GUI. [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + NOTE: Never call plain `cosmo` with no args in CI, because cosmo.cli.main defaults to GUI. """ env = dict(os.environ) - env.setdefault("MPLBACKEND", "Agg") # CI already sets this, but keep it deterministic. [1](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + env.setdefault("MPLBACKEND", "Agg") # CI already sets this, but keep it deterministic. exe = shutil.which("cosmo") if exe: cmd = [exe, *args] else: # Still the real entrypoint, just executed as a module. - cmd = [os.environ.get("PYTHON", "python"), "-m", "cosmo.cli.main", *args] # dispatch logic in main.py [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + cmd = [os.environ.get("PYTHON", "python"), "-m", "cosmo.cli.main", *args] # dispatch logic in main.py return subprocess.run(cmd, cwd=str(cwd), capture_output=True, text=True, env=env) diff --git a/tests/test_calibrate_cli_validation.py b/tests/test_calibrate_cli_validation.py index 0740b11..9eefbb6 100644 --- a/tests/test_calibrate_cli_validation.py +++ b/tests/test_calibrate_cli_validation.py @@ -20,4 +20,4 @@ def test_calibrate_rejects_mixed_input_styles(tmp_path: Path): cwd=tmp_path, ) assert res.returncode != 0 - assert "Mixed input styles detected" in (res.stderr + res.stdout) # error text comes from argparse error [2](blob:https://www.microsoft365.com/c655cd7d-9ac4-4928-84e7-b9a8f04405bd) + assert "Mixed input styles detected" in (res.stderr + res.stdout) # error text comes from argparse error diff --git a/tests/test_calibration_compute_whitebox.py b/tests/test_calibration_compute_whitebox.py index 0a54fc0..48f87c8 100644 --- a/tests/test_calibration_compute_whitebox.py +++ b/tests/test_calibration_compute_whitebox.py @@ -6,7 +6,7 @@ import numpy as np import pytest -from cosmo.calibration.compute import ( # [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) +from cosmo.calibration.compute import ( # compute_calibration, write_calibration_outputs, ) @@ -75,7 +75,7 @@ def test_write_calibration_outputs_writes_expected_json_schema(tmp_path: Path): fps=30.0, image_width=3840, image_height=2160, - residuals_png_path=str(resid_png), # optional; may become None on plot errors [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + residuals_png_path=str(resid_png), # optional; may become None on plot errors overlay_png_path=None, image_path=None, openlabel_path=None, @@ -85,7 +85,7 @@ def test_write_calibration_outputs_writes_expected_json_schema(tmp_path: Path): assert Path(outs.summary_json_path).is_file() calib = json.loads(Path(outs.calibration_json_path).read_text(encoding="utf-8")) - # Required keys written by write_calibration_outputs [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + # Required keys written by write_calibration_outputs for k in ("fps", "image_width", "image_height", "homography", "intrinsics", "extrinsics", "default_dimensions_m"): assert k in calib H = calib["homography"] @@ -95,7 +95,7 @@ def test_write_calibration_outputs_writes_expected_json_schema(tmp_path: Path): for k in ("rmse_m", "inliers_count", "pairs_used", "pixel_points", "world_points_ENU_m", "homography"): assert k in summary - # Residual plot can be omitted if matplotlib errors; if present, ensure file exists [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + # Residual plot can be omitted if matplotlib errors; if present, ensure file exists if outs.residuals_png_path is not None: assert Path(outs.residuals_png_path).is_file() diff --git a/tests/test_cli_contracts_not_smoke.py b/tests/test_cli_contracts_not_smoke.py index 6df2b00..1c25858 100644 --- a/tests/test_cli_contracts_not_smoke.py +++ b/tests/test_cli_contracts_not_smoke.py @@ -14,7 +14,7 @@ def test_calibrate_rejects_mixed_input_styles(tmp_path: Path): vm.write_text("point_name,E,N\np1,0,0\np2,1,0\np3,0,1\np4,1,1\n", encoding="utf-8") odr.write_text("", encoding="utf-8") - # Mix two styles intentionally -> should error (logic is in calibrate.py) [2](blob:https://www.microsoft365.com/c655cd7d-9ac4-4928-84e7-b9a8f04405bd) + # Mix two styles intentionally -> should error (logic is in calibrate.py) res = run_cosmo( ["calibrate", "--inputs", str(pp), str(vm), str(odr), "--pixel-pairs", str(pp)], cwd=tmp_path, @@ -27,7 +27,7 @@ def test_convert_rejects_positional_and_flag_input(tmp_path: Path): ol = tmp_path / "openlabel.json" ol.write_text(json.dumps({"openlabel": {"objects": {}, "frames": {}}}), encoding="utf-8") - # convert.py rejects positional + --input together [1](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + # convert.py rejects positional + --input together res = run_cosmo(["convert", str(ol), "--input", str(ol), "--no-mcap"], cwd=tmp_path) assert res.returncode != 0 assert "Provide either a positional input OR --input/--openlabel" in (res.stderr + res.stdout) diff --git a/tests/test_cli_smoke_calibrate.py b/tests/test_cli_smoke_calibrate.py index 30da43d..423a823 100644 --- a/tests/test_cli_smoke_calibrate.py +++ b/tests/test_cli_smoke_calibrate.py @@ -14,7 +14,7 @@ def test_cli_calibrate_smoke(tmp_path: Path): Smoke test for the real CLI: cosmo calibrate --inputs pixel_pairs.csv visual_markers.csv map.xodr --json - Uses --inputs style and --json output exactly as supported by cosmo.cli.calibrate. [3](blob:https://www.microsoft365.com/c655cd7d-9ac4-4928-84e7-b9a8f04405bd) + Uses --inputs style and --json output exactly as supported by cosmo.cli.calibrate. """ # Minimal input CSVs pixel_pairs_csv = tmp_path / "pixel_pairs.csv" @@ -39,7 +39,7 @@ def test_cli_calibrate_smoke(tmp_path: Path): encoding="utf-8", ) - # Dummy OpenDRIVE file (required arg in CLI contract) [3](blob:https://www.microsoft365.com/c655cd7d-9ac4-4928-84e7-b9a8f04405bd) + # Dummy OpenDRIVE file (required arg in CLI contract) odr_path = tmp_path / "map.xodr" odr_path.write_text("", encoding="utf-8") @@ -78,7 +78,7 @@ def test_cli_calibrate_smoke(tmp_path: Path): payload = extract_json_from_stdout(res.stdout) - # CLI prints asdict(result) when --json is provided. [3](blob:https://www.microsoft365.com/c655cd7d-9ac4-4928-84e7-b9a8f04405bd) + # CLI prints asdict(result) when --json is provided. run_dir = Path(payload["run_dir"]) outputs_dir = Path(payload.get("outputs_dir") or payload["run_dir"]) calib_path = Path(payload["calibration_json_path"]) diff --git a/tests/test_cli_smoke_convert.py b/tests/test_cli_smoke_convert.py index 9bb1062..df7826d 100644 --- a/tests/test_cli_smoke_convert.py +++ b/tests/test_cli_smoke_convert.py @@ -14,7 +14,7 @@ def test_cli_convert_smoke_csv_only(tmp_path: Path): Smoke test for the real CLI: cosmo convert --input openlabel.json --calibration Calibration.json --no-mcap --json - Uses flags from cosmo.cli.convert and disables MCAP for CI stability. [1](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + Uses flags from cosmo.cli.convert and disables MCAP for CI stability. """ # Minimal OpenLABEL input (one object, one frame, rbbox) openlabel = { @@ -64,7 +64,7 @@ def test_cli_convert_smoke_csv_only(tmp_path: Path): str(out_dir), "--run-name", "smoke_convert", - "--no-mcap", # convert CLI uses BooleanOptionalAction; --no-mcap disables MCAP. [1](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + "--no-mcap", # convert CLI uses BooleanOptionalAction; --no-mcap disables MCAP. "--json", ], cwd=tmp_path, diff --git a/tests/test_console_scripts_smoke.py b/tests/test_console_scripts_smoke.py index c2c94b9..6527104 100644 --- a/tests/test_console_scripts_smoke.py +++ b/tests/test_console_scripts_smoke.py @@ -50,19 +50,19 @@ def test_console_scripts_exist_and_help_works(): assert cosmo_calibrate, "Expected 'cosmo-calibrate' on PATH or in venv bin dir after install." assert cosmo_gui, "Expected 'cosmo-gui' on PATH or in venv bin dir after install." - # Always safe: module entrypoint tests real main.py behavior. [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + # Always safe: module entrypoint tests real main.py behavior. res = _run([sys.executable, "-m", "cosmo.cli.main", "--version"]) assert res.returncode == 0, f"cosmo --version failed.\nSTDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}" assert res.stdout.strip(), "cosmo --version produced no output." - # Dispatch help via main.py (convert/calibrate are subcommands). [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + # Dispatch help via main.py (convert/calibrate are subcommands). res = _run([sys.executable, "-m", "cosmo.cli.main", "convert", "--help"]) assert res.returncode == 0, f"cosmo convert --help failed.\nSTDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}" res = _run([sys.executable, "-m", "cosmo.cli.main", "calibrate", "--help"]) assert res.returncode == 0, f"cosmo calibrate --help failed.\nSTDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}" - # Direct entrypoints: run via module on Windows to avoid blocked .EXE shims. [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py)[2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + # Direct entrypoints: run via module on Windows to avoid blocked .EXE shims. if os.name == "nt": res = _run([sys.executable, "-m", "cosmo.cli.convert", "--help"]) assert res.returncode == 0, f"python -m cosmo.cli.convert --help failed.\nSTDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}" @@ -76,4 +76,4 @@ def test_console_scripts_exist_and_help_works(): res = _run([cosmo_calibrate, "--help"]) assert res.returncode == 0, f"cosmo-calibrate --help failed.\nSTDOUT:\n{res.stdout}\nSTDERR:\n{res.stderr}" - # Do NOT execute cosmo-gui in tests: it launches GUI immediately and can fail headless. [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py)[3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + # Do NOT execute cosmo-gui in tests: it launches GUI immediately and can fail headless. diff --git a/tests/test_convert_alignment_flags.py b/tests/test_convert_alignment_flags.py index 36ba79a..3f52b9a 100644 --- a/tests/test_convert_alignment_flags.py +++ b/tests/test_convert_alignment_flags.py @@ -29,4 +29,4 @@ def test_convert_accepts_alignment_flags(tmp_path: Path): "--no-mcap", "--swap-xy", "--flip-x", "--flip-y", "--xy-offset", "1.0", "-2.0", "--yaw-offset-deg", "90"], cwd=tmp_path, ) - assert res.returncode == 0 # should be accepted and run successfully [3](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + assert res.returncode == 0 # should be accepted and run successfully diff --git a/tests/test_convert_app_e2e_not_smoke.py b/tests/test_convert_app_e2e_not_smoke.py index d5ce9e1..bfa902f 100644 --- a/tests/test_convert_app_e2e_not_smoke.py +++ b/tests/test_convert_app_e2e_not_smoke.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from cosmo.app.convert_app import ( # [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) +from cosmo.app.convert_app import ( # ConvertConfig, run_convert, ) diff --git a/tests/test_convert_cli_validation.py b/tests/test_convert_cli_validation.py index d9d8f6e..ce24958 100644 --- a/tests/test_convert_cli_validation.py +++ b/tests/test_convert_cli_validation.py @@ -16,4 +16,4 @@ def test_convert_rejects_positional_and_flag_input(tmp_path: Path): cwd=tmp_path, ) assert res.returncode != 0 - assert "Provide either a positional input OR --input/--openlabel" in (res.stderr + res.stdout) # emitted by ap.error [3](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + assert "Provide either a positional input OR --input/--openlabel" in (res.stderr + res.stdout) # emitted by ap.error diff --git a/tests/test_convert_no_scripts_fallback.py b/tests/test_convert_no_scripts_fallback.py index fa60d78..6e2d9e1 100644 --- a/tests/test_convert_no_scripts_fallback.py +++ b/tests/test_convert_no_scripts_fallback.py @@ -2,7 +2,7 @@ from __future__ import annotations from cosmo.app.convert_app import ( - _get_converter_or_raise, # internal helper [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + _get_converter_or_raise, # internal helper ) @@ -11,7 +11,7 @@ def test_converter_import_has_clear_error_message(): fn = _get_converter_or_raise() assert callable(fn) except RuntimeError as e: - # If it fails, ensure the error message guides the developer correctly. [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + # If it fails, ensure the error message guides the developer correctly. msg = str(e) assert "COSMO converter implementation not found" in msg assert "cosmo.converters.openlabel_to_omega" in msg diff --git a/tests/test_convert_outputs.py b/tests/test_convert_outputs.py index a778def..6aa8355 100644 --- a/tests/test_convert_outputs.py +++ b/tests/test_convert_outputs.py @@ -32,4 +32,4 @@ def test_convert_no_csv_reports_no_csv_path(tmp_path: Path): ) assert res.returncode == 0 payload = extract_json_from_stdout(res.stdout) - assert payload.get("csv_path") in (None, "", False) # run_convert should reflect csv disabled [3](blob:https://www.microsoft365.com/3377b2c7-ce90-4a5f-bcf4-96fccf281101) + assert payload.get("csv_path") in (None, "", False) # run_convert should reflect csv disabled diff --git a/tests/test_core_calibrate_whitebox.py b/tests/test_core_calibrate_whitebox.py index 570a3d6..712c509 100644 --- a/tests/test_core_calibrate_whitebox.py +++ b/tests/test_core_calibrate_whitebox.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from cosmo.app.calibrate_app import ( # app layer API [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) +from cosmo.app.calibrate_app import ( # app layer API CalibrateConfig, run_calibrate, ) @@ -34,7 +34,7 @@ def test_run_calibrate_writes_stem_based_outputs(tmp_path: Path): opendrive = tmp_path / "map.xodr" opendrive.write_text("", encoding="utf-8") - # Deterministic run folder: existing out_dir + run_name => out_dir/run_name [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + # Deterministic run folder: existing out_dir + run_name => out_dir/run_name runs_dir = tmp_path / "runs" runs_dir.mkdir() @@ -55,7 +55,7 @@ def test_run_calibrate_writes_stem_based_outputs(tmp_path: Path): ) logs: list[str] = [] - result = run_calibrate(cfg, log_fn=logs.append) # same signature used by CLI [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + result = run_calibrate(cfg, log_fn=logs.append) # same signature used by CLI run_dir = Path(result.run_dir) outputs_dir = Path(result.outputs_dir) @@ -63,7 +63,7 @@ def test_run_calibrate_writes_stem_based_outputs(tmp_path: Path): assert outputs_dir == run_dir / "outputs" assert outputs_dir.exists() - # Output naming is stem-based: _calibration.json etc. [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + # Output naming is stem-based: _calibration.json etc. calib_path = Path(result.calibration_json_path) assert calib_path.is_file() assert calib_path.name.endswith("_calibration.json") @@ -73,15 +73,15 @@ def test_run_calibrate_writes_stem_based_outputs(tmp_path: Path): H = calib["homography"] assert isinstance(H, list) and len(H) == 3 and all(len(r) == 3 for r in H) - # Summary JSON should be produced (unless compute/write changes) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + # Summary JSON should be produced (unless compute/write changes) if result.summary_json_path: assert Path(result.summary_json_path).is_file() - # Residual plot is intended but can be None (matplotlib issues), so keep optional. [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + # Residual plot is intended but can be None (matplotlib issues), so keep optional. if result.residuals_png_path: assert Path(result.residuals_png_path).is_file() - # No overlay expected because image=None [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/calibrate_app.py) + # No overlay expected because image=None assert result.overlay_png_path is None # Some logging should occur (useful diagnostic) diff --git a/tests/test_core_convert_whitebox.py b/tests/test_core_convert_whitebox.py index 9226017..c2fd688 100644 --- a/tests/test_core_convert_whitebox.py +++ b/tests/test_core_convert_whitebox.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from cosmo.app.convert_app import ( # app layer API [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) +from cosmo.app.convert_app import ( # app layer API ConvertConfig, run_convert, ) @@ -31,7 +31,7 @@ def test_run_convert_writes_csv_and_sets_csv_path(tmp_path: Path): openlabel_path = tmp_path / "openlabel.json" openlabel_path.write_text(json.dumps(openlabel), encoding="utf-8") - # Minimal calibration file path (convert_app passes this to converter if provided) [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + # Minimal calibration file path (convert_app passes this to converter if provided) calib = { "fps": 30.0, "image_width": 3840, @@ -51,7 +51,7 @@ def test_run_convert_writes_csv_and_sets_csv_path(tmp_path: Path): calibration=str(calib_path), fps=None, write_csv=True, - write_mcap=False, # keep core tests independent of optional MCAP deps [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + write_mcap=False, # keep core tests independent of optional MCAP deps swap_xy=False, flip_x=False, flip_y=False, @@ -62,7 +62,7 @@ def test_run_convert_writes_csv_and_sets_csv_path(tmp_path: Path): ) logs: list[str] = [] - result = run_convert(cfg, log_fn=logs.append) # same signature used by CLI [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + result = run_convert(cfg, log_fn=logs.append) # same signature used by CLI run_dir = Path(result.run_dir) outputs_dir = Path(result.outputs_dir) @@ -70,7 +70,7 @@ def test_run_convert_writes_csv_and_sets_csv_path(tmp_path: Path): assert outputs_dir == run_dir / "outputs" assert outputs_dir.exists() - # csv_path is only set if the file exists (summary filters non-existent files) [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + # csv_path is only set if the file exists (summary filters non-existent files) assert result.csv_path is not None csv_path = Path(result.csv_path) assert csv_path.is_file() @@ -103,7 +103,7 @@ def test_run_convert_no_csv_results_in_none_csv_path(tmp_path: Path): write_mcap=False, out_dir=str(runs_dir), run_name="wb_no_csv", - ) # relies on defaults for other fields [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/convert_app.py) + ) # relies on defaults for other fields result = run_convert(cfg, log_fn=lambda _: None) assert result.csv_path is None diff --git a/tests/test_integration_mcap_convert_app.py b/tests/test_integration_mcap_convert_app.py index 625494f..b181215 100644 --- a/tests/test_integration_mcap_convert_app.py +++ b/tests/test_integration_mcap_convert_app.py @@ -5,7 +5,7 @@ import pytest -from cosmo.app.convert_app import ( # [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) +from cosmo.app.convert_app import ( # ConvertConfig, run_convert, ) @@ -20,7 +20,7 @@ def _has_betterosi() -> bool: @pytest.mark.integration -@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; MCAP integration test skipped") # [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) +@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; MCAP integration test skipped") # def test_run_convert_app_writes_mcap_and_sets_result_path(tmp_path: Path): # Minimal OpenLABEL openlabel = { @@ -36,7 +36,7 @@ def test_run_convert_app_writes_mcap_and_sets_result_path(tmp_path: Path): calib_path = tmp_path / "Calibration.json" calib_path.write_text(json.dumps({"fps": 10.0, "homography": [[1, 0, 0], [0, 1, 0], [0, 0, 1]]}), encoding="utf-8") - # OpenDRIVE embedded flag in convert_app summary is based on cfg.opendrive [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + # OpenDRIVE embedded flag in convert_app summary is based on cfg.opendrive odr_path = tmp_path / "map.xodr" odr_path.write_text("", encoding="utf-8") @@ -51,14 +51,14 @@ def test_run_convert_app_writes_mcap_and_sets_result_path(tmp_path: Path): write_mcap=True, out_dir=str(runs_dir), run_name="int_mcap", - ) # config matches app layer [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + ) # config matches app layer result = run_convert(cfg, log_fn=None) assert Path(result.run_dir).exists() assert Path(result.outputs_dir).exists() - # convert_app only sets mcap_path if file exists [3](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + # convert_app only sets mcap_path if file exists assert result.mcap_path is not None assert Path(result.mcap_path).is_file() assert Path(result.mcap_path).stat().st_size > 0 diff --git a/tests/test_integration_mcap_converter.py b/tests/test_integration_mcap_converter.py index eab2a48..a770f68 100644 --- a/tests/test_integration_mcap_converter.py +++ b/tests/test_integration_mcap_converter.py @@ -6,7 +6,7 @@ import pytest from cosmo.converters.openlabel_to_omega import ( - convert_openlabel_to_omega, # [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + convert_openlabel_to_omega, # ) @@ -37,7 +37,7 @@ def _try_read_mcap_topics(mcap_path: Path) -> set[str]: @pytest.mark.integration -@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; MCAP integration test skipped") # [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) +@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; MCAP integration test skipped") # def test_converter_writes_mcap_with_ground_truth_and_map(tmp_path: Path): # Minimal OpenLABEL with two frames (ensures multiple GT messages) openlabel = { @@ -67,7 +67,7 @@ def test_converter_writes_mcap_with_ground_truth_and_map(tmp_path: Path): encoding="utf-8", ) - # Provide OpenDRIVE so map embedding path is exercised (ground_truth_map topic) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # Provide OpenDRIVE so map embedding path is exercised (ground_truth_map topic) odr_path = tmp_path / "map.xodr" odr_path.write_text("", encoding="utf-8") @@ -82,7 +82,7 @@ def test_converter_writes_mcap_with_ground_truth_and_map(tmp_path: Path): georef_data_path=None, fps_arg=None, write_csv=False, # focus on MCAP in this integration test - write_mcap=True, # requires betterosi [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + write_mcap=True, # requires betterosi swap_xy=False, flip_x=False, flip_y=False, @@ -98,6 +98,6 @@ def test_converter_writes_mcap_with_ground_truth_and_map(tmp_path: Path): # Optional deeper validation if the `mcap` reader is installed: topics = _try_read_mcap_topics(mcap_path) if topics: - # Converter writes these topics without leading '/' [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # Converter writes these topics without leading '/' assert "ground_truth" in topics assert "ground_truth_map" in topics diff --git a/tests/test_integration_mcap_gt_count.py b/tests/test_integration_mcap_gt_count.py index 7d7d2e4..fffca3c 100644 --- a/tests/test_integration_mcap_gt_count.py +++ b/tests/test_integration_mcap_gt_count.py @@ -23,7 +23,7 @@ import pytest -from cosmo.app.convert_app import ( # app layer [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) +from cosmo.app.convert_app import ( # app layer ConvertConfig, run_convert, ) @@ -62,7 +62,7 @@ def _count_topic_messages(mcap_path: Path, topic: str) -> int: @pytest.mark.integration -@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; cannot write MCAP") # converter depends on it [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) +@pytest.mark.skipif(not _has_betterosi(), reason="betterosi not installed; cannot write MCAP") # converter depends on it @pytest.mark.skipif(not has_mcap_reader(), reason="mcap reader not installed; cannot inspect MCAP") def test_mcap_gt_count_map_once_and_strictly_increasing_log_time(tmp_path: Path): # Create OpenLABEL with a known number of frames. @@ -104,7 +104,7 @@ def test_mcap_gt_count_map_once_and_strictly_increasing_log_time(tmp_path: Path) encoding="utf-8", ) - # OpenDRIVE included so map is embedded as `ground_truth_map` (written once with log_time=0) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # OpenDRIVE included so map is embedded as `ground_truth_map` (written once with log_time=0) odr_path = tmp_path / "map.xodr" odr_path.write_text("", encoding="utf-8") @@ -119,10 +119,10 @@ def test_mcap_gt_count_map_once_and_strictly_increasing_log_time(tmp_path: Path) write_mcap=True, out_dir=str(runs_dir), run_name="mcap_gt_count", - ) # app layer writes into per-run outputs/ folder [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + ) # app layer writes into per-run outputs/ folder result = run_convert(cfg, log_fn=None) - assert result.mcap_path, "Expected mcap_path to be set when MCAP is written" # app layer sets only if file exists [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/compute.py) + assert result.mcap_path, "Expected mcap_path to be set when MCAP is written" # app layer sets only if file exists mcap_path = assert_mcap_nonempty(result.mcap_path) @@ -130,11 +130,11 @@ def test_mcap_gt_count_map_once_and_strictly_increasing_log_time(tmp_path: Path) gt_times = _collect_topic_log_times(mcap_path, "ground_truth") assert len(gt_times) == len(frame_ids), f"Expected {len(frame_ids)} ground_truth messages, got {len(gt_times)}" - # 2) Map message written exactly once at log_time=0 (converter writes once) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # 2) Map message written exactly once at log_time=0 (converter writes once) map_times = _collect_topic_log_times(mcap_path, "ground_truth_map") assert len(map_times) == 1, f"Expected exactly 1 ground_truth_map message, got {len(map_times)}" assert map_times[0] == 0, f"Expected ground_truth_map log_time=0, got {map_times[0]}" - # 3) GroundTruth log_time strictly increasing (converter uses log_time=total_nanos) [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # 3) GroundTruth log_time strictly increasing (converter uses log_time=total_nanos) assert gt_times == sorted(gt_times), f"ground_truth log_time not non-decreasing: {gt_times}" assert all(b > a for a, b in zip(gt_times, gt_times[1:])), f"ground_truth log_time not strictly increasing: {gt_times}" diff --git a/tests/test_main_dispatch.py b/tests/test_main_dispatch.py index c784b48..8dc4554 100644 --- a/tests/test_main_dispatch.py +++ b/tests/test_main_dispatch.py @@ -14,5 +14,5 @@ def test_main_version_works_without_gui(): text=True, env={**os.environ, "MPLBACKEND": "Agg"}, ) - assert res.returncode == 0 # main.py returns 0 on --version [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) - assert res.stdout.strip() # prints version string [1](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/main.py) + assert res.returncode == 0 # main.py returns 0 on --version + assert res.stdout.strip() # prints version string diff --git a/tests/test_openlabel_to_omega_whitebox.py b/tests/test_openlabel_to_omega_whitebox.py index 22f267f..1fedbd5 100644 --- a/tests/test_openlabel_to_omega_whitebox.py +++ b/tests/test_openlabel_to_omega_whitebox.py @@ -6,7 +6,7 @@ import numpy as np -from cosmo.converters.openlabel_to_omega import ( # [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) +from cosmo.converters.openlabel_to_omega import ( # convert_openlabel_to_omega, load_alignment, parse_openlabel, @@ -82,7 +82,7 @@ def test_convert_openlabel_to_omega_writes_csv_only(tmp_path: Path): georef_data_path=None, fps_arg=None, write_csv=True, - write_mcap=False, # ensure no betterosi dependency [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + write_mcap=False, # ensure no betterosi dependency swap_xy=False, flip_x=False, flip_y=False, @@ -96,11 +96,11 @@ def test_convert_openlabel_to_omega_writes_csv_only(tmp_path: Path): with csv_path.open(newline="", encoding="utf-8") as f: reader = csv.DictReader(f) - # Columns defined in module [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # Columns defined in module for col in ("total_nanos", "idx", "x", "y", "yaw", "type_name", "subtype_name", "role_name"): assert col in (reader.fieldnames or []) rows = list(reader) assert len(rows) >= 2 - # Ensure time is non-decreasing (rows are sorted by total_nanos) [2](https://risecloud-my.sharepoint.com/personal/anders_thorsen_ri_se/Documents/Microsoft%20Copilot%20Chat%20Files/openlabel_to_omega.py) + # Ensure time is non-decreasing (rows are sorted by total_nanos) times = [int(r["total_nanos"]) for r in rows] assert times == sorted(times)