humalab 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl

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.

Potentially problematic release.


This version of humalab might be problematic. Click here for more details.

Files changed (37) hide show
  1. humalab/__init__.py +11 -0
  2. humalab/assets/__init__.py +2 -2
  3. humalab/assets/files/resource_file.py +29 -3
  4. humalab/assets/files/urdf_file.py +14 -10
  5. humalab/assets/resource_operator.py +91 -0
  6. humalab/constants.py +39 -5
  7. humalab/dists/bernoulli.py +2 -1
  8. humalab/dists/discrete.py +2 -2
  9. humalab/dists/gaussian.py +2 -2
  10. humalab/dists/log_uniform.py +2 -2
  11. humalab/dists/truncated_gaussian.py +4 -4
  12. humalab/episode.py +181 -11
  13. humalab/humalab.py +44 -28
  14. humalab/humalab_api_client.py +301 -94
  15. humalab/humalab_test.py +46 -17
  16. humalab/metrics/__init__.py +5 -5
  17. humalab/metrics/code.py +28 -0
  18. humalab/metrics/metric.py +41 -108
  19. humalab/metrics/scenario_stats.py +95 -0
  20. humalab/metrics/summary.py +24 -18
  21. humalab/run.py +180 -103
  22. humalab/scenarios/__init__.py +4 -0
  23. humalab/{scenario.py → scenarios/scenario.py} +120 -129
  24. humalab/scenarios/scenario_operator.py +82 -0
  25. humalab/{scenario_test.py → scenarios/scenario_test.py} +150 -269
  26. humalab/utils.py +37 -0
  27. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/METADATA +1 -1
  28. humalab-0.0.6.dist-info/RECORD +39 -0
  29. humalab/assets/resource_manager.py +0 -58
  30. humalab/evaluators/__init__.py +0 -16
  31. humalab/humalab_main.py +0 -119
  32. humalab/metrics/dist_metric.py +0 -22
  33. humalab-0.0.5.dist-info/RECORD +0 -37
  34. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/WHEEL +0 -0
  35. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/entry_points.txt +0 -0
  36. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/licenses/LICENSE +0 -0
  37. {humalab-0.0.5.dist-info → humalab-0.0.6.dist-info}/top_level.txt +0 -0
humalab/__init__.py CHANGED
@@ -1,9 +1,20 @@
1
1
  from humalab.humalab import init, finish, login
2
+ from humalab import assets
3
+ from humalab import metrics
4
+ from humalab import scenarios
2
5
  from humalab.run import Run
6
+ from humalab.constants import MetricDimType, GraphType
7
+ # from humalab import evaluators
3
8
 
4
9
  __all__ = [
5
10
  "init",
6
11
  "finish",
7
12
  "login",
13
+ "assets",
14
+ "metrics",
15
+ "scenarios",
8
16
  "Run",
17
+ "MetricDimType",
18
+ "GraphType",
19
+ # "evaluators",
9
20
  ]
@@ -1,4 +1,4 @@
1
- from .resource_manager import ResourceManager
1
+ from .resource_operator import download, list_resources
2
2
  from .files import ResourceFile, URDFFile
3
3
 
4
- __all__ = ["ResourceManager", "ResourceFile", "URDFFile"]
4
+ __all__ = ["download", "list_resources", "ResourceFile", "URDFFile"]
@@ -1,4 +1,17 @@
1
1
  from datetime import datetime
2
+ from enum import Enum
3
+
4
+ from humalab.constants import DEFAULT_PROJECT
5
+
6
+ class ResourceType(Enum):
7
+ URDF = "urdf"
8
+ MJCF = "mjcf"
9
+ USD = "usd"
10
+ MESH = "mesh"
11
+ VIDEO = "video"
12
+ IMAGE = "image"
13
+ DATA = "data"
14
+
2
15
 
3
16
 
4
17
  class ResourceFile:
@@ -6,16 +19,22 @@ class ResourceFile:
6
19
  name: str,
7
20
  version: int,
8
21
  filename: str,
9
- resource_type: str,
22
+ resource_type: str | ResourceType,
23
+ project: str = DEFAULT_PROJECT,
10
24
  description: str | None = None,
11
25
  created_at: datetime | None = None):
26
+ self._project = project
12
27
  self._name = name
13
28
  self._version = version
14
29
  self._filename = filename
15
- self._resource_type = resource_type
30
+ self._resource_type = ResourceType(resource_type)
16
31
  self._description = description
17
32
  self._created_at = created_at
18
33
 
34
+ @property
35
+ def project(self) -> str:
36
+ return self._project
37
+
19
38
  @property
20
39
  def name(self) -> str:
21
40
  return self._name
@@ -29,7 +48,7 @@ class ResourceFile:
29
48
  return self._filename
30
49
 
31
50
  @property
32
- def resource_type(self) -> str:
51
+ def resource_type(self) -> ResourceType:
33
52
  return self._resource_type
34
53
 
35
54
  @property
@@ -39,3 +58,10 @@ class ResourceFile:
39
58
  @property
40
59
  def description(self) -> str | None:
41
60
  return self._description
61
+
62
+ def __repr__(self) -> str:
63
+ return f"ResourceFile(project={self._project}, name={self._name}, version={self._version}, filename={self._filename}, resource_type={self._resource_type}, description={self._description}, created_at={self._created_at})"
64
+
65
+ def __str__(self) -> str:
66
+ return self.__repr__()
67
+
@@ -1,8 +1,10 @@
1
- from datetime import datetime
2
1
  import os
3
2
  import glob
4
- from humalab.assets.files.resource_file import ResourceFile
3
+ from datetime import datetime
4
+
5
+ from humalab.assets.files.resource_file import ResourceFile, ResourceType
5
6
  from humalab.assets.archive import extract_archive
7
+ from humalab.constants import DEFAULT_PROJECT
6
8
 
7
9
 
8
10
  class URDFFile(ResourceFile):
@@ -10,30 +12,32 @@ class URDFFile(ResourceFile):
10
12
  name: str,
11
13
  version: int,
12
14
  filename: str,
15
+ project: str = DEFAULT_PROJECT,
13
16
  urdf_filename: str | None = None,
14
17
  description: str | None = None,
15
18
  created_at: datetime | None = None,):
