Skip to content

Add ROS2 MCAP file support (.mcap/.mcap.zstd)#10

Merged
Tiryoh merged 10 commits intomainfrom
feat/mcap-support
Mar 27, 2026
Merged

Add ROS2 MCAP file support (.mcap/.mcap.zstd)#10
Tiryoh merged 10 commits intomainfrom
feat/mcap-support

Conversation

@Tiryoh
Copy link
Copy Markdown
Owner

@Tiryoh Tiryoh commented Mar 25, 2026

Summary

  • Add support for ROS2 MCAP files (.mcap / .mcap.zstd) alongside existing ROS1 .bag files
  • Abstract severity from numeric values to string-based SeverityLevel type ('DEBUG'|'INFO'|'WARN'|'ERROR'|'FATAL') so both ROS1 (1/2/4/8/16) and ROS2 (10/20/30/40/50) are unified
  • MCAP module is dynamically imported for code splitting — bag-only users don't load MCAP code
  • Uses @mcap/core for MCAP reading, @foxglove/rosmsg2-serialization for CDR deserialization, fzstd (pure JS) for zstd decompression

Changed files

  • src/mcapUtils.ts — New MCAP parsing module
  • src/types.tsSeverityLevel string type, ROS1_SEVERITY/ROS2_SEVERITY mappings
  • src/rosbagUtils.tsloadMessages dispatcher, severity string conversion
  • src/App.tsx — Accept .mcap, use loadMessages, severity UI updates
  • src/i18n.ts — Updated file type description
  • e2e/mcap.spec.ts — 20 E2E test cases for MCAP
  • e2e/fixtures/ — MCAP fixture + Dockerfile (ROS2 Jazzy)

Test plan

  • Unit tests: 42 passed (severity type migration verified)
  • E2E (bag): 44 passed (no regression)
  • E2E (MCAP): 20 passed (upload, filters, export, diagnostics)
  • E2E (SQLite): 2 passed (schema update verified)
  • Lint: clean
  • Build: successful, MCAP in separate chunk (127KB gzipped 35KB)

🤖 Generated with Claude Code

- Add MCAP parsing with @mcap/core, @foxglove/rosmsg2-serialization, fzstd
- Abstract severity to string-based SeverityLevel type (ROS1/ROS2 unified)
- Dynamic import for MCAP module to keep bundle split for bag-only users
- Add E2E tests (20 cases) with MCAP fixture generated via ROS2 Jazzy Docker
- Update file accept to .bag,.mcap, i18n text, and CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 25, 2026

Test Results

42 tests  ±0   42 ✅ ±0   0s ⏱️ ±0s
 1 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 800dfff. ± Comparison against base commit fbdd49b.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 25, 2026

Tiryoh and others added 5 commits March 25, 2026 22:35
- Add try/catch with detailed error logging (matching loadRosbagMessages style)
- Show available topics when no rosout/diagnostics topics are found
- Add console logging for load progress and success

Co-Authored-By: Claude Opus 4.6 <[email protected]>
MCAP files recorded by some tools lack the summary/footer section,
causing McapIndexedReader to fail. Detect the footer magic and fall
back to McapStreamReader for incomplete files. Also decompress
whole-file zstd-wrapped .mcap files by detecting the zstd magic at
the start of the buffer.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Replace unreliable hasMcapFooter() check with try/catch fallback to
  streaming reader, since all valid MCAP files share the same trailing
  magic bytes regardless of index presence
- Add state-change deduplication for diagnostics to match ROS1 loader
  behavior, preventing duplicate rows from repeated status samples

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The filter only compared level and message, causing entries with
different values (e.g. packets_received changing) to be incorrectly
skipped. This fixes the e2e M-5-3 name filter test failure.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds ROS2 log/diagnostics ingestion via MCAP (.mcap / .mcap.zstd) alongside existing ROS1 .bag support, and standardizes rosout severity handling across ROS1/ROS2 by moving to a string-based SeverityLevel.

Changes:

  • Introduces MCAP parsing (src/mcapUtils.ts) and a loadMessages() dispatcher to choose ROS1 bag vs ROS2 MCAP at runtime.
  • Migrates rosout severity from numeric codes to SeverityLevel strings and updates exports/UI accordingly.
  • Adds Playwright E2E coverage and fixtures for MCAP, plus updates SQLite export schema and docs.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/types.ts Adds SeverityLevel and ROS1/ROS2 severity mappings; updates color/bg mappings to string keys.
src/rosbagUtils.ts Maps ROS1 numeric severity to SeverityLevel; updates export formats and SQLite schema; adds loadMessages() dispatcher.
src/rosbagUtils.test.ts Updates unit tests for string-based severities and updated SQLite schema.
src/mcapUtils.ts New MCAP reader using @mcap/core, CDR deserialization, and zstd decompression; collects rosout + diagnostics.
src/i18n.ts Updates upload file-type hint to include MCAP extensions.
src/App.tsx Accepts MCAP uploads, uses loadMessages(), and updates severity filtering/UI to SeverityLevel.
package.json Adds dependencies required for MCAP + ROS2 deserialization.
package-lock.json Locks new MCAP/ROS2-related dependencies and transitive updates.
e2e/sqlite-export.spec.ts Updates assertions for new SQLite severity column.
e2e/mcap.spec.ts Adds Playwright E2E coverage for MCAP upload/filter/export/diagnostics.
e2e/fixtures/test_sample.mcap Adds MCAP fixture used by E2E tests.
e2e/fixtures/generate_test_mcap.py Adds generator script for the MCAP test fixture (ROS2).
e2e/fixtures/Dockerfile.mcap Adds Docker image to generate the MCAP fixture using ROS2 Jazzy.
README.md Updates supported formats/versions and dependency list to include MCAP.
CLAUDE.md Updates repo guidance to reflect MCAP support and severity mapping changes.
Comments suppressed due to low confidence (1)

