tlabel 0.1.0a1__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.
- tlabel-0.1.0a1/PKG-INFO +113 -0
- tlabel-0.1.0a1/README.md +79 -0
- tlabel-0.1.0a1/pyproject.toml +43 -0
- tlabel-0.1.0a1/setup.cfg +4 -0
- tlabel-0.1.0a1/tests/test_tlabel.py +299 -0
- tlabel-0.1.0a1/tlabel/__init__.py +8 -0
- tlabel-0.1.0a1/tlabel/_version.py +1 -0
- tlabel-0.1.0a1/tlabel/adapters/__init__.py +5 -0
- tlabel-0.1.0a1/tlabel/adapters/base.py +54 -0
- tlabel-0.1.0a1/tlabel/adapters/daimon.py +536 -0
- tlabel-0.1.0a1/tlabel/adapters/gelsight.py +405 -0
- tlabel-0.1.0a1/tlabel/adapters/paxini.py +349 -0
- tlabel-0.1.0a1/tlabel/core/__init__.py +7 -0
- tlabel-0.1.0a1/tlabel/core/loader.py +63 -0
- tlabel-0.1.0a1/tlabel/core/registry.py +92 -0
- tlabel-0.1.0a1/tlabel/core/types.py +213 -0
- tlabel-0.1.0a1/tlabel/export/__init__.py +5 -0
- tlabel-0.1.0a1/tlabel/export/writer.py +80 -0
- tlabel-0.1.0a1/tlabel/viewer/__init__.py +5 -0
- tlabel-0.1.0a1/tlabel/viewer/panel.py +41 -0
- tlabel-0.1.0a1/tlabel/viewer/templates.py +509 -0
- tlabel-0.1.0a1/tlabel.egg-info/PKG-INFO +113 -0
- tlabel-0.1.0a1/tlabel.egg-info/SOURCES.txt +24 -0
- tlabel-0.1.0a1/tlabel.egg-info/dependency_links.txt +1 -0
- tlabel-0.1.0a1/tlabel.egg-info/requires.txt +19 -0
- tlabel-0.1.0a1/tlabel.egg-info/top_level.txt +1 -0
tlabel-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tlabel
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: TouchLabel AI - Tactile Data Annotation Toolkit
|
|
5
|
+
Author-email: Niuzu Tech <luoxi@touchlabelai.cn>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/liesliy/tlabel
|
|
8
|
+
Keywords: tactile,annotation,robotics,GelSight,PaXini,touch
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: numpy>=1.20
|
|
21
|
+
Provides-Extra: gelsight
|
|
22
|
+
Requires-Dist: opencv-python>=4.0; extra == "gelsight"
|
|
23
|
+
Provides-Extra: paxini
|
|
24
|
+
Requires-Dist: h5py>=3.0; extra == "paxini"
|
|
25
|
+
Provides-Extra: daimon
|
|
26
|
+
Requires-Dist: pyarrow>=10.0; extra == "daimon"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: opencv-python>=4.0; extra == "all"
|
|
29
|
+
Requires-Dist: h5py>=3.0; extra == "all"
|
|
30
|
+
Requires-Dist: pyarrow>=10.0; extra == "all"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: jupyter; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# TouchLabel AI — Tactile Data Annotation Toolkit
|
|
36
|
+
|
|
37
|
+
> 触觉数据标注工具包 · pip install 一行搞定
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 基础安装
|
|
43
|
+
pip install tlabel
|
|
44
|
+
|
|
45
|
+
# 带GelSight/DIGIT支持
|
|
46
|
+
pip install tlabel[gelsight]
|
|
47
|
+
|
|
48
|
+
# 带帕西尼支持
|
|
49
|
+
pip install tlabel[paxini]
|
|
50
|
+
|
|
51
|
+
# 带戴盟支持
|
|
52
|
+
pip install tlabel[daimon]
|
|
53
|
+
|
|
54
|
+
# 全部传感器
|
|
55
|
+
pip install tlabel[all]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 5分钟上手
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import tlabel
|
|
62
|
+
|
|
63
|
+
# 加载数据 — 自动识别格式
|
|
64
|
+
data = tlabel.load("gelsight_force.pkl") # GelSight/DIGIT
|
|
65
|
+
data = tlabel.load("paxini_episode.h5") # 帕西尼
|
|
66
|
+
data = tlabel.load("daimon_data/") # 戴盟(目录或.parquet)
|
|
67
|
+
|
|
68
|
+
# 弹出彩色标注面板(Jupyter)
|
|
69
|
+
data.review()
|
|
70
|
+
|
|
71
|
+
# 英文界面
|
|
72
|
+
data.review(lang="en")
|
|
73
|
+
|
|
74
|
+
# 导出
|
|
75
|
+
data.export("output.json") # TLabel Format v2 JSON
|
|
76
|
+
data.export("output.csv") # CSV平面表
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 支持的传感器
|
|
80
|
+
|
|
81
|
+
| 传感器 | 格式 | 状态 |
|
|
82
|
+
|--------|------|------|
|
|
83
|
+
| GelSight Mini | .pkl | ✅ 第一期 |
|
|
84
|
+
| DIGIT | .pkl | ✅ 第一期 |
|
|
85
|
+
| PaXini PXCap | .h5/.hdf5 | ✅ 第一期 |
|
|
86
|
+
| Daimon DM-TacClaw | .parquet / 目录 | ✅ 支持 |
|
|
87
|
+
|
|
88
|
+
## 交互面板功能
|
|
89
|
+
|
|
90
|
+
- 🎨 **彩色时间轴**:绿=接触、红=滑移、灰=无接触
|
|
91
|
+
- 🕸 **18维雷达图**:TLabel Format v2全部维度可视化
|
|
92
|
+
- ✏️ **批量修正**:选中帧区间,一键修改接触/滑移/力度
|
|
93
|
+
- 🔗 **联动规则**:接触=0时自动清除力度和滑移
|
|
94
|
+
- 🌐 **中英文切换**:右上角一键切换
|
|
95
|
+
- 📤 **导出**:JSON / CSV
|
|
96
|
+
|
|
97
|
+
## TLabel Format v2 (18维)
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
# v1 (11维)
|
|
101
|
+
contact · deformation_magnitude · force_magnitude · force_peak
|
|
102
|
+
force_direction · slip_entropy · slip_event · texture_energy
|
|
103
|
+
edge_density · contact_area · centroid_x
|
|
104
|
+
|
|
105
|
+
# v2新增 (7维)
|
|
106
|
+
normal_field_magnitude · normal_field_variance
|
|
107
|
+
shear_field_magnitude · shear_field_direction
|
|
108
|
+
delta_force_normal · delta_force_shear · friction_cone_ratio
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT © 牛宿科技
|
tlabel-0.1.0a1/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# TouchLabel AI — Tactile Data Annotation Toolkit
|
|
2
|
+
|
|
3
|
+
> 触觉数据标注工具包 · pip install 一行搞定
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 基础安装
|
|
9
|
+
pip install tlabel
|
|
10
|
+
|
|
11
|
+
# 带GelSight/DIGIT支持
|
|
12
|
+
pip install tlabel[gelsight]
|
|
13
|
+
|
|
14
|
+
# 带帕西尼支持
|
|
15
|
+
pip install tlabel[paxini]
|
|
16
|
+
|
|
17
|
+
# 带戴盟支持
|
|
18
|
+
pip install tlabel[daimon]
|
|
19
|
+
|
|
20
|
+
# 全部传感器
|
|
21
|
+
pip install tlabel[all]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 5分钟上手
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import tlabel
|
|
28
|
+
|
|
29
|
+
# 加载数据 — 自动识别格式
|
|
30
|
+
data = tlabel.load("gelsight_force.pkl") # GelSight/DIGIT
|
|
31
|
+
data = tlabel.load("paxini_episode.h5") # 帕西尼
|
|
32
|
+
data = tlabel.load("daimon_data/") # 戴盟(目录或.parquet)
|
|
33
|
+
|
|
34
|
+
# 弹出彩色标注面板(Jupyter)
|
|
35
|
+
data.review()
|
|
36
|
+
|
|
37
|
+
# 英文界面
|
|
38
|
+
data.review(lang="en")
|
|
39
|
+
|
|
40
|
+
# 导出
|
|
41
|
+
data.export("output.json") # TLabel Format v2 JSON
|
|
42
|
+
data.export("output.csv") # CSV平面表
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 支持的传感器
|
|
46
|
+
|
|
47
|
+
| 传感器 | 格式 | 状态 |
|
|
48
|
+
|--------|------|------|
|
|
49
|
+
| GelSight Mini | .pkl | ✅ 第一期 |
|
|
50
|
+
| DIGIT | .pkl | ✅ 第一期 |
|
|
51
|
+
| PaXini PXCap | .h5/.hdf5 | ✅ 第一期 |
|
|
52
|
+
| Daimon DM-TacClaw | .parquet / 目录 | ✅ 支持 |
|
|
53
|
+
|
|
54
|
+
## 交互面板功能
|
|
55
|
+
|
|
56
|
+
- 🎨 **彩色时间轴**:绿=接触、红=滑移、灰=无接触
|
|
57
|
+
- 🕸 **18维雷达图**:TLabel Format v2全部维度可视化
|
|
58
|
+
- ✏️ **批量修正**:选中帧区间,一键修改接触/滑移/力度
|
|
59
|
+
- 🔗 **联动规则**:接触=0时自动清除力度和滑移
|
|
60
|
+
- 🌐 **中英文切换**:右上角一键切换
|
|
61
|
+
- 📤 **导出**:JSON / CSV
|
|
62
|
+
|
|
63
|
+
## TLabel Format v2 (18维)
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
# v1 (11维)
|
|
67
|
+
contact · deformation_magnitude · force_magnitude · force_peak
|
|
68
|
+
force_direction · slip_entropy · slip_event · texture_energy
|
|
69
|
+
edge_density · contact_area · centroid_x
|
|
70
|
+
|
|
71
|
+
# v2新增 (7维)
|
|
72
|
+
normal_field_magnitude · normal_field_variance
|
|
73
|
+
shear_field_magnitude · shear_field_direction
|
|
74
|
+
delta_force_normal · delta_force_shear · friction_cone_ratio
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT © 牛宿科技
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tlabel"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "TouchLabel AI - Tactile Data Annotation Toolkit"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Niuzu Tech", email = "luoxi@touchlabelai.cn"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["tactile", "annotation", "robotics", "GelSight", "PaXini", "touch"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
"numpy>=1.20",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
gelsight = ["opencv-python>=4.0"]
|
|
34
|
+
paxini = ["h5py>=3.0"]
|
|
35
|
+
daimon = ["pyarrow>=10.0"]
|
|
36
|
+
all = ["opencv-python>=4.0", "h5py>=3.0", "pyarrow>=10.0"]
|
|
37
|
+
dev = ["pytest", "jupyter"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/liesliy/tlabel"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
include = ["tlabel*"]
|
tlabel-0.1.0a1/setup.cfg
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""TouchLabel pip包单元测试"""
|
|
2
|
+
import json
|
|
3
|
+
import tempfile
|
|
4
|
+
import os
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestImport:
|
|
9
|
+
def test_import_tlabel(self):
|
|
10
|
+
import tlabel
|
|
11
|
+
assert hasattr(tlabel, "load")
|
|
12
|
+
assert hasattr(tlabel, "__version__")
|
|
13
|
+
|
|
14
|
+
def test_import_adapters(self):
|
|
15
|
+
from tlabel.adapters.gelsight import GelSightAdapter
|
|
16
|
+
from tlabel.adapters.paxini import PaxiniAdapter
|
|
17
|
+
from tlabel.adapters.daimon import DaimonAdapter
|
|
18
|
+
|
|
19
|
+
def test_import_types(self):
|
|
20
|
+
from tlabel.core.types import TLabelData, TLabelFrame
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestRegistry:
|
|
24
|
+
def test_pkl_detection(self):
|
|
25
|
+
from tlabel.core.registry import auto_detect_format
|
|
26
|
+
with tempfile.NamedTemporaryFile(suffix=".pkl", delete=False) as f:
|
|
27
|
+
f.write(b"test")
|
|
28
|
+
path = f.name
|
|
29
|
+
try:
|
|
30
|
+
assert auto_detect_format(path) == "gelsight"
|
|
31
|
+
finally:
|
|
32
|
+
os.unlink(path)
|
|
33
|
+
|
|
34
|
+
def test_h5_detection(self):
|
|
35
|
+
from tlabel.core.registry import auto_detect_format
|
|
36
|
+
with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f:
|
|
37
|
+
f.write(b"test")
|
|
38
|
+
path = f.name
|
|
39
|
+
try:
|
|
40
|
+
assert auto_detect_format(path) == "paxini"
|
|
41
|
+
finally:
|
|
42
|
+
os.unlink(path)
|
|
43
|
+
|
|
44
|
+
def test_hdf5_detection(self):
|
|
45
|
+
from tlabel.core.registry import auto_detect_format
|
|
46
|
+
with tempfile.NamedTemporaryFile(suffix=".hdf5", delete=False) as f:
|
|
47
|
+
f.write(b"test")
|
|
48
|
+
path = f.name
|
|
49
|
+
try:
|
|
50
|
+
assert auto_detect_format(path) == "paxini"
|
|
51
|
+
finally:
|
|
52
|
+
os.unlink(path)
|
|
53
|
+
|
|
54
|
+
def test_parquet_detection(self):
|
|
55
|
+
from tlabel.core.registry import auto_detect_format
|
|
56
|
+
with tempfile.NamedTemporaryFile(suffix=".parquet", delete=False) as f:
|
|
57
|
+
f.write(b"test")
|
|
58
|
+
path = f.name
|
|
59
|
+
try:
|
|
60
|
+
assert auto_detect_format(path) == "daimon"
|
|
61
|
+
finally:
|
|
62
|
+
os.unlink(path)
|
|
63
|
+
|
|
64
|
+
def test_daimon_dir_detection(self):
|
|
65
|
+
from tlabel.core.registry import auto_detect_format
|
|
66
|
+
info = {"robot_type": "ugripper_right", "fps": 30, "total_episodes": 1}
|
|
67
|
+
with tempfile.TemporaryDirectory() as td:
|
|
68
|
+
meta_dir = os.path.join(td, "meta")
|
|
69
|
+
os.makedirs(meta_dir)
|
|
70
|
+
with open(os.path.join(meta_dir, "info.json"), "w") as f:
|
|
71
|
+
json.dump(info, f)
|
|
72
|
+
assert auto_detect_format(td) == "daimon"
|
|
73
|
+
|
|
74
|
+
def test_unknown_format(self):
|
|
75
|
+
from tlabel.core.registry import auto_detect_format
|
|
76
|
+
with tempfile.NamedTemporaryFile(suffix=".xyz", delete=False) as f:
|
|
77
|
+
f.write(b"test")
|
|
78
|
+
path = f.name
|
|
79
|
+
try:
|
|
80
|
+
assert auto_detect_format(path) is None
|
|
81
|
+
finally:
|
|
82
|
+
os.unlink(path)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TestTLabelFrame:
|
|
86
|
+
def _make_frame(self, **overrides):
|
|
87
|
+
from tlabel.core.types import TLabelFrame
|
|
88
|
+
defaults = {
|
|
89
|
+
"frame_idx": 0,
|
|
90
|
+
"timestamp_s": 0.0,
|
|
91
|
+
"tlabel_v2": {
|
|
92
|
+
"contact": 1.0,
|
|
93
|
+
"force_magnitude": 0.8,
|
|
94
|
+
"force_peak": 0.6,
|
|
95
|
+
"slip_event": 0.3,
|
|
96
|
+
"contact_area": 0.5,
|
|
97
|
+
"deformation_magnitude": 0.4,
|
|
98
|
+
"force_direction": 0.2,
|
|
99
|
+
"slip_entropy": 0.1,
|
|
100
|
+
"texture_energy": 0.0,
|
|
101
|
+
"edge_density": 0.0,
|
|
102
|
+
"centroid_x": 0.5,
|
|
103
|
+
"normal_field_magnitude": 0.3,
|
|
104
|
+
"normal_field_variance": 0.2,
|
|
105
|
+
"shear_field_magnitude": 0.0,
|
|
106
|
+
"shear_field_direction": 0.0,
|
|
107
|
+
"delta_force_normal": 0.1,
|
|
108
|
+
"delta_force_shear": 0.05,
|
|
109
|
+
"friction_cone_ratio": 0.7,
|
|
110
|
+
},
|
|
111
|
+
"manipulation_phase": "stable_contact",
|
|
112
|
+
"confidence": 0.9,
|
|
113
|
+
}
|
|
114
|
+
defaults.update(overrides)
|
|
115
|
+
return TLabelFrame(**defaults)
|
|
116
|
+
|
|
117
|
+
def test_properties(self):
|
|
118
|
+
f = self._make_frame()
|
|
119
|
+
assert f.contact == 1.0
|
|
120
|
+
assert f.slip_event == 0.3
|
|
121
|
+
assert f.force_magnitude == 0.8
|
|
122
|
+
|
|
123
|
+
def test_patch_basic(self):
|
|
124
|
+
f = self._make_frame()
|
|
125
|
+
rec = f.patch("contact", 0.0, cascade=False)
|
|
126
|
+
assert f.tlabel_v2["contact"] == 0.0
|
|
127
|
+
assert rec["old_value"] == 1.0
|
|
128
|
+
assert rec["new_value"] == 0.0
|
|
129
|
+
|
|
130
|
+
def test_patch_cascade_contact_to_zero(self):
|
|
131
|
+
f = self._make_frame()
|
|
132
|
+
f.patch("contact", 0.0, cascade=True)
|
|
133
|
+
assert f.tlabel_v2["force_magnitude"] == 0.0
|
|
134
|
+
assert f.tlabel_v2["slip_event"] == 0.0
|
|
135
|
+
assert f.tlabel_v2["contact_area"] == 0.0
|
|
136
|
+
assert f.manipulation_phase == "idle"
|
|
137
|
+
|
|
138
|
+
def test_patch_no_cascade_when_not_zero(self):
|
|
139
|
+
f = self._make_frame()
|
|
140
|
+
f.patch("contact", 0.5, cascade=True)
|
|
141
|
+
# force should NOT be zeroed when contact != 0
|
|
142
|
+
assert f.tlabel_v2["force_magnitude"] == 0.8
|
|
143
|
+
|
|
144
|
+
def test_is_modified(self):
|
|
145
|
+
f = self._make_frame()
|
|
146
|
+
assert not f.is_modified
|
|
147
|
+
f.patch("contact", 0.0)
|
|
148
|
+
assert f.is_modified
|
|
149
|
+
|
|
150
|
+
def test_to_dict(self):
|
|
151
|
+
f = self._make_frame()
|
|
152
|
+
d = f.to_dict()
|
|
153
|
+
assert "frame_idx" in d
|
|
154
|
+
assert "tlabel_v2" in d
|
|
155
|
+
assert "manipulation_phase" in d
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestTLabelData:
|
|
159
|
+
def _make_data(self, n_frames=10):
|
|
160
|
+
from tlabel.core.types import TLabelData, TLabelFrame
|
|
161
|
+
frames = []
|
|
162
|
+
for i in range(n_frames):
|
|
163
|
+
f = TLabelFrame(
|
|
164
|
+
frame_idx=i,
|
|
165
|
+
timestamp_s=i / 30.0,
|
|
166
|
+
tlabel_v2={"contact": 1.0 if i % 3 == 0 else 0.0,
|
|
167
|
+
"force_magnitude": 0.5 if i % 3 == 0 else 0.0,
|
|
168
|
+
"slip_event": 0.0, "force_peak": 0.0,
|
|
169
|
+
"deformation_magnitude": 0.0, "force_direction": 0.0,
|
|
170
|
+
"slip_entropy": 0.0, "texture_energy": 0.0,
|
|
171
|
+
"edge_density": 0.0, "contact_area": 0.0,
|
|
172
|
+
"centroid_x": 0.5, "normal_field_magnitude": 0.0,
|
|
173
|
+
"normal_field_variance": 0.0,
|
|
174
|
+
"shear_field_magnitude": 0.0,
|
|
175
|
+
"shear_field_direction": 0.0,
|
|
176
|
+
"delta_force_normal": 0.0, "delta_force_shear": 0.0,
|
|
177
|
+
"friction_cone_ratio": 0.0},
|
|
178
|
+
manipulation_phase="idle",
|
|
179
|
+
confidence=0.9,
|
|
180
|
+
)
|
|
181
|
+
frames.append(f)
|
|
182
|
+
return TLabelData(
|
|
183
|
+
frames=frames,
|
|
184
|
+
sensor_info={"type": "test"},
|
|
185
|
+
episode_info={"source": "test"},
|
|
186
|
+
capabilities={"contact": True},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def test_num_frames(self):
|
|
190
|
+
data = self._make_data(10)
|
|
191
|
+
assert data.num_frames == 10
|
|
192
|
+
|
|
193
|
+
def test_duration(self):
|
|
194
|
+
data = self._make_data(10)
|
|
195
|
+
assert data.duration_s > 0
|
|
196
|
+
|
|
197
|
+
def test_batch_patch(self):
|
|
198
|
+
data = self._make_data(10)
|
|
199
|
+
n = data.batch_patch(0, 9, "contact", 0.0)
|
|
200
|
+
assert n > 0
|
|
201
|
+
|
|
202
|
+
def test_to_dict(self):
|
|
203
|
+
data = self._make_data(5)
|
|
204
|
+
d = data.to_dict()
|
|
205
|
+
assert d["schema_version"] == "0.3.0"
|
|
206
|
+
assert "frames" in d
|
|
207
|
+
assert len(d["frames"]) == 5
|
|
208
|
+
|
|
209
|
+
def test_empty_data(self):
|
|
210
|
+
from tlabel.core.types import TLabelData
|
|
211
|
+
data = TLabelData(
|
|
212
|
+
frames=[], sensor_info={}, episode_info={}, capabilities={}
|
|
213
|
+
)
|
|
214
|
+
assert data.num_frames == 0
|
|
215
|
+
assert data.duration_s == 0.0
|
|
216
|
+
assert data.modified_count == 0
|
|
217
|
+
|
|
218
|
+
def test_len(self):
|
|
219
|
+
data = self._make_data(10)
|
|
220
|
+
assert len(data) == 10
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestLoaderErrors:
|
|
224
|
+
def test_file_not_found(self):
|
|
225
|
+
import tlabel
|
|
226
|
+
with pytest.raises(FileNotFoundError):
|
|
227
|
+
tlabel.load("/nonexistent/path/data.pkl")
|
|
228
|
+
|
|
229
|
+
def test_unknown_format(self):
|
|
230
|
+
import tlabel
|
|
231
|
+
with tempfile.NamedTemporaryFile(suffix=".xyz", delete=False) as f:
|
|
232
|
+
f.write(b"test")
|
|
233
|
+
path = f.name
|
|
234
|
+
try:
|
|
235
|
+
with pytest.raises(ValueError, match="无法识别"):
|
|
236
|
+
tlabel.load(path)
|
|
237
|
+
finally:
|
|
238
|
+
os.unlink(path)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TestExport:
|
|
242
|
+
def test_json_export(self):
|
|
243
|
+
from tlabel.core.types import TLabelData, TLabelFrame
|
|
244
|
+
from tlabel.export.writer import export_data
|
|
245
|
+
frames = [TLabelFrame(
|
|
246
|
+
frame_idx=0, timestamp_s=0.0,
|
|
247
|
+
tlabel_v2={"contact": 1.0, "force_magnitude": 0.5,
|
|
248
|
+
"slip_event": 0.0, "force_peak": 0.0,
|
|
249
|
+
"deformation_magnitude": 0.0, "force_direction": 0.0,
|
|
250
|
+
"slip_entropy": 0.0, "texture_energy": 0.0,
|
|
251
|
+
"edge_density": 0.0, "contact_area": 0.0,
|
|
252
|
+
"centroid_x": 0.5, "normal_field_magnitude": 0.0,
|
|
253
|
+
"normal_field_variance": 0.0,
|
|
254
|
+
"shear_field_magnitude": 0.0,
|
|
255
|
+
"shear_field_direction": 0.0,
|
|
256
|
+
"delta_force_normal": 0.0, "delta_force_shear": 0.0,
|
|
257
|
+
"friction_cone_ratio": 0.0},
|
|
258
|
+
manipulation_phase="idle", confidence=0.9,
|
|
259
|
+
)]
|
|
260
|
+
data = TLabelData(
|
|
261
|
+
frames=frames, sensor_info={"type": "test"},
|
|
262
|
+
episode_info={"source": "test"}, capabilities={"contact": True},
|
|
263
|
+
)
|
|
264
|
+
with tempfile.TemporaryDirectory() as td:
|
|
265
|
+
path = export_data(data, os.path.join(td, "out"), format="json")
|
|
266
|
+
assert os.path.exists(path)
|
|
267
|
+
with open(path) as f:
|
|
268
|
+
d = json.load(f)
|
|
269
|
+
assert d["frames"][0]["tlabel_v2"]["contact"] == 1.0
|
|
270
|
+
|
|
271
|
+
def test_csv_export(self):
|
|
272
|
+
from tlabel.core.types import TLabelData, TLabelFrame
|
|
273
|
+
from tlabel.export.writer import export_data
|
|
274
|
+
frames = [TLabelFrame(
|
|
275
|
+
frame_idx=0, timestamp_s=0.0,
|
|
276
|
+
tlabel_v2={"contact": 1.0, "force_magnitude": 0.5,
|
|
277
|
+
"slip_event": 0.0, "force_peak": 0.0,
|
|
278
|
+
"deformation_magnitude": 0.0, "force_direction": 0.0,
|
|
279
|
+
"slip_entropy": 0.0, "texture_energy": 0.0,
|
|
280
|
+
"edge_density": 0.0, "contact_area": 0.0,
|
|
281
|
+
"centroid_x": 0.5, "normal_field_magnitude": 0.0,
|
|
282
|
+
"normal_field_variance": 0.0,
|
|
283
|
+
"shear_field_magnitude": 0.0,
|
|
284
|
+
"shear_field_direction": 0.0,
|
|
285
|
+
"delta_force_normal": 0.0, "delta_force_shear": 0.0,
|
|
286
|
+
"friction_cone_ratio": 0.0},
|
|
287
|
+
manipulation_phase="idle", confidence=0.9,
|
|
288
|
+
)]
|
|
289
|
+
data = TLabelData(
|
|
290
|
+
frames=frames, sensor_info={"type": "test"},
|
|
291
|
+
episode_info={"source": "test"}, capabilities={"contact": True},
|
|
292
|
+
)
|
|
293
|
+
with tempfile.TemporaryDirectory() as td:
|
|
294
|
+
path = export_data(data, os.path.join(td, "out"), format="csv")
|
|
295
|
+
assert os.path.exists(path)
|
|
296
|
+
with open(path) as f:
|
|
297
|
+
lines = f.readlines()
|
|
298
|
+
assert len(lines) == 2 # header + 1 data row
|
|
299
|
+
assert "contact" in lines[0]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0a1"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
适配器抽象基类 — 所有传感器适配器的统一接口
|
|
3
|
+
|
|
4
|
+
适配器的职责:在load阶段消化格式差异,输出统一的TLabelData。
|
|
5
|
+
review阶段全是TLabel格式,交互完全统一。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Optional, Dict, Any
|
|
10
|
+
|
|
11
|
+
from tlabel.core.types import TLabelData, TLabelFrame
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseAdapter(ABC):
|
|
15
|
+
"""传感器适配器基类"""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def name(self) -> str:
|
|
20
|
+
"""适配器名称"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def supported_extensions(self) -> list:
|
|
26
|
+
"""支持的文件扩展名"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def load(self, file_path: str,
|
|
31
|
+
trajectory_id: Optional[int] = None,
|
|
32
|
+
**kwargs) -> TLabelData:
|
|
33
|
+
"""
|
|
34
|
+
加载数据文件,转换为TLabelData
|
|
35
|
+
|
|
36
|
+
参数:
|
|
37
|
+
file_path: 数据文件路径
|
|
38
|
+
trajectory_id: 轨迹ID(可选)
|
|
39
|
+
**kwargs: 适配器特有参数
|
|
40
|
+
|
|
41
|
+
返回:
|
|
42
|
+
TLabelData — 统一标注容器
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def get_capabilities(self) -> Dict[str, bool]:
|
|
48
|
+
"""返回该传感器的18维能力声明"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def get_sensor_info(self) -> Dict[str, Any]:
|
|
53
|
+
"""返回传感器元信息"""
|
|
54
|
+
pass
|