16
- super().__init__(name=name,
19
+ super().__init__(project=project,
20
+ name=name,
17
21
  version=version,
18
22
  description=description,
19
23
  filename=filename,
20
- resource_type="URDF",
24
+ resource_type=ResourceType.URDF,
21
25
  created_at=created_at)
22
26
  self._urdf_base_filename = urdf_filename
23
27
  self._urdf_filename, self._root_path = self._extract()
24
28
  self._urdf_filename = os.path.join(self._urdf_filename, self._urdf_filename)
25
29
 
26
30
  def _extract(self):
27
- working_path = os.path.dirname(self._filename)
28
- if os.path.exists(self._filename):
29
- _, ext = os.path.splitext(self._filename)
31
+ working_path = os.path.dirname(self.filename)
32
+ if os.path.exists(self.filename):
33
+ _, ext = os.path.splitext(self.filename)
30
34
  ext = ext.lstrip('.') # Remove leading dot
31
35
  if ext.lower() != "urdf":
32
- extract_archive(self._filename, working_path)
36
+ extract_archive(self.filename, working_path)
33
37
  try:
34
- os.remove(self._filename)
38
+ os.remove(self.filename)
35
39
  except Exception as e:
36
- print(f"Error removing saved file {self._filename}: {e}")
40
+ print(f"Error removing saved file {self.filename}: {e}")
37
41
  local_filename = self.search_resource_file(self._urdf_base_filename, working_path)
38
42
  if local_filename is None:
39
43
  raise ValueError(f"Resource filename {self._urdf_base_filename} not found in {working_path}")
@@ -0,0 +1,91 @@
1
+ from humalab.constants import DEFAULT_PROJECT
2
+ from humalab.assets.files.resource_file import ResourceFile, ResourceType
3
+ from humalab.humalab_config import HumalabConfig
4
+ from humalab.humalab_api_client import HumaLabApiClient
5
+ from humalab.assets.files.urdf_file import URDFFile
6
+ import os
7
+ from typing import Any, Optional
8
+
9
+
10
+ def _asset_dir(humalab_config: HumalabConfig, name: str, version: int) -> str:
11
+ return os.path.join(humalab_config.workspace_path, "assets", name, f"{version}")
12
+
13
+ def _create_asset_dir(humalab_config: HumalabConfig, name: str, version: int) -> bool:
14
+ asset_dir = _asset_dir(humalab_config, name, version)
15
+ if not os.path.exists(asset_dir):
16
+ os.makedirs(asset_dir, exist_ok=True)
17
+ return True
18
+ return False
19
+
20
+ def download(name: str,
21
+ version: int | None=None,
22
+ project: str = DEFAULT_PROJECT,
23
+
24
+ host: str | None = None,
25
+ api_key: str | None = None,
26
+ timeout: float | None = None,
27
+ ) -> Any:
28
+ humalab_config = HumalabConfig()
29
+
30
+ api_client = HumaLabApiClient(base_url=host,
31
+ api_key=api_key,
32
+ timeout=timeout)
33
+
34
+ resource = api_client.get_resource(project_name=project, name=name, version=version)
35
+ filename = os.path.basename(resource['resource_url'])
36
+ filename = os.path.join(_asset_dir(humalab_config, name, resource["version"]), filename)
37
+ if _create_asset_dir(humalab_config, name, resource["version"]):
38
+ file_content = api_client.download_resource(project_name=project, name="lerobot")
39
+ with open(filename, "wb") as f:
40
+ f.write(file_content)
41
+
42
+ if resource["resource_type"].lower() == "urdf":
43
+ return URDFFile(project=project,
44
+ name=name,
45
+ version=resource["version"],
46
+ description=resource.get("description"),
47
+ filename=filename,
48
+ urdf_filename=resource.get("filename"),
49
+ created_at=resource.get("created_at"))
50
+
51
+ return ResourceFile(project=project,
52
+ name=name,
53
+ version=resource["version"],
54
+ filename=filename,
55
+ resource_type=resource["resource_type"],
56
+ description=resource.get("description"),
57
+ created_at=resource.get("created_at"))
58
+
59
+ def list_resources(project: str = DEFAULT_PROJECT,
60
+ resource_types: Optional[list[str | ResourceType]] = None,
61
+ limit: int = 20,
62
+ offset: int = 0,
63
+ latest_only: bool = True,
64
+
65
+ host: str | None = None,
66
+ api_key: str | None = None,
67
+ timeout: float | None = None,) -> list[ResourceFile]:
68
+ api_client = HumaLabApiClient(base_url=host,
69
+ api_key=api_key,
70
+ timeout=timeout)
71
+
72
+ resource_type_string = None
73
+ if resource_types:
74
+ resource_type_strings = {rt.value if isinstance(rt, ResourceType) else rt for rt in resource_types}
75
+ resource_type_string = ",".join(resource_type_strings)
76
+ resp = api_client.get_resources(project_name=project,
77
+ resource_types=resource_type_string,
78
+ limit=limit,
79
+ offset=offset,
80
+ latest_only=latest_only)
81
+ resources = resp.get("resources", [])
82
+ ret_list = []
83
+ for resource in resources:
84
+ ret_list.append(ResourceFile(name=resource["name"],
85
+ version=resource.get("version"),
86
+ project=project,
87
+ filename=resource.get("filename"),
88
+ resource_type=resource.get("resource_type"),
89
+ description=resource.get("description"),
90
+ created_at=resource.get("created_at")))
91
+ return ret_list
humalab/constants.py CHANGED
@@ -1,7 +1,41 @@
1
1
  from enum import Enum
2
2
 
3
- class EpisodeStatus(Enum):
4
- PASS = "pass"
5
- FAILED = "failed"
6
- CANCELED = "canceled"
7
- ERROR = "error"
3
+
4
+ RESERVED_NAMES = {
5
+ "sceanario"
6
+ }
7
+
8
+ DEFAULT_PROJECT = "default"
9
+
10
+
11
+ class ArtifactType(Enum):
12
+ """Types of artifacts that can be stored"""
13
+ METRICS = "metrics" # Run & Episode
14
+ SCENARIO_STATS = "scenario_stats" # Run only
15
+ PYTHON = "python" # Run & Episode
16
+ CODE = "code" # Run & Episode (YAML)
17
+
18
+
19
+ class MetricType(Enum):
20
+ METRICS = ArtifactType.METRICS.value
21
+ SCENARIO_STATS = ArtifactType.SCENARIO_STATS.value
22
+
23
+
24
+ class GraphType(Enum):
25
+ """Types of graphs supported by Humalab."""
26
+ NUMERIC = "numeric"
27
+ LINE = "line"
28
+ BAR = "bar"
29
+ SCATTER = "scatter"
30
+ HISTOGRAM = "histogram"
31
+ GAUSSIAN = "gaussian"
32
+ HEATMAP = "heatmap"
33
+ THREE_D_MAP = "3d_map"
34
+
35
+
36
+ class MetricDimType(Enum):
37
+ """Types of metric dimensions"""
38
+ ZERO_D = "0d"
39
+ ONE_D = "1d"
40
+ TWO_D = "2d"
41
+ THREE_D = "3d"
@@ -31,8 +31,9 @@ class Bernoulli(Distribution):
31
31
  return True
32
32
  if not isinstance(arg1, (int, float)):
33
33
  if isinstance(arg1, (list, np.ndarray)):
34
- if len(arg1) > dimensions:
34
+ if len(arg1) != dimensions:
35
35
  return False
36
+
36
37
  return True
37
38
 
38
39
  def _sample(self) -> int | float | np.ndarray:
humalab/dists/discrete.py CHANGED
@@ -41,11 +41,11 @@ class Discrete(Distribution):
41
41
  return True
42
42
  if not isinstance(arg1, int):
43
43
  if isinstance(arg1, (list, np.ndarray)):
44
- if len(arg1) > dimensions:
44
+ if len(arg1) != dimensions:
45
45
  return False
46
46
  if not isinstance(arg2, int):
47
47
  if isinstance(arg2, (list, np.ndarray)):
48
- if len(arg2) > dimensions:
48
+ if len(arg2) != dimensions:
49
49
  return False
50
50
  return True
51
51
 
humalab/dists/gaussian.py CHANGED
@@ -37,11 +37,11 @@ class Gaussian(Distribution):
37
37
  return True
38
38
  if not isinstance(arg1, (int, float)):
39
39
  if isinstance(arg1, (list, np.ndarray)):
40
- if len(arg1) > dimensions:
40
+ if len(arg1) != dimensions:
41
41
  return False
42
42
  if not isinstance(arg2, (int, float)):
43
43
  if isinstance(arg2, (list, np.ndarray)):
44
- if len(arg2) > dimensions:
44
+ if len(arg2) != dimensions:
45
45
  return False
46
46
  return True
47
47
 
@@ -37,11 +37,11 @@ class LogUniform(Distribution):
37
37
  return True
38
38
  if not isinstance(arg1, (int, float)):
39
39
  if isinstance(arg1, (list, np.ndarray)):
40
- if len(arg1) > dimensions:
40
+ if len(arg1) != dimensions:
41
41
  return False
42
42
  if not isinstance(arg2, (int, float)):
43
43
  if isinstance(arg2, (list, np.ndarray)):
44
- if len(arg2) > dimensions:
44
+ if len(arg2) != dimensions:
45
45
  return False
46
46
  return True
47
47
 
@@ -49,19 +49,19 @@ class TruncatedGaussian(Distribution):
49
49
  return True
50
50
  if not isinstance(arg1, (int, float)):
51
51
  if isinstance(arg1, (list, np.ndarray)):
52
- if len(arg1) > dimensions:
52
+ if len(arg1) != dimensions:
53
53
  return False
54
54
  if not isinstance(arg2, (int, float)):
55
55
  if isinstance(arg2, (list, np.ndarray)):
56
- if len(arg2) > dimensions:
56
+ if len(arg2) != dimensions:
57
57
  return False
58
58
  if not isinstance(arg3, (int, float)):
59
59
  if isinstance(arg3, (list, np.ndarray)):
60
- if len(arg3) > dimensions:
60
+ if len(arg3) != dimensions:
61
61
  return False
62
62
  if not isinstance(arg4, (int, float)):
63
63
  if isinstance(arg4, (list, np.ndarray)):
64
- if len(arg4) > dimensions:
64
+ if len(arg4) != dimensions:
65
65
  return False
66
66
  return True
67
67
 
humalab/episode.py CHANGED
@@ -1,20 +1,121 @@
1
+ from humalab.constants import RESERVED_NAMES, ArtifactType
2
+ from humalab.humalab_api_client import HumaLabApiClient, EpisodeStatus
3
+ from humalab.metrics.code import Code
4
+ from humalab.metrics.summary import Summary
5
+ from humalab.metrics.metric import Metrics
6
+ from omegaconf import DictConfig, ListConfig, OmegaConf
7
+ from typing import Any
8
+ import pickle
9
+ import traceback
1
10
 
2
- from humalab.scenario import Scenario
3
- from omegaconf import DictConfig, OmegaConf
11
+ from humalab.utils import is_standard_type
4
12
 
5
13
 
6
14
  class Episode:
7
- def __init__(self, run_id: str, episode_id: str, scenario_conf: DictConfig):
8
- self.run_id = run_id
9
- self.episode_id = episode_id
10
- self.scenario_conf = scenario_conf
15
+ def __init__(self,
16
+ run_id: str,
17
+ episode_id: str,
18
+ scenario_conf: DictConfig | ListConfig,
19
+ episode_vals: dict | None = None,
20
+
21
+ base_url: str | None = None,
22
+ api_key: str | None = None,
23
+ timeout: float | None = None,):
24
+ self._run_id = run_id
25
+ self._episode_id = episode_id
26
+ self._episode_status = EpisodeStatus.RUNNING
27
+ self._scenario_conf = scenario_conf
28
+ self._logs = {}
29
+ self._episode_vals = episode_vals or {}
30
+ self._is_finished = False
31
+
32
+ self._api_client = HumaLabApiClient(base_url=base_url,
33
+ api_key=api_key,
34
+ timeout=timeout)
35
+
36
+ @property
37
+ def run_id(self) -> str:
38
+ return self._run_id
39
+
40
+ @property
41
+ def episode_id(self) -> str:
42
+ return self._episode_id
11
43
 
12
44
  @property
13
- def scenario(self) -> DictConfig:
14
- return self.scenario_conf
45
+ def scenario(self) -> DictConfig | ListConfig:
46
+ return self._scenario_conf
47
+
48
+ @property
49
+ def status(self) -> EpisodeStatus:
50
+ return self._episode_status
51
+
52
+ @property
53
+ def episode_vals(self) -> dict:
54
+ return self._episode_vals
55
+
56
+ @property
57
+ def is_finished(self) -> bool:
58
+ return self._is_finished
59
+
60
+ def __enter__(self):
61
+ return self
62
+
63
+ def __exit__(self, exception_type, exception_value, exception_traceback):
64
+ if self._is_finished:
65
+ return
66
+ if exception_type is not None:
67
+ err_msg = "".join(traceback.format_exception(exception_type, exception_value, exception_traceback))
68
+ self.finish(status=EpisodeStatus.ERRORED, err_msg=err_msg)
69
+ else:
70
+ self.finish(status=EpisodeStatus.SUCCESS)
15
71
 
16
- def finish(self):
17
- print(f"Finishing episode {self.episode_id} for scenario {self.scenario.name}")
72
+ def __getattr__(self, name: Any) -> Any:
73
+ if name in self._scenario_conf:
74
+ return self._scenario_conf[name]
75
+ raise AttributeError(f"'Scenario' object has no attribute '{name}'")
76
+
77
+ def __getitem__(self, key: Any) -> Any:
78
+ if key in self._scenario_conf:
79
+ return self._scenario_conf[key]
80
+ raise KeyError(f"'Scenario' object has no key '{key}'")
81
+
82
+ def add_metric(self, name: str, metric: Metrics) -> None:
83
+ if name in self._logs:
84
+ raise ValueError(f"{name} is a reserved name and is not allowed.")
85
+ self._logs[name] = metric
86
+
87
+ def log_code(self, key: str, code_content: str) -> None:
88
+ """Log code content as an artifact.
89
+
90
+ Args:
91
+ key (str): The key for the code artifact.
92
+ code_content (str): The code content to log.
93
+ """
94
+ if key in RESERVED_NAMES:
95
+ raise ValueError(f"{key} is a reserved name and is not allowed.")
96
+ self._logs[key] = Code(
97
+ run_id=self._run_id,
98
+ key=key,
99
+ code_content=code_content,
100
+ episode_id=self._episode_id
101
+ )
102
+
103
+ def log(self, data: dict, x: dict | None = None, replace: bool = False) -> None:
104
+ for key, value in data.items():
105
+ if key in RESERVED_NAMES:
106
+ raise ValueError(f"{key} is a reserved name and is not allowed.")
107
+ if key not in self._logs:
108
+ self._logs[key] = value
109
+ else:
110
+ cur_val = self._logs[key]
111
+ if isinstance(cur_val, Metrics):
112
+ cur_x = x.get(key) if x is not None else None
113
+ cur_val.log(value, x=cur_x, replace=replace)
114
+ else:
115
+ if replace:
116
+ self._logs[key] = value
117
+ else:
118
+ raise ValueError(f"Cannot log value for key '{key}' as there is already a value logged.")
18
119
 
19
120
  @property
20
121
  def yaml(self) -> str:
@@ -23,4 +124,73 @@ class Episode:
23
124
  Returns:
24
125
  str: The current scenario as a YAML string.
25
126
  """
26
- return OmegaConf.to_yaml(self.scenario_conf)
127
+ return OmegaConf.to_yaml(self._scenario_conf)
128
+
129
+ def discard(self) -> None:
130
+ self._finish(EpisodeStatus.CANCELED)
131
+
132
+ def success(self) -> None:
133
+ self._finish(EpisodeStatus.SUCCESS)
134
+
135
+ def fail(self) -> None:
136
+ self._finish(EpisodeStatus.FAILED)
137
+
138
+ def finish(self, status: EpisodeStatus, err_msg: str | None = None) -> None:
139
+ if self._is_finished:
140
+ return
141
+ self._is_finished = True
142
+ self._episode_status = status
143
+
144
+ self._api_client.upload_code(
145
+ artifact_key="scenario",
146
+ run_id=self._run_id,
147
+ episode_id=self._episode_id,
148
+ code_content=self.yaml
149
+ )
150
+
151
+ # TODO: submit final metrics
152
+ for key, value in self._logs.items():
153
+ if isinstance(value, Summary):
154
+ metric_val = value.finalize()
155
+ pickled = pickle.dumps(metric_val["value"])
156
+ self._api_client.upload_python(
157
+ artifact_key=key,
158
+ run_id=self._id,
159
+ episode_id=self._episode_id,
160
+ pickled_bytes=pickled
161
+ )
162
+ elif isinstance(value, Metrics):
163
+ metric_val = value.finalize()
164
+ pickled = pickle.dumps(metric_val)
165
+ self._api_client.upload_metrics(
166
+ artifact_key=key,
167
+ run_id=self._id,
168
+ episode_id=self._episode_id,
169
+ pickled_bytes=pickled,
170
+ graph_type=value.graph_type.value,
171
+ metric_dim_type=value.metric_dim_type.value
172
+ )
173
+ elif isinstance(value, Code):
174
+ self._api_client.upload_code(
175
+ artifact_key=value.key,
176
+ run_id=value.run_id,
177
+ episode_id=value.episode_id,
178
+ code_content=value.code_content
179
+ )
180
+ else:
181
+ if not is_standard_type(value):
182
+ raise ValueError(f"Value for key '{key}' is not a standard type.")
183
+ pickled = pickle.dumps(value)
184
+ self._api_client.upload_python(
185
+ artifact_key=key,
186
+ run_id=self._run_id,
187
+ episode_id=self._episode_id,
188
+ pickled_bytes=pickled
189
+ )
190
+
191
+ self._api_client.update_episode(
192
+ run_id=self._run_id,
193
+ episode_id=self._episode_id,
194
+ status=status,
195
+ err_msg=err_msg
196
+ )