unienv 0.0.1b5__py3-none-any.whl → 0.0.1b7__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.
Files changed (30) hide show
  1. {unienv-0.0.1b5.dist-info → unienv-0.0.1b7.dist-info}/METADATA +3 -2
  2. {unienv-0.0.1b5.dist-info → unienv-0.0.1b7.dist-info}/RECORD +30 -21
  3. {unienv-0.0.1b5.dist-info → unienv-0.0.1b7.dist-info}/WHEEL +1 -1
  4. unienv_data/base/common.py +25 -10
  5. unienv_data/batches/backend_compat.py +1 -1
  6. unienv_data/batches/combined_batch.py +1 -1
  7. unienv_data/replay_buffer/replay_buffer.py +51 -8
  8. unienv_data/storages/_episode_storage.py +438 -0
  9. unienv_data/storages/_list_storage.py +136 -0
  10. unienv_data/storages/backend_compat.py +268 -0
  11. unienv_data/storages/flattened.py +3 -3
  12. unienv_data/storages/hdf5.py +7 -2
  13. unienv_data/storages/image_storage.py +144 -0
  14. unienv_data/storages/npz_storage.py +135 -0
  15. unienv_data/storages/pytorch.py +16 -9
  16. unienv_data/storages/video_storage.py +297 -0
  17. unienv_data/third_party/tensordict/memmap_tensor.py +1174 -0
  18. unienv_data/transformations/image_compress.py +81 -18
  19. unienv_interface/space/space_utils/batch_utils.py +5 -1
  20. unienv_interface/space/spaces/dict.py +6 -0
  21. unienv_interface/transformations/__init__.py +3 -1
  22. unienv_interface/transformations/batch_and_unbatch.py +43 -4
  23. unienv_interface/transformations/chained_transform.py +9 -8
  24. unienv_interface/transformations/crop.py +69 -0
  25. unienv_interface/transformations/dict_transform.py +8 -2
  26. unienv_interface/transformations/identity.py +16 -0
  27. unienv_interface/transformations/rescale.py +24 -5
  28. unienv_interface/wrapper/backend_compat.py +1 -1
  29. {unienv-0.0.1b5.dist-info → unienv-0.0.1b7.dist-info}/licenses/LICENSE +0 -0
  30. {unienv-0.0.1b5.dist-info → unienv-0.0.1b7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,297 @@
1
+ from typing import Generic, TypeVar, Generic, Optional, Any, Dict, Tuple, Sequence, Union, List, Iterable, Type, Literal, cast
2
+ from fractions import Fraction
3
+ from unienv_interface.space import Space, BoxSpace
4
+ from unienv_interface.space.space_utils import batch_utils as sbu, flatten_utils as sfu
5
+ from unienv_interface.backends import ComputeBackend, BArrayType, BDeviceType, BDtypeType, BRNGType
6
+ from unienv_interface.utils.symbol_util import *
7
+
8
+ from unienv_data.base import SpaceStorage
9
+ from ._episode_storage import IndexableType, EpisodeStorageBase
10
+
11
+ import numpy as np
12
+ import os
13
+ import json
14
+ import shutil
15
+
16
+ import av
17
+ import imageio.v3 as iio
18
+ from imageio.plugins.pyav import PyAVPlugin
19
+ from av.codec.hwaccel import HWAccel, hwdevices_available
20
+ from av.codec import codecs_available
21
+
22
+ class VideoStorage(EpisodeStorageBase[
23
+ BArrayType,
24
+ BArrayType,
25
+ BDeviceType,
26
+ BDtypeType,
27
+ BRNGType,
28
+ ]):
29
+ """
30
+ A storage for RGB or depth video data using video files
31
+ If encoding RGB video
32
+ - Set `buffer_pixel_format` to `rgb24`
33
+ - Set `file_pixel_format` to `None`
34
+ - Set `file_ext` to anything you like (e.g., "mp4", "avi", "mkv", etc.)
35
+ If encoding depth video
36
+ - Set `buffer_pixel_format` to `gray16le` (You can use rescale transform inside a `TransformedStorage` to convert depth values to this format, where `dtype` should be `np.uint16`) - if in meters, set min to 0 and max to 65.535 as the multiplication factor is 1000 (i.e., depth in mm)
37
+ - Set `file_pixel_format` to `gray16le`
38
+ - Set `codec` to a lossless codec that supports gray16le, e.g., `ffv1`
39
+ - Set `file_ext` to `mkv`
40
+ """
41
+
42
+ # ========== Class Attributes ==========
43
+ @classmethod
44
+ def create(
45
+ cls,
46
+ single_instance_space: BoxSpace[BArrayType, BDeviceType, BDtypeType, BRNGType],
47
+ *args,
48
+ hardware_acceleration : Optional[Union[HWAccel, Literal['auto']]] = 'auto',
49
+ codec : Union[str, Literal['auto']] = 'auto',
50
+ file_ext : str = "mp4",
51
+ file_pixel_format : Optional[str] = None,
52
+ buffer_pixel_format : str = "rgb24",
53
+ fps : int = 15,
54
+ capacity : Optional[int] = None,
55
+ cache_path : Optional[str] = None,
56
+ multiprocessing : bool = False,
57
+ **kwargs
58
+ ) -> "VideoStorage[BArrayType, BDeviceType, BDtypeType, BRNGType]":
59
+ assert not multiprocessing, "VideoStorage does not support multiprocessing mode when creating a new mutable storage"
60
+ if cache_path is None:
61
+ raise ValueError("cache_path must be provided for ImageStorage.create")
62
+ assert not os.path.exists(cache_path), f"Cache path {cache_path} already exists"
63
+ os.makedirs(cache_path, exist_ok=True)
64
+ return VideoStorage(
65
+ single_instance_space,
66
+ cache_filename=cache_path,
67
+ hardware_acceleration=hardware_acceleration,
68
+ codec=codec,
69
+ file_ext=file_ext,
70
+ file_pixel_format=file_pixel_format,
71
+ buffer_pixel_format=buffer_pixel_format,
72
+ fps=fps,
73
+ capacity=capacity,
74
+ )
75
+
76
+ @classmethod
77
+ def load_from(
78
+ cls,
79
+ path : Union[str, os.PathLike],
80
+ single_instance_space : BoxSpace[BArrayType, BDeviceType, BDtypeType, BRNGType],
81
+ *,
82
+ hardware_acceleration : Optional[Union[HWAccel, Literal['auto']]] = 'auto',
83
+ codec : Union[str, Literal['auto']] = 'auto',
84
+ capacity : Optional[int] = None,
85
+ read_only : bool = True,
86
+ multiprocessing : bool = False,
87
+ **kwargs
88
+ ) -> "VideoStorage[BArrayType, BDeviceType, BDtypeType, BRNGType]":
89
+ assert read_only or (not multiprocessing), "VideoStorage does not support multiprocessing mode when loading a mutable storage"
90
+ metadata_path = os.path.join(path, "image_metadata.json")
91
+ assert os.path.exists(metadata_path), f"Metadata file {metadata_path} does not exist"
92
+ with open(metadata_path, "r") as f:
93
+ metadata = json.load(f)
94
+ assert metadata["storage_type"] == cls.__name__, \
95
+ f"Expected storage type {cls.__name__}, but found {metadata['storage_type']}"
96
+
97
+ if codec == "auto":
98
+ codec_base = metadata.get("codec_base", None)
99
+ if hardware_acceleration is not None:
100
+ codec = cls.get_auto_codec(base=codec_base)
101
+ else:
102
+ assert codec_base is not None, "Codec base must be specified in metadata to load without hardware acceleration"
103
+ codec = codec_base
104
+ file_ext = metadata["file_ext"]
105
+ fps = int(metadata["fps"])
106
+ file_pixel_format = metadata.get("file_pixel_format", None)
107
+ buffer_pixel_format = metadata.get("buffer_pixel_format", "rgb24")
108
+
109
+ if "capacity" in metadata:
110
+ capacity = None if metadata['capacity'] is None else int(metadata["capacity"])
111
+ length = None if capacity is None else metadata["length"]
112
+
113
+ return VideoStorage(
114
+ single_instance_space,
115
+ cache_filename=path,
116
+ hardware_acceleration=hardware_acceleration,
117
+ codec=codec,
118
+ file_ext=file_ext,
119
+ file_pixel_format=file_pixel_format,
120
+ buffer_pixel_format=buffer_pixel_format,
121
+ fps=fps,
122
+ mutable=not read_only,
123
+ capacity=capacity,
124
+ length=length,
125
+ )
126
+
127
+ # ========== Instance Implementations ==========
128
+ single_file_ext = None
129
+
130
+ @staticmethod
131
+ def get_auto_hwaccel() -> Optional[HWAccel]:
132
+ if hasattr(__class__, "_auto_hwaccel"):
133
+ return __class__._auto_hwaccel
134
+ available_hwdevices = hwdevices_available()
135
+ target_hwaccel = None
136
+ if "d3d11va" in available_hwdevices:
137
+ target_hwaccel = HWAccel(device_type="d3d11va", allow_software_fallback=True)
138
+ elif "cuda" in available_hwdevices:
139
+ target_hwaccel = HWAccel(device_type="cuda", allow_software_fallback=True)
140
+ elif "vaapi" in available_hwdevices:
141
+ target_hwaccel = HWAccel(device_type="vaapi", allow_software_fallback=True)
142
+ elif "videotoolbox" in available_hwdevices:
143
+ target_hwaccel = HWAccel(device_type="videotoolbox", allow_software_fallback=True)
144
+ __class__._auto_hwaccel = target_hwaccel
145
+ return target_hwaccel
146
+
147
+ @staticmethod
148
+ def get_auto_codec(
149
+ base : Optional[str] = None
150
+ ) -> str:
151
+ if hasattr(__class__, "_auto_codec"):
152
+ return __class__._auto_codec
153
+ preferred_codecs = ["av1", "hevc", "h264", "mpeg4", "vp9", "vp8"] if base is None else [base]
154
+ preferred_suffixes = ["_nvenc", "_amf", "_qsv"]
155
+
156
+ target_codec = None
157
+ for codec in preferred_codecs:
158
+ for suffix in preferred_suffixes:
159
+ full_codec_name = codec + suffix
160
+ if full_codec_name in codecs_available:
161
+ target_codec = full_codec_name
162
+ break
163
+
164
+ if target_codec is None:
165
+ for codec in preferred_codecs:
166
+ if codec in codecs_available:
167
+ target_codec = codec
168
+ break
169
+
170
+ if target_codec is None:
171
+ raise RuntimeError("No suitable video codec found in available codecs.")
172
+
173
+ __class__._auto_codec = target_codec
174
+ return target_codec
175
+
176
+ def __init__(
177
+ self,
178
+ single_instance_space: BoxSpace[BArrayType, BDeviceType, BDtypeType, BRNGType],
179
+ cache_filename : Union[str, os.PathLike],
180
+ hardware_acceleration : Optional[Union[HWAccel, Literal['auto']]] = 'auto',
181
+ codec : Union[str, Literal['auto']] = 'auto',
182
+ file_ext : str = "mp4",
183
+ file_pixel_format : Optional[str] = None,
184
+ buffer_pixel_format : str = "rgb24",
185
+ fps : int = 15,
186
+ mutable : bool = True,
187
+ capacity : Optional[int] = None,
188
+ length : int = 0,
189
+ ):
190
+ super().__init__(
191
+ single_instance_space,
192
+ file_ext=file_ext,
193
+ cache_filename=cache_filename,
194
+ mutable=mutable,
195
+ capacity=capacity,
196
+ length=length,
197
+ )
198
+ self.hwaccel = None if hardware_acceleration is None else (
199
+ self.get_auto_hwaccel() if hardware_acceleration == 'auto' else hardware_acceleration
200
+ )
201
+ self.codec = self.get_auto_codec() if codec == 'auto' else codec
202
+ self.fps = fps
203
+ self.file_pixel_format = file_pixel_format
204
+ self.buffer_pixel_format = buffer_pixel_format
205
+
206
+ def get_from_file(self, filename : str, index : Union[IndexableType, BArrayType], total_length : int) -> BArrayType:
207
+ with iio.imopen(filename, 'r', plugin='pyav', hwaccel=self.hwaccel) as video:
208
+ video = cast(PyAVPlugin, video)
209
+ if isinstance(index, int):
210
+ frame_np = video.read(index=index, format=self.buffer_pixel_format)
211
+ frame = self.backend.from_numpy(frame_np)
212
+ if self.device is not None:
213
+ frame = self.backend.to_device(frame, self.device)
214
+ return frame
215
+ else:
216
+ if index is Ellipsis:
217
+ index = np.arange(total_length)
218
+ elif isinstance(index, slice):
219
+ index = np.arange(*index.indices(total_length))
220
+ elif self.backend.is_backendarray(index) and self.backend.dtype_is_boolean(index.dtype):
221
+ index = self.backend.nonzero(index)[0]
222
+ if self.backend.is_backendarray(index):
223
+ index = self.backend.to_numpy(index)
224
+
225
+ argsorted_indices = np.argsort(index)
226
+ sorted_index = index[argsorted_indices]
227
+ reserve_index = np.argsort(argsorted_indices)
228
+
229
+ if len(index) < total_length // 2:
230
+ all_frames_np = []
231
+ for frame_i in sorted_index:
232
+ frame_np = video.read(index=frame_i, format=self.buffer_pixel_format)
233
+ all_frames_np.append(frame_np)
234
+ all_frames_np = np.stack(all_frames_np, axis=0)
235
+ # Reorder from sorted order back to original order
236
+ all_frames_np = all_frames_np[reserve_index]
237
+ else:
238
+ # Create a set for O(1) lookup and a mapping from frame index to position in sorted_index
239
+ sorted_index_set = set(sorted_index)
240
+ frame_to_sorted_pos = {int(frame_idx): pos for pos, frame_idx in enumerate(sorted_index)}
241
+
242
+ # Pre-allocate array to store frames in sorted order
243
+ all_frames_list = [None] * len(sorted_index)
244
+ past_frame_np = None
245
+ video_iter = video.iter(format=self.buffer_pixel_format)
246
+ for frame_i in range(total_length):
247
+ try:
248
+ frame_np = next(video_iter)
249
+ except StopIteration:
250
+ frame_np = past_frame_np
251
+ if frame_i in sorted_index_set:
252
+ # Store at the position corresponding to sorted_index order
253
+ all_frames_list[frame_to_sorted_pos[frame_i]] = frame_np
254
+ past_frame_np = frame_np
255
+ all_frames_np = np.stack(all_frames_list, axis=0)
256
+ # Reorder from sorted order back to original order
257
+ all_frames_np = all_frames_np[reserve_index]
258
+ all_frames = self.backend.from_numpy(all_frames_np)
259
+ if self.device is not None:
260
+ all_frames = self.backend.to_device(all_frames, self.device)
261
+ return all_frames
262
+
263
+ def set_to_file(self, filename : str, value : BArrayType):
264
+ value_np = self.backend.to_numpy(value)
265
+ with iio.imopen(
266
+ filename,
267
+ 'w',
268
+ plugin='pyav',
269
+ ) as video:
270
+ video = cast(PyAVPlugin, video)
271
+ video.init_video_stream(self.codec, fps=self.fps, pixel_format=self.file_pixel_format)
272
+
273
+ # Fix codec time base if not set:
274
+ if video._video_stream.codec_context.time_base is None:
275
+ video._video_stream.codec_context.time_base = Fraction(1 / self.fps).limit_denominator(int(2**16 - 1))
276
+
277
+ for i, frame in enumerate(value_np):
278
+ video.write_frame(frame, pixel_format=self.buffer_pixel_format)
279
+
280
+ def dumps(self, path):
281
+ assert os.path.samefile(path, self.cache_filename), \
282
+ f"Dump path {path} does not match cache filename {self.cache_filename}"
283
+ metadata = {
284
+ "storage_type": __class__.__name__,
285
+ "codec_base": self.codec if "_" not in self.codec else self.codec.split("_")[0],
286
+ "file_ext": self.file_ext,
287
+ "fps": self.fps,
288
+ "file_pixel_format": self.file_pixel_format,
289
+ "buffer_pixel_format": self.buffer_pixel_format,
290
+ "capacity": self.capacity,
291
+ "length": self.length,
292
+ }
293
+ with open(os.path.join(path, "image_metadata.json"), "w") as f:
294
+ json.dump(metadata, f)
295
+
296
+ def close(self):
297
+ pass