src/i18n.ts:18

  • The English i18n strings still say “Loading bag file…” / “Error loading bag file”, but the app now supports MCAP uploads too. Update these strings (and any similar bag-specific wording) to be format-agnostic (e.g., “Loading file…” / “Error loading file”).
    'loading.message': 'Loading bag file...',
    'error.title': 'Error loading bag file',

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json
Comment on lines 19 to +23
"@foxglove/rosbag": "^0.4.1",
"@foxglove/rosmsg": "^5.0.5",
"@foxglove/rosmsg2-serialization": "^3.0.3",
"@mcap/core": "^2.2.0",
"fzstd": "^0.1.1",
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

@foxglove/rosmsg2-serialization declares engines.node >= 20 (see package-lock), but this repo’s package.json doesn’t declare an engines requirement. Adding an explicit engines field (or documenting the Node >=20 requirement) will prevent confusing install failures for developers on older Node versions.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is a browser-only app — Node is only used for dev tooling. The runtime doesn't depend on Node engine compatibility, so adding an engines field isn't necessary here.

Comment thread src/mcapUtils.ts Outdated
Comment thread src/mcapUtils.ts
Comment on lines +234 to +236
const decompressHandlers: DecompressHandlers = {
zstd: (data) => zstdDecompress(new Uint8Array(data)),
};
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

decompressHandlers only registers a zstd handler. MCAP chunks can also be lz4-compressed; without an lz4 handler those files will fail to load. Consider adding an lz4 handler (you already depend on lz4js) or documenting that only zstd-compressed MCAPs are supported.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Added lz4 handler in 6f09f3f. lz4js was already in dependencies (used by the ROS1 bag loader).

Comment thread src/App.tsx Outdated
Tiryoh and others added 2 commits March 26, 2026 21:48
Add tests for .mcap.zstd (outer zstd compression) and truncated .mcap files
to verify error handling and data recovery. Also update fixture generator to
produce these variants automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
fzstd.decompress() can return a Uint8Array with non-zero byteOffset,
so using .buffer directly caused the MCAP reader to read from wrong
offsets. Use .slice().buffer to get a properly aligned ArrayBuffer.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 19 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/i18n.ts:18

  • UI strings still refer to a "bag file" in loading/error text, but the uploader now supports .mcap/.mcap.zstd too. Please update loading.message and the English error.title to be format-neutral (e.g. "Loading file..." / "Error loading file") to avoid misleading users.
    'upload.fileType': 'ROSbag / MCAP file (.bag, .mcap, .mcap.zstd)',

    // Loading / Error
    'loading.message': 'Loading bag file...',
    'error.title': 'Error loading bag file',

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/mcapUtils.ts Outdated
Comment thread src/mcapUtils.ts Outdated
const magic = new Uint8Array(buffer, 0, 4);
if (magic[0] === 0x28 && magic[1] === 0xb5 && magic[2] === 0x2f && magic[3] === 0xfd) {
const decompressed = zstdDecompress(new Uint8Array(buffer));
buffer = decompressed.slice().buffer as ArrayBuffer;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

buffer = decompressed.slice().buffer makes an additional full copy of the decompressed data. For large .mcap.zstd files this can double peak memory usage. Consider reusing the Uint8Array’s underlying buffer when possible (or only copying when byteOffset/byteLength require it).

Suggested change
buffer = decompressed.slice().buffer as ArrayBuffer;
if (decompressed.byteOffset === 0 && decompressed.byteLength === decompressed.buffer.byteLength) {
buffer = decompressed.buffer as ArrayBuffer;
} else {
buffer = decompressed.slice().buffer as ArrayBuffer;
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 6f09f3f — now checks byteOffset === 0 && byteLength === buffer.byteLength before reusing the backing buffer directly, only copying when needed.

Comment thread src/rosbagUtils.ts
Tiryoh and others added 2 commits March 27, 2026 02:20
…n magic check

- Remove unused channelsById map from McapMessageCollector
- Add lz4 decompression handler for MCAP chunks
- Add buffer.byteLength >= 4 guard before reading magic bytes
- Avoid unnecessary buffer copy when byteOffset is already 0
- Tighten file picker accept to .mcap.zstd instead of .zstd

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…nknown severity

- Fall back to streaming reader when indexed reader returns 0 messages
  (handles unchunked MCAPs where messages are not inside Chunk records)
- Track pending channels and rebuild readers when their schema arrives
  later (handles streaming MCAPs with Channel before Schema order)
- Map unmapped/unknown severity levels to 'UNKNOWN' instead of 'DEBUG'
  to preserve original data fidelity

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
Repository owner deleted a comment from s-daisuke Mar 26, 2026
@Tiryoh Tiryoh merged commit 57fc11c into main Mar 27, 2026
8 checks passed
@Tiryoh Tiryoh deleted the feat/mcap-support branch March 27, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants