phys-ai-dataprofiler 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- phys_ai_dataprofiler-0.3.1/LICENSE +21 -0
- phys_ai_dataprofiler-0.3.1/PKG-INFO +659 -0
- phys_ai_dataprofiler-0.3.1/README.md +618 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/__init__.py +13 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/__main__.py +5 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/cli.py +214 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/compare.py +123 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/connectors/__init__.py +1 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/connectors/huggingface.py +1113 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/evaluate.py +570 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/inspectors/__init__.py +1 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/inspectors/mat.py +485 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/inspectors/mcap.py +266 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/inspectors/rich.py +428 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/presets/lidar-perception.yaml +15 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/presets/teleop-imitation.yaml +22 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/presets/timeseries-clean.yaml +14 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/presets/visual-odometry.yaml +21 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/remote/__init__.py +1 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/remote/inspector.py +795 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/remote/parquet.py +578 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/remote/range_file.py +326 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/remote/rosbag.py +998 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler/requirements.py +421 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/PKG-INFO +659 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/SOURCES.txt +38 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/dependency_links.txt +1 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/entry_points.txt +2 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/requires.txt +16 -0
- phys_ai_dataprofiler-0.3.1/phys_ai_dataprofiler.egg-info/top_level.txt +1 -0
- phys_ai_dataprofiler-0.3.1/pyproject.toml +53 -0
- phys_ai_dataprofiler-0.3.1/setup.cfg +4 -0
- phys_ai_dataprofiler-0.3.1/tests/test_connector.py +188 -0
- phys_ai_dataprofiler-0.3.1/tests/test_mat_inspector.py +109 -0
- phys_ai_dataprofiler-0.3.1/tests/test_mcap_inspector.py +99 -0
- phys_ai_dataprofiler-0.3.1/tests/test_parquet_inspector.py +290 -0
- phys_ai_dataprofiler-0.3.1/tests/test_range_file.py +165 -0
- phys_ai_dataprofiler-0.3.1/tests/test_remote_rich.py +240 -0
- phys_ai_dataprofiler-0.3.1/tests/test_requirements.py +482 -0
- phys_ai_dataprofiler-0.3.1/tests/test_rosbag_inspector.py +307 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anand Krishniyer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phys-ai-dataprofiler
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Profile robotics and lab datasets on the Hugging Face Hub without downloading them (MCAP, ROS bags, MATLAB, Parquet), and check them against your requirements
|
|
5
|
+
Author-email: Anand Krishniyer <akrishniyer@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/akrishni/phys-ai-dataprofiler
|
|
8
|
+
Project-URL: Documentation, https://github.com/akrishni/phys-ai-dataprofiler#readme
|
|
9
|
+
Project-URL: Issues, https://github.com/akrishni/phys-ai-dataprofiler/issues
|
|
10
|
+
Keywords: robotics,dataset,mcap,rosbag,ros2,matlab,parquet,lerobot,huggingface,data-quality,profiling
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: huggingface_hub>=1.0
|
|
28
|
+
Requires-Dist: mcap>=1.0
|
|
29
|
+
Requires-Dist: numpy
|
|
30
|
+
Requires-Dist: scipy
|
|
31
|
+
Requires-Dist: pyarrow>=14
|
|
32
|
+
Requires-Dist: pyyaml>=6
|
|
33
|
+
Provides-Extra: mat73
|
|
34
|
+
Requires-Dist: h5py; extra == "mat73"
|
|
35
|
+
Provides-Extra: rosbag
|
|
36
|
+
Requires-Dist: rosbags; extra == "rosbag"
|
|
37
|
+
Requires-Dist: lz4; extra == "rosbag"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# phys-ai-dataprofiler
|
|
43
|
+
|
|
44
|
+
[](https://github.com/akrishni/phys-ai-dataprofiler/actions/workflows/tests.yml)
|
|
45
|
+
|
|
46
|
+
Profile physical-AI and lab datasets on the Hugging Face Hub **without downloading them**, and check
|
|
47
|
+
them against what your model needs.
|
|
48
|
+
|
|
49
|
+
It reads only the bytes that hold metadata (headers, indexes, footers) through HTTP range requests,
|
|
50
|
+
usually well under 1% of each file, and reports streams, message counts, rates, jitter, gaps, episodes,
|
|
51
|
+
NaN counts and problems such as truncated or unindexed recordings. A requirements file turns that into a
|
|
52
|
+
pass / fail / unknown verdict, and `compare` ranks candidate datasets side by side.
|
|
53
|
+
|
|
54
|
+
| Format | What is read |
|
|
55
|
+
|---|---|
|
|
56
|
+
| MCAP (incl. ROS 2 MCAP storage) | header, footer, summary, message indexes |
|
|
57
|
+
| ROS 1 bag (V2.0) | bag index and per-chunk index data; compressed payloads are skipped |
|
|
58
|
+
| ROS 2 bag (SQLite `.db3`) | schema, topics, metadata and the leaf pages of the messages table |
|
|
59
|
+
| MATLAB `.mat` (v5, v7, v7.3) | header and each variable's header; float data within a NaN budget |
|
|
60
|
+
| Parquet / LeRobot | footer, column statistics, timestamp and episode columns |
|
|
61
|
+
|
|
62
|
+
Measured on public Hub data: a 5.8 GB ROS 1 bag profiled from 533 KB, a 2.6 GB MAT v7.3 file from 808 KB.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install phys-ai-dataprofiler
|
|
68
|
+
pip install "phys-ai-dataprofiler[mat73]" # MAT v7.3 (HDF5) files; needs Python 3.13 or older for h5py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Python 3.10 or newer. Public datasets need no account; for gated or private ones set `HF_TOKEN` (or run
|
|
72
|
+
`hf auth login`). The token is only sent to huggingface.co, never to the CDN hosts that serve file bytes.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
phys-ai-dataprofiler org/dataset --dry-run # what files are there, and how big
|
|
78
|
+
phys-ai-dataprofiler org/dataset --max-files 10 # profile a sample -> reports/<repo>__<time>.md/.json
|
|
79
|
+
|
|
80
|
+
phys-ai-dataprofiler requirements list # built-in presets
|
|
81
|
+
phys-ai-dataprofiler requirements show teleop-imitation > req.yaml # start your own, edit the numbers
|
|
82
|
+
phys-ai-dataprofiler check org/dataset -r req.yaml # verdict; exit 0 pass, 1 fail, 4 unknown
|
|
83
|
+
phys-ai-dataprofiler compare org/a org/b -r req.yaml # rank candidates against the same requirements
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Nothing is downloaded unless you ask for it (`--mode download`, or `--full-read-max-mb` for small files).
|
|
87
|
+
Nothing is ever written to the Hub.
|
|
88
|
+
|
|
89
|
+
## Requirements and verdicts
|
|
90
|
+
|
|
91
|
+
Say what your model needs in a YAML file, and the profiler answers pass, fail or unknown for each
|
|
92
|
+
requirement, with evidence and, for unknowns, how to settle them. Checks are deterministic rules, with no
|
|
93
|
+
model call. Full reference: [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md).
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
phys-ai-dataprofiler check org/dataset -r preset:teleop-imitation # exit 0 pass, 1 fail, 4 unknown
|
|
97
|
+
phys-ai-dataprofiler compare org/a org/b -r my_requirements.yaml # rank candidate datasets
|
|
98
|
+
phys-ai-dataprofiler check --report reports/run.json -r my_requirements.yaml # re-check offline
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
streams:
|
|
103
|
+
- name: camera
|
|
104
|
+
match: {modality: video}
|
|
105
|
+
min_rate_hz: 25
|
|
106
|
+
- name: joint state
|
|
107
|
+
match: {topic: ["/joint_states", "observation.state"]}
|
|
108
|
+
min_rate_hz: 50
|
|
109
|
+
max_gap_ms: 100
|
|
110
|
+
dataset:
|
|
111
|
+
min_total_hours: 20
|
|
112
|
+
min_episodes: 500
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A number that was sampled and lies within 10% of the threshold, a NaN count the format cannot provide,
|
|
116
|
+
or a file that failed to profile makes the result **unknown**, never a guessed pass.
|
|
117
|
+
|
|
118
|
+
## ROS bag support
|
|
119
|
+
|
|
120
|
+
`.bag` (ROS 1, format V2.0) and `.db3` (ROS 2, SQLite storage) files are profiled from byte ranges, like
|
|
121
|
+
MCAP. ROS 2 bags in MCAP storage are plain `.mcap` files and use the MCAP path. Code:
|
|
122
|
+
`phys_ai_dataprofiler/remote/rosbag.py`.
|
|
123
|
+
|
|
124
|
+
- **ROS 1:** the bag header points to the index at the end of the file (connections plus per-chunk counts
|
|
125
|
+
and time ranges). Per-message timestamps come from the IndexData records after each chunk, 12 bytes per
|
|
126
|
+
message, one request per chunk. Chunk payloads are never read, so compression (bz2, lz4) makes no
|
|
127
|
+
difference. Over `--msgidx-budget-mb`, chunks are sampled; counts stay exact. Bags with no index
|
|
128
|
+
(recorder killed) are walked record by record and reported `unindexed_bag`; truncated bags are reported
|
|
129
|
+
`truncated`. Old V1.2 bags are identified but not parsed.
|
|
130
|
+
- **ROS 2 SQLite:** a small read-only SQLite reader descends the `messages` table B-tree. Each row's
|
|
131
|
+
`topic_id` and `timestamp` sit at the start of its leaf cell, so message payloads and their overflow
|
|
132
|
+
pages are never read. Counts and duration come from the `metadata` table, or from the `metadata.yaml`
|
|
133
|
+
next to the `.db3` when the database has none. Over budget, runs of leaf pages are sampled and the
|
|
134
|
+
start and end times come from `timestamp_idx`. Without any metadata, sampled per-topic counts are
|
|
135
|
+
estimates, reported as `estimated_counts`.
|
|
136
|
+
|
|
137
|
+
Timing statistics use the same code as MCAP. Tests compare every case against a full read through
|
|
138
|
+
`rosbags`: `python tests/test_rosbag_inspector.py` (add `--hub` for two public bags).
|
|
139
|
+
|
|
140
|
+
| Dataset | Result | Read |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `Adapting/nuscenes-rosbags` `103.bag` (ROS 1) | 14 topics, 1,585 chunks, 19.5 s | 533 KB of 5.8 GB |
|
|
143
|
+
| `dvar1ch/ros2-bag-datasets` (ROS 2 SQLite, 2 files) | `/image` at 30 Hz, metadata v9, jazzy | 1.5 MB of 668 MB |
|
|
144
|
+
| `liuxh010/IndoorLocalization_...` (ROS 1), `UniflexAI/rosbag_lekiwi_exp_result` (ROS 2) | per-topic stats identical to a full read | 146 KB of 891 KB; 2.1 MB of 18.6 MB |
|
|
145
|
+
|
|
146
|
+
## Parquet support
|
|
147
|
+
|
|
148
|
+
Parquet keeps its schema, row groups and per-column statistics in a footer at the end of the file, so the
|
|
149
|
+
profiler reads the footer first (typically under 100 KB, whatever the file size). It reports rows, row groups,
|
|
150
|
+
every column's type, null count, min and max, compressed size and codec.
|
|
151
|
+
|
|
152
|
+
If a time column is present (`timestamp`, `log_time`, `time`, ...), it also reads that column, and
|
|
153
|
+
`episode_index` when present (the LeRobot convention). Their byte ranges are known from the footer, so the cost
|
|
154
|
+
is computed up front and capped by `--column-budget-mb` (default 16); beyond that, row groups are sampled and
|
|
155
|
+
the report says so. From these it derives frame rate, jitter, gaps, episode count and lengths, and flags
|
|
156
|
+
dropped frames, short episodes and non-monotonic time. Image, text and state columns are never read.
|
|
157
|
+
|
|
158
|
+
Units are inferred: float seconds (LeRobot), integer epoch s/ms/us/ns, and Arrow timestamp types.
|
|
159
|
+
NaN/inf counts are not in Parquet statistics, so float columns are read too, capped by `--nan-budget-mb`
|
|
160
|
+
(default 32); beyond that, row groups are sampled and the report says so. `--nan-budget-mb 0` turns this off.
|
|
161
|
+
|
|
162
|
+
| Dataset | Result | Read |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `lerobot/pusht` (LeRobot v3) | 206 episodes at 10 Hz, matching `meta/info.json` | 205 KB of 659 KB |
|
|
165
|
+
| `nvidia/Nemotron-Personas-Vietnam` (text) | 90,910 rows, 21 columns per file | 143 KB of 194 MB |
|
|
166
|
+
|
|
167
|
+
Repos with nothing to profile now say so and exit with code 3, listing the file types they do contain.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# MCAP File Inspector
|
|
172
|
+
|
|
173
|
+
A tool to inspect MCAP (Message Capture) files and extract header, footer, channels, topics, and schema information.
|
|
174
|
+
|
|
175
|
+
## Features
|
|
176
|
+
|
|
177
|
+
- **Header Inspection**: Extracts profile and library information
|
|
178
|
+
- **Footer Inspection**: Extracts summary start offset, summary offset start, and CRC
|
|
179
|
+
- **Channel Discovery**: Lists all channels with their topics, encoding, schema IDs, and metadata
|
|
180
|
+
- **Topic Listing**: Provides a sorted list of all unique topics
|
|
181
|
+
- **Schema Information**: Extracts schema details including name, encoding, and data
|
|
182
|
+
- **Local and Remote Files**: Supports both local file paths and remote URLs
|
|
183
|
+
- **Fallback Support**: Works with or without the mcap library installed
|
|
184
|
+
|
|
185
|
+
## Installation
|
|
186
|
+
|
|
187
|
+
1. Create a virtual environment:
|
|
188
|
+
```bash
|
|
189
|
+
python3 -m venv venv
|
|
190
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
2. Install dependencies:
|
|
194
|
+
```bash
|
|
195
|
+
pip install mcap
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Usage
|
|
199
|
+
|
|
200
|
+
### As a module
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from phys_ai_dataprofiler.inspectors.mcap import McapFileInspector
|
|
204
|
+
|
|
205
|
+
# Inspect a local file
|
|
206
|
+
inspector = McapFileInspector("samples/sample.mcap")
|
|
207
|
+
result = inspector.inspect()
|
|
208
|
+
|
|
209
|
+
print(f"Topics: {result.topics}")
|
|
210
|
+
print(f"Channels: {len(result.channels)}")
|
|
211
|
+
print(f"Header Profile: {result.header.profile}")
|
|
212
|
+
print(f"Footer Summary Start: {result.footer.summary_start}")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### As a command-line tool
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Using the basic inspect_file script
|
|
219
|
+
python -m phys_ai_dataprofiler.inspectors.mcap samples/sample.mcap
|
|
220
|
+
|
|
221
|
+
# Using the test script with formatted output
|
|
222
|
+
python tests/test_mcap_inspector.py samples/sample.mcap
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Remote URLs
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
python tests/test_mcap_inspector.py https://example.com/data.mcap
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Data Structures
|
|
232
|
+
|
|
233
|
+
### McapInspectionResult
|
|
234
|
+
- `url`: File URL or path
|
|
235
|
+
- `is_mcap`: Boolean indicating if file is a valid MCAP
|
|
236
|
+
- `magic`: Magic bytes from file header
|
|
237
|
+
- `size_bytes`: File size in bytes
|
|
238
|
+
- `path`: Local path (if applicable)
|
|
239
|
+
- `error`: Error message (if any)
|
|
240
|
+
- `header`: HeaderInfo object
|
|
241
|
+
- `footer`: FooterInfo object
|
|
242
|
+
- `channels`: List of ChannelInfo objects
|
|
243
|
+
- `schemas`: List of SchemaInfo objects
|
|
244
|
+
- `topics`: Sorted list of topic strings
|
|
245
|
+
- `mcap_available`: Boolean indicating if mcap library is available
|
|
246
|
+
|
|
247
|
+
### HeaderInfo
|
|
248
|
+
- `profile`: MCAP profile string
|
|
249
|
+
- `library`: Library that created the file
|
|
250
|
+
|
|
251
|
+
### FooterInfo
|
|
252
|
+
- `summary_start`: Offset where summary section starts
|
|
253
|
+
- `summary_offset_start`: Offset where summary offsets start
|
|
254
|
+
- `summary_crc`: CRC checksum of summary section
|
|
255
|
+
|
|
256
|
+
### ChannelInfo
|
|
257
|
+
- `id`: Channel ID
|
|
258
|
+
- `topic`: Topic name
|
|
259
|
+
- `message_encoding`: Message encoding (e.g., "json", "protobuf")
|
|
260
|
+
- `schema_id`: Associated schema ID
|
|
261
|
+
- `metadata`: Dictionary of channel metadata
|
|
262
|
+
|
|
263
|
+
### SchemaInfo
|
|
264
|
+
- `id`: Schema ID
|
|
265
|
+
- `name`: Schema name
|
|
266
|
+
- `encoding`: Schema encoding type
|
|
267
|
+
- `data`: Raw schema data (bytes)
|
|
268
|
+
|
|
269
|
+
## Example Output
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
Inspecting: samples/sample.mcap
|
|
273
|
+
|
|
274
|
+
URL: samples/sample.mcap
|
|
275
|
+
Is MCAP: True
|
|
276
|
+
Size: 1042 bytes
|
|
277
|
+
MCAP Library Available: True
|
|
278
|
+
|
|
279
|
+
=== Header ===
|
|
280
|
+
Profile: test
|
|
281
|
+
Library: sample_creator
|
|
282
|
+
|
|
283
|
+
=== Footer ===
|
|
284
|
+
Summary Start: 397
|
|
285
|
+
Summary Offset Start: 849
|
|
286
|
+
Summary CRC: 544820573
|
|
287
|
+
|
|
288
|
+
=== Topics (2) ===
|
|
289
|
+
- /sensors/gps
|
|
290
|
+
- /sensors/imu
|
|
291
|
+
|
|
292
|
+
=== Channels (2) ===
|
|
293
|
+
Channel 1:
|
|
294
|
+
Topic: /sensors/gps
|
|
295
|
+
Encoding: json
|
|
296
|
+
Schema ID: 1
|
|
297
|
+
Metadata: {'description': 'GPS sensor data'}
|
|
298
|
+
Channel 2:
|
|
299
|
+
Topic: /sensors/imu
|
|
300
|
+
Encoding: json
|
|
301
|
+
Schema ID: 1
|
|
302
|
+
Metadata: {'description': 'IMU sensor data'}
|
|
303
|
+
|
|
304
|
+
=== Schemas (1) ===
|
|
305
|
+
Schema 1:
|
|
306
|
+
Name: example.Point
|
|
307
|
+
Encoding: jsonschema
|
|
308
|
+
Data size: 84 bytes
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Creating Test Files
|
|
312
|
+
|
|
313
|
+
Use the included `create_sample_mcap.py` script to create test MCAP files:
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
python scripts/create_sample_mcap.py output.mcap
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Implementation Details
|
|
320
|
+
|
|
321
|
+
The inspector uses the official [mcap](https://github.com/foxglove/mcap) library to parse MCAP files:
|
|
322
|
+
- `make_reader()` for high-level summary and header access
|
|
323
|
+
- `StreamReader` for low-level footer record extraction
|
|
324
|
+
- Supports both file handles and BytesIO for remote content
|
|
325
|
+
|
|
326
|
+
For remote files, the inspector first checks the magic bytes using HTTP Range requests, then downloads the full file for parsing if valid.
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
# MATLAB File Inspector
|
|
332
|
+
|
|
333
|
+
A tool to inspect MATLAB .mat files and extract header information, variable metadata, and data types.
|
|
334
|
+
|
|
335
|
+
## Features
|
|
336
|
+
|
|
337
|
+
- **Header Inspection**: Extracts description, version, and endianness information
|
|
338
|
+
- **Variable Discovery**: Lists all variables with their names, types, shapes, and sizes
|
|
339
|
+
- **Type Detection**: Identifies scalars, vectors, matrices, complex numbers, sparse matrices, structs, and cell arrays
|
|
340
|
+
- **Format Support**: Handles MAT-File v4, v5, v7, and v7.3 (HDF5-based) formats
|
|
341
|
+
- **Local and Remote Files**: Supports both local file paths and remote URLs
|
|
342
|
+
- **Fallback Support**: Works with limited functionality if scipy/h5py are not installed
|
|
343
|
+
|
|
344
|
+
## Installation
|
|
345
|
+
|
|
346
|
+
1. Create a virtual environment (if not already created):
|
|
347
|
+
```bash
|
|
348
|
+
python3 -m venv venv
|
|
349
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
2. Install dependencies:
|
|
353
|
+
```bash
|
|
354
|
+
pip install scipy numpy # For v4/v5/v7 format support
|
|
355
|
+
pip install h5py # For v7.3 (HDF5) format support (requires HDF5 library)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Usage
|
|
359
|
+
|
|
360
|
+
### As a module
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
from phys_ai_dataprofiler.inspectors.mat import MatFileInspector
|
|
364
|
+
|
|
365
|
+
# Inspect a local file
|
|
366
|
+
inspector = MatFileInspector("data.mat")
|
|
367
|
+
result = inspector.inspect()
|
|
368
|
+
|
|
369
|
+
print(f"Format: {result.file_format}")
|
|
370
|
+
print(f"Variables: {result.variable_names}")
|
|
371
|
+
for var in result.variables:
|
|
372
|
+
print(f"{var.name}: {var.dtype} {var.shape}")
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### As a command-line tool
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# Using the basic matfile_inspector script
|
|
379
|
+
python -m phys_ai_dataprofiler.inspectors.mat data.mat
|
|
380
|
+
|
|
381
|
+
# Using the test script with formatted output
|
|
382
|
+
python tests/test_mat_inspector.py data.mat
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Remote URLs
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
python tests/test_mat_inspector.py https://example.com/data.mat
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Data Structures
|
|
392
|
+
|
|
393
|
+
### MatInspectionResult
|
|
394
|
+
- `url`: File URL or path
|
|
395
|
+
- `is_matfile`: Boolean indicating if file is a valid MAT-File
|
|
396
|
+
- `file_format`: Format version ("v4", "v5", "v7", or "v7.3")
|
|
397
|
+
- `size_bytes`: File size in bytes
|
|
398
|
+
- `path`: Local path (if applicable)
|
|
399
|
+
- `error`: Error message (if any)
|
|
400
|
+
- `header`: MatFileHeader object
|
|
401
|
+
- `variables`: List of VariableInfo objects
|
|
402
|
+
- `variable_names`: Sorted list of variable names
|
|
403
|
+
- `scipy_available`: Boolean indicating if scipy is available
|
|
404
|
+
- `h5py_available`: Boolean indicating if h5py is available
|
|
405
|
+
|
|
406
|
+
### MatFileHeader
|
|
407
|
+
- `description`: File description string (includes platform and creation time)
|
|
408
|
+
- `version`: Version number in hex format
|
|
409
|
+
- `endian`: Endianness ("little", "big", or "unknown")
|
|
410
|
+
- `created`: Creation timestamp (when available)
|
|
411
|
+
|
|
412
|
+
### VariableInfo
|
|
413
|
+
- `name`: Variable name
|
|
414
|
+
- `dtype`: Data type string
|
|
415
|
+
- `shape`: Tuple of dimensions
|
|
416
|
+
- `size_bytes`: Size in bytes
|
|
417
|
+
- `is_sparse`: Boolean indicating sparse matrix
|
|
418
|
+
- `is_complex`: Boolean indicating complex numbers
|
|
419
|
+
- `is_struct`: Boolean indicating MATLAB struct
|
|
420
|
+
- `is_cell`: Boolean indicating cell array
|
|
421
|
+
- `field_names`: List of field names (for structs)
|
|
422
|
+
|
|
423
|
+
## Example Output
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
Inspecting: samples/sample_v5.mat
|
|
427
|
+
|
|
428
|
+
URL: samples/sample_v5.mat
|
|
429
|
+
Is MAT-File: True
|
|
430
|
+
File Format: v5
|
|
431
|
+
Size: 1544 bytes
|
|
432
|
+
SciPy Available: True
|
|
433
|
+
h5py Available: False
|
|
434
|
+
|
|
435
|
+
=== Header ===
|
|
436
|
+
Description: MATLAB 5.0 MAT-file Platform: posix, Created on: Sun Oct 11 18:59:29 2026
|
|
437
|
+
Version: 0x0100
|
|
438
|
+
Endian: little
|
|
439
|
+
|
|
440
|
+
=== Variables (12) ===
|
|
441
|
+
- col_vector
|
|
442
|
+
- complex_data
|
|
443
|
+
- double_data
|
|
444
|
+
- float_data
|
|
445
|
+
- int_array
|
|
446
|
+
- logical_array
|
|
447
|
+
- matrix_2d
|
|
448
|
+
- matrix_3d
|
|
449
|
+
- row_vector
|
|
450
|
+
- scalar_value
|
|
451
|
+
- text_string
|
|
452
|
+
- uint_array
|
|
453
|
+
|
|
454
|
+
=== Variable Details ===
|
|
455
|
+
|
|
456
|
+
scalar_value:
|
|
457
|
+
Type: int64
|
|
458
|
+
Shape: (1, 1)
|
|
459
|
+
Size: 8 bytes
|
|
460
|
+
|
|
461
|
+
complex_data:
|
|
462
|
+
Type: complex128
|
|
463
|
+
Shape: (1, 3)
|
|
464
|
+
Size: 48 bytes
|
|
465
|
+
Flags: complex
|
|
466
|
+
|
|
467
|
+
matrix_3d:
|
|
468
|
+
Type: float64
|
|
469
|
+
Shape: (2, 3, 4)
|
|
470
|
+
Size: 192 bytes
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Supported MAT-File Formats
|
|
474
|
+
|
|
475
|
+
### v4 Format
|
|
476
|
+
- Legacy format from MATLAB 4
|
|
477
|
+
- Limited data type support
|
|
478
|
+
- Requires scipy
|
|
479
|
+
|
|
480
|
+
### v5 Format (Default)
|
|
481
|
+
- MATLAB 5 and later (default save format in older versions)
|
|
482
|
+
- Supports all standard MATLAB types
|
|
483
|
+
- Requires scipy
|
|
484
|
+
|
|
485
|
+
### v7 Format
|
|
486
|
+
- Compressed v5 format
|
|
487
|
+
- Same structure as v5 but with compression
|
|
488
|
+
- Requires scipy
|
|
489
|
+
|
|
490
|
+
### v7.3 Format (HDF5-based)
|
|
491
|
+
- MATLAB R2006b and later
|
|
492
|
+
- Based on HDF5 file format
|
|
493
|
+
- Required for variables larger than 2GB
|
|
494
|
+
- Requires h5py and HDF5 library
|
|
495
|
+
|
|
496
|
+
## Creating Test Files
|
|
497
|
+
|
|
498
|
+
Use the included `create_sample_mat.py` script to create test MAT files:
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
python scripts/create_sample_mat.py samples/sample_v5.mat samples/sample_v7.mat
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Implementation Details
|
|
505
|
+
|
|
506
|
+
The inspector uses two libraries depending on the MAT-File format:
|
|
507
|
+
- **scipy.io.loadmat**: For v4, v5, and v7 formats
|
|
508
|
+
- **h5py**: For v7.3 (HDF5-based) format
|
|
509
|
+
|
|
510
|
+
For remote files, the inspector first checks the file header using HTTP Range requests to determine the format, then downloads the full file for parsing if valid.
|
|
511
|
+
|
|
512
|
+
## Comparison with MCAP Inspector
|
|
513
|
+
|
|
514
|
+
Both inspectors follow a similar architecture:
|
|
515
|
+
|
|
516
|
+
| Feature | MCAP Inspector | MATLAB Inspector |
|
|
517
|
+
|---------|----------------|------------------|
|
|
518
|
+
| File Format | MCAP (robotics) | MAT-File (MATLAB) |
|
|
519
|
+
| Header Info | ✓ | ✓ |
|
|
520
|
+
| Footer Info | ✓ | N/A |
|
|
521
|
+
| Variable/Channel List | ✓ | ✓ |
|
|
522
|
+
| Type Information | ✓ | ✓ |
|
|
523
|
+
| Remote Support | ✓ | ✓ |
|
|
524
|
+
| Compression Support | ✓ | ✓ |
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
# Hugging Face Connector
|
|
529
|
+
|
|
530
|
+
`phys_ai_dataprofiler/connectors/huggingface.py` (CLI: `phys-ai-dataprofiler`, or `python -m phys_ai_dataprofiler`) connects to the Hugging Face Hub with an access token,
|
|
531
|
+
finds the MCAP / MAT files in a repo, inspects them and writes a JSON and a
|
|
532
|
+
Markdown report. By default **nothing is downloaded**: files are read through
|
|
533
|
+
HTTP Range requests and only the bytes holding metadata are fetched.
|
|
534
|
+
|
|
535
|
+
| What | MCAP | MAT (v5 / v7) |
|
|
536
|
+
|---|---|---|
|
|
537
|
+
| Bytes read | header, footer, summary section, MessageIndex records | 128-byte header + each variable's tag and array header, then float variables up to `--nan-budget-mb` |
|
|
538
|
+
| Typical share of file | well under 1% (163 MB file: ~0.5 MB) | headers: one 64 KB block per few variables; plus the float data that fits the NaN budget |
|
|
539
|
+
| Available | profile, library, topics, schemas, channels, per-channel count, rate, jitter, gaps, duration | format, header, every variable with name, class/dtype, dims, stored size, complex/sparse/struct/cell flags, struct field names, NaN counts |
|
|
540
|
+
| No summary / truncated | walked record by record: chunk headers and message indexes, chunk bodies only for channel definitions or when a chunk has no index; capped by `--msgidx-budget-mb` | - |
|
|
541
|
+
| Needs a full read | - | v4 files, v7.3 without `h5py` |
|
|
542
|
+
|
|
543
|
+
Remote mode gives the same streams, rates, gaps, findings and NaN counts as
|
|
544
|
+
the full-read `RichInspector` (checked by `tests/test_remote_rich.py`). It also
|
|
545
|
+
profiles truncated MCAPs, which the full read cannot open. Anything partial
|
|
546
|
+
(budget reached, chunks sampled) is reported as a finding. Use
|
|
547
|
+
`--full-read-max-mb N` to fetch files at or below that size whole instead
|
|
548
|
+
(deleted afterwards unless `--keep-downloads`), or `--mode download` to go
|
|
549
|
+
back to downloading everything.
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
## Token
|
|
553
|
+
|
|
554
|
+
Resolved in this order: `--token`, then `HF_TOKEN` / `HUGGINGFACE_HUB_TOKEN`,
|
|
555
|
+
then the login cached by `hf auth login`. Public repos work with no token.
|
|
556
|
+
The token is only ever sent to huggingface.co, never to the CDN hosts that
|
|
557
|
+
serve the file bytes.
|
|
558
|
+
|
|
559
|
+
## Usage
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
# Inspect every .mcap/.mat file in a dataset repo without downloading
|
|
563
|
+
phys-ai-dataprofiler org/dataset --token hf_xxx
|
|
564
|
+
|
|
565
|
+
# See what would be inspected first
|
|
566
|
+
phys-ai-dataprofiler org/dataset --dry-run
|
|
567
|
+
|
|
568
|
+
# Limit the selection: by glob, by count, by size
|
|
569
|
+
phys-ai-dataprofiler org/dataset --include "episodes/*.mcap" --max-files 5
|
|
570
|
+
|
|
571
|
+
# Exact files
|
|
572
|
+
phys-ai-dataprofiler org/dataset --file logs/run1.mcap --file calib/cam.mat
|
|
573
|
+
|
|
574
|
+
# Larger NaN budget (default 32 MB of float data per file), or none
|
|
575
|
+
phys-ai-dataprofiler org/dataset --nan-budget-mb 256
|
|
576
|
+
phys-ai-dataprofiler org/dataset --nan-budget-mb 0
|
|
577
|
+
|
|
578
|
+
# Fetch small files whole instead of range-reading them
|
|
579
|
+
phys-ai-dataprofiler org/dataset --full-read-max-mb 50
|
|
580
|
+
|
|
581
|
+
# Classic download mode, and re-running on an earlier download
|
|
582
|
+
phys-ai-dataprofiler org/dataset --mode download --max-total-mb 2000
|
|
583
|
+
phys-ai-dataprofiler org/dataset --mode download --offline
|
|
584
|
+
|
|
585
|
+
# Model repos, specific revisions
|
|
586
|
+
phys-ai-dataprofiler org/model --repo-type model --revision v1.2
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Tuning for remote mode: `--workers` (parallel range requests, default 16),
|
|
590
|
+
`--msgidx-budget-mb` (cap on MCAP message-index bytes per file; beyond it
|
|
591
|
+
chunks are sampled and the report says so; also caps the record walk of MCAPs
|
|
592
|
+
without a summary), `--nan-budget-mb` (float bytes read per file for NaN
|
|
593
|
+
counts), `--block-kb` (cached read block).
|
|
594
|
+
|
|
595
|
+
Reports go to `./reports/<repo>__<timestamp>.{json,md}` (override with
|
|
596
|
+
`--report-dir`, `--report-name`) and include, per file, how many bytes were
|
|
597
|
+
read and in how many requests. Exit code is 1 when any file failed, so it is
|
|
598
|
+
CI-friendly.
|
|
599
|
+
|
|
600
|
+
## Modules
|
|
601
|
+
|
|
602
|
+
- `phys_ai_dataprofiler/remote/range_file.py`: `HfRangeFile` (seekable file over Hub byte ranges,
|
|
603
|
+
block cache, parallel `fetch_many`) and `LocalRangeFile` (same API on disk).
|
|
604
|
+
- `phys_ai_dataprofiler/remote/inspector.py`: `inspect_mcap_remote` / `inspect_mat_remote`,
|
|
605
|
+
returning the same `McapInspectionResult`, `MatInspectionResult` and
|
|
606
|
+
`RichProfile` types as the full-read inspectors. Run it on a local file with
|
|
607
|
+
`python -m phys_ai_dataprofiler.remote.inspector file.mcap` to see what it extracts.
|
|
608
|
+
- `phys_ai_dataprofiler/inspectors/rich.py` exposes `timing_stats` and `profile_mcap_channels`,
|
|
609
|
+
shared by both paths so remote and local statistics are identical.
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
from phys_ai_dataprofiler.connectors.huggingface import HuggingFaceConnector
|
|
613
|
+
from phys_ai_dataprofiler.remote.inspector import inspect_mcap_remote, inspect_mat_remote, sniff_format
|
|
614
|
+
|
|
615
|
+
hf = HuggingFaceConnector("org/dataset", token="hf_xxx")
|
|
616
|
+
hf.connect()
|
|
617
|
+
selected, skipped = hf.select_files(hf.list_files(), max_files=10)
|
|
618
|
+
for f in selected:
|
|
619
|
+
rf = hf.open_range(f, block_size=64 * 1024)
|
|
620
|
+
fmt = sniff_format(rf, f.path)
|
|
621
|
+
result, profile = inspect_mcap_remote(rf) if fmt == "mcap" else inspect_mat_remote(rf)
|
|
622
|
+
print(f.path, rf.bytes_fetched, "of", rf.size, "bytes read")
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
# Development
|
|
628
|
+
|
|
629
|
+
```bash
|
|
630
|
+
git clone https://github.com/akrishni/phys-ai-dataprofiler
|
|
631
|
+
cd phys-ai-dataprofiler
|
|
632
|
+
pip install -e ".[mat73,rosbag,dev]"
|
|
633
|
+
python tests/run_all.py # every test script, with a summary
|
|
634
|
+
python tests/run_all.py --hub # also profile small public Hub datasets
|
|
635
|
+
python tests/test_rosbag_inspector.py # or run one script on its own
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
CI (`.github/workflows/tests.yml`) runs `tests/run_all.py` on every push and pull request, on Python 3.10
|
|
639
|
+
to 3.13 on Linux and 3.13 on macOS, and builds the package and smoke-tests the installed CLI. The Hub
|
|
640
|
+
checks need network access and are only run on demand: Actions tab, "tests", "Run workflow", tick
|
|
641
|
+
"hub".
|
|
642
|
+
|
|
643
|
+
```
|
|
644
|
+
phys_ai_dataprofiler/
|
|
645
|
+
connectors/huggingface.py Hub connector and the default CLI command
|
|
646
|
+
cli.py check, compare, requirements subcommands
|
|
647
|
+
requirements.py evaluate.py compare.py requirements files, verdicts, comparisons
|
|
648
|
+
presets/ built-in requirements files
|
|
649
|
+
remote/ byte-range readers: MCAP, MAT, Parquet, ROS bags
|
|
650
|
+
inspectors/ full-read inspectors for local files
|
|
651
|
+
tests/ samples/ scripts/ docs/
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## License and citation
|
|
655
|
+
|
|
656
|
+
MIT License, copyright (c) 2026 Anand Krishniyer. See [LICENSE](LICENSE).
|
|
657
|
+
|
|
658
|
+
If you use this tool in research, please cite it; GitHub's "Cite this repository" button (from
|
|
659
|
+
[CITATION.cff](CITATION.cff)) gives a ready-made reference.
|