gsvvcompressor 1.2.0__cp311-cp311-win_amd64.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 (50) hide show
  1. gsvvcompressor/__init__.py +13 -0
  2. gsvvcompressor/__main__.py +243 -0
  3. gsvvcompressor/combinations/__init__.py +84 -0
  4. gsvvcompressor/combinations/registry.py +52 -0
  5. gsvvcompressor/combinations/vq_xyz_1mask.py +89 -0
  6. gsvvcompressor/combinations/vq_xyz_1mask_zstd.py +103 -0
  7. gsvvcompressor/combinations/vq_xyz_draco.py +468 -0
  8. gsvvcompressor/combinations/vq_xyz_draco_2pass.py +156 -0
  9. gsvvcompressor/combinations/vq_xyz_zstd.py +106 -0
  10. gsvvcompressor/compress/__init__.py +5 -0
  11. gsvvcompressor/compress/zstd.py +144 -0
  12. gsvvcompressor/decoder.py +155 -0
  13. gsvvcompressor/deserializer.py +42 -0
  14. gsvvcompressor/draco/__init__.py +34 -0
  15. gsvvcompressor/draco/draco_decoder.exe +0 -0
  16. gsvvcompressor/draco/draco_encoder.exe +0 -0
  17. gsvvcompressor/draco/dracoreduced3dgs.cp311-win_amd64.pyd +0 -0
  18. gsvvcompressor/draco/interface.py +339 -0
  19. gsvvcompressor/draco/serialize.py +235 -0
  20. gsvvcompressor/draco/twopass.py +359 -0
  21. gsvvcompressor/encoder.py +122 -0
  22. gsvvcompressor/interframe/__init__.py +11 -0
  23. gsvvcompressor/interframe/combine.py +271 -0
  24. gsvvcompressor/interframe/decoder.py +99 -0
  25. gsvvcompressor/interframe/encoder.py +92 -0
  26. gsvvcompressor/interframe/interface.py +221 -0
  27. gsvvcompressor/interframe/twopass.py +226 -0
  28. gsvvcompressor/io/__init__.py +31 -0
  29. gsvvcompressor/io/bytes.py +103 -0
  30. gsvvcompressor/io/config.py +78 -0
  31. gsvvcompressor/io/gaussian_model.py +127 -0
  32. gsvvcompressor/movecameras.py +33 -0
  33. gsvvcompressor/payload.py +34 -0
  34. gsvvcompressor/serializer.py +42 -0
  35. gsvvcompressor/vq/__init__.py +15 -0
  36. gsvvcompressor/vq/interface.py +324 -0
  37. gsvvcompressor/vq/singlemask.py +127 -0
  38. gsvvcompressor/vq/twopass.py +1 -0
  39. gsvvcompressor/xyz/__init__.py +26 -0
  40. gsvvcompressor/xyz/dense.py +39 -0
  41. gsvvcompressor/xyz/interface.py +382 -0
  42. gsvvcompressor/xyz/knn.py +141 -0
  43. gsvvcompressor/xyz/quant.py +143 -0
  44. gsvvcompressor/xyz/size.py +44 -0
  45. gsvvcompressor/xyz/twopass.py +1 -0
  46. gsvvcompressor-1.2.0.dist-info/METADATA +690 -0
  47. gsvvcompressor-1.2.0.dist-info/RECORD +50 -0
  48. gsvvcompressor-1.2.0.dist-info/WHEEL +5 -0
  49. gsvvcompressor-1.2.0.dist-info/licenses/LICENSE +21 -0
  50. gsvvcompressor-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,468 @@
1
+ """
2
+ VQ + XYZ quantization + Draco compression encoder/decoder combination.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Optional, Self
7
+
8
+ import numpy as np
9
+ import torch
10
+
11
+ from ..draco.interface import (
12
+ DracoInterframeCodecInterface,
13
+ DracoInterframeCodecTranscodingInterface,
14
+ DracoPayload,
15
+ )
16
+ from ..draco.serialize import DracoSerializer, DracoDeserializer
17
+ from ..interframe.combine import (
18
+ CombinedInterframeEncoderInitConfig,
19
+ CombinedPayload,
20
+ )
21
+ from ..interframe.encoder import InterframeEncoder
22
+ from ..interframe.decoder import InterframeDecoder
23
+ from ..payload import Payload
24
+ from ..vq.interface import (
25
+ VQInterframeCodecConfig,
26
+ VQKeyframePayload,
27
+ )
28
+ from ..vq.singlemask import VQMergeMaskInterframePayload
29
+ from ..xyz.interface import (
30
+ XYZQuantInterframeCodecConfig,
31
+ XYZQuantKeyframePayload,
32
+ XYZQuantInterframePayload,
33
+ )
34
+ from ..xyz.quant import XYZQuantConfig
35
+ from .registry import register_encoder, register_decoder
36
+ from .vq_xyz_1mask import VQXYZQuantMergeMaskInterframeCodecInterface
37
+
38
+
39
+ # =============================================================================
40
+ # Helper functions for converting between VQ ids_dict and Draco numpy arrays
41
+ # =============================================================================
42
+
43
+ def vq_ids_dict_to_draco_arrays(
44
+ ids_dict: Dict[str, torch.Tensor],
45
+ max_sh_degree: int,
46
+ ) -> tuple:
47
+ """
48
+ Convert VQ ids_dict to Draco format numpy arrays.
49
+
50
+ Args:
51
+ ids_dict: Dictionary of VQ indices.
52
+ max_sh_degree: Maximum SH degree (for features_rest).
53
+
54
+ Returns:
55
+ Tuple of (scales, rotations, opacities, features_dc, features_rest) numpy arrays.
56
+ """
57
+ assert max_sh_degree == 3, "Only max_sh_degree=3 is supported."
58
+
59
+ scales = ids_dict["scaling"].cpu().numpy().reshape(-1, 1).astype(np.int32)
60
+ rotations = np.column_stack([
61
+ ids_dict["rotation_re"].cpu().numpy(),
62
+ ids_dict["rotation_im"].cpu().numpy()
63
+ ]).astype(np.int32)
64
+ opacities = ids_dict["opacity"].cpu().numpy().reshape(-1, 1).astype(np.int32)
65
+ features_dc = ids_dict["features_dc"].cpu().numpy().reshape(-1, 1).astype(np.int32)
66
+ features_rest_list = [
67
+ ids_dict[f"features_rest_{sh_degree}"].cpu().numpy()
68
+ for sh_degree in range(max_sh_degree)
69
+ ]
70
+ features_rest = np.column_stack(features_rest_list).astype(np.int32)
71
+
72
+ return scales, rotations, opacities, features_dc, features_rest
73
+
74
+
75
+ def draco_arrays_to_vq_ids_dict(
76
+ scales: np.ndarray,
77
+ rotations: np.ndarray,
78
+ opacities: np.ndarray,
79
+ features_dc: np.ndarray,
80
+ features_rest: np.ndarray,
81
+ max_sh_degree: int,
82
+ ) -> Dict[str, torch.Tensor]:
83
+ """
84
+ Convert Draco format numpy arrays back to VQ ids_dict.
85
+
86
+ Args:
87
+ scales: Scale indices, shape (N, 1).
88
+ rotations: Rotation indices, shape (N, 2).
89
+ opacities: Opacity indices, shape (N, 1).
90
+ features_dc: DC feature indices, shape (N, 1).
91
+ features_rest: Rest feature indices, shape (N, max_sh_degree*3).
92
+ max_sh_degree: Maximum SH degree.
93
+
94
+ Returns:
95
+ Dictionary of VQ indices as torch tensors.
96
+ """
97
+ assert max_sh_degree == 3, "Only max_sh_degree=3 is supported."
98
+
99
+ ids_dict = {
100
+ 'scaling': torch.from_numpy(scales.flatten()),
101
+ 'rotation_re': torch.from_numpy(rotations[:, 0].copy()),
102
+ 'rotation_im': torch.from_numpy(rotations[:, 1].copy()),
103
+ 'opacity': torch.from_numpy(opacities.flatten()),
104
+ 'features_dc': torch.from_numpy(features_dc.flatten()).unsqueeze(-1),
105
+ }
106
+
107
+ for sh_degree in range(max_sh_degree):
108
+ start_idx = sh_degree * 3
109
+ end_idx = start_idx + 3
110
+ ids_dict[f'features_rest_{sh_degree}'] = torch.from_numpy(
111
+ features_rest[:, start_idx:end_idx].copy()
112
+ )
113
+
114
+ return ids_dict
115
+
116
+
117
+ # =============================================================================
118
+ # Extra payload classes for Draco format
119
+ # =============================================================================
120
+
121
+ @dataclass
122
+ class VQXYZDracoKeyframeExtra(Payload):
123
+ """
124
+ Extra data for VQ+XYZ keyframe that cannot be stored in Draco format.
125
+
126
+ Attributes:
127
+ quant_config: XYZ quantization configuration.
128
+ codebook_dict: VQ codebooks.
129
+ max_sh_degree: Maximum SH degree.
130
+ """
131
+ quant_config: XYZQuantConfig
132
+ codebook_dict: Dict[str, torch.Tensor]
133
+ max_sh_degree: int
134
+
135
+ def to(self, device) -> Self:
136
+ return VQXYZDracoKeyframeExtra(
137
+ quant_config=XYZQuantConfig(
138
+ step_size=self.quant_config.step_size,
139
+ origin=self.quant_config.origin.to(device),
140
+ ),
141
+ codebook_dict={k: v.to(device) for k, v in self.codebook_dict.items()},
142
+ max_sh_degree=self.max_sh_degree,
143
+ )
144
+
145
+
146
+ @dataclass
147
+ class VQXYZDracoInterframeExtra(Payload):
148
+ """
149
+ Extra data for VQ+XYZ interframe that cannot be stored in Draco format.
150
+
151
+ Attributes:
152
+ ids_mask: Boolean tensor indicating which positions changed.
153
+ """
154
+ ids_mask: torch.Tensor
155
+
156
+ def to(self, device) -> Self:
157
+ return VQXYZDracoInterframeExtra(
158
+ ids_mask=self.ids_mask.to(device),
159
+ )
160
+
161
+
162
+ class VQXYZDracoInterframeCodecTranscodingInterface(DracoInterframeCodecTranscodingInterface):
163
+ """
164
+ Transcoding interface for VQ + XYZ combined codec to/from DracoPayload.
165
+
166
+ For keyframes:
167
+ - Positions: quantized XYZ coordinates (int32)
168
+ - Scales, rotations, opacities, features_dc, features_rest: VQ indices
169
+ - Extra: quant_config, codebook_dict, max_sh_degree, tolerance
170
+
171
+ For interframes:
172
+ - Positions: sparse quantized XYZ (only changed positions)
173
+ - Scales, rotations, opacities, features_dc, features_rest: sparse VQ indices
174
+ - Extra: ids_mask (boolean tensor indicating changed positions)
175
+ """
176
+
177
+ def keyframe_payload_to_draco(self, payload: CombinedPayload) -> DracoPayload:
178
+ """
179
+ Convert a VQ+XYZ keyframe CombinedPayload to DracoPayload.
180
+
181
+ Args:
182
+ payload: CombinedPayload containing [XYZQuantKeyframePayload, VQKeyframePayload]
183
+
184
+ Returns:
185
+ DracoPayload with full point cloud data and extra metadata.
186
+ """
187
+ xyz_payload: XYZQuantKeyframePayload = payload.payloads[0]
188
+ vq_payload: VQKeyframePayload = payload.payloads[1]
189
+
190
+ # Convert quantized XYZ to numpy
191
+ positions = xyz_payload.quantized_xyz.cpu().numpy().astype(np.int32)
192
+
193
+ # Convert VQ ids_dict to Draco arrays using helper function
194
+ scales, rotations, opacities, features_dc, features_rest = vq_ids_dict_to_draco_arrays(
195
+ vq_payload.ids_dict, vq_payload.max_sh_degree
196
+ )
197
+
198
+ # Store extra data
199
+ extra = VQXYZDracoKeyframeExtra(
200
+ quant_config=xyz_payload.quant_config,
201
+ codebook_dict=vq_payload.codebook_dict,
202
+ max_sh_degree=vq_payload.max_sh_degree,
203
+ )
204
+
205
+ return DracoPayload(
206
+ positions=positions,
207
+ scales=scales,
208
+ rotations=rotations,
209
+ opacities=opacities,
210
+ features_dc=features_dc,
211
+ features_rest=features_rest,
212
+ extra=extra,
213
+ )
214
+
215
+ def draco_to_keyframe_payload(self, draco_payload: DracoPayload) -> CombinedPayload:
216
+ """
217
+ Convert a DracoPayload back to VQ+XYZ keyframe CombinedPayload.
218
+
219
+ Args:
220
+ draco_payload: DracoPayload with point cloud data and extra metadata.
221
+
222
+ Returns:
223
+ CombinedPayload containing [XYZQuantKeyframePayload, VQKeyframePayload]
224
+ """
225
+ extra: VQXYZDracoKeyframeExtra = draco_payload.extra
226
+
227
+ # Reconstruct quantized XYZ
228
+ quantized_xyz = torch.from_numpy(draco_payload.positions.copy())
229
+
230
+ # Reconstruct VQ ids_dict using helper function
231
+ ids_dict = draco_arrays_to_vq_ids_dict(
232
+ draco_payload.scales,
233
+ draco_payload.rotations,
234
+ draco_payload.opacities,
235
+ draco_payload.features_dc,
236
+ draco_payload.features_rest,
237
+ extra.max_sh_degree,
238
+ )
239
+
240
+ # Create XYZ keyframe payload
241
+ xyz_payload = XYZQuantKeyframePayload(
242
+ quant_config=extra.quant_config,
243
+ quantized_xyz=quantized_xyz,
244
+ )
245
+
246
+ # Create VQ keyframe payload
247
+ vq_payload = VQKeyframePayload(
248
+ ids_dict=ids_dict,
249
+ codebook_dict=extra.codebook_dict,
250
+ max_sh_degree=extra.max_sh_degree,
251
+ )
252
+
253
+ return CombinedPayload(payloads=[xyz_payload, vq_payload])
254
+
255
+ def interframe_payload_to_draco(self, payload: CombinedPayload) -> DracoPayload:
256
+ """
257
+ Convert a VQ+XYZ interframe CombinedPayload to DracoPayload.
258
+
259
+ Args:
260
+ payload: CombinedPayload containing [XYZQuantInterframePayload, VQMergeMaskInterframePayload]
261
+
262
+ Returns:
263
+ DracoPayload with sparse changed data and mask in extra.
264
+ """
265
+ xyz_payload: XYZQuantInterframePayload = payload.payloads[0]
266
+ vq_payload: VQMergeMaskInterframePayload = payload.payloads[1]
267
+ assert torch.equal(
268
+ xyz_payload.xyz_mask, vq_payload.ids_mask
269
+ ), "Masks in XYZ and VQ payloads must be the same."
270
+
271
+ # Convert sparse quantized XYZ to numpy
272
+ positions = xyz_payload.quantized_xyz.cpu().numpy().astype(np.int32)
273
+
274
+ # Convert sparse VQ ids_dict to Draco arrays using helper function
275
+ # Note: max_sh_degree=3 is assumed (asserted in helper function)
276
+ scales, rotations, opacities, features_dc, features_rest = vq_ids_dict_to_draco_arrays(
277
+ vq_payload.ids_dict, max_sh_degree=3
278
+ )
279
+
280
+ # Store mask in extra (both payloads should have the same merged mask)
281
+ extra = VQXYZDracoInterframeExtra(
282
+ ids_mask=vq_payload.ids_mask,
283
+ )
284
+
285
+ return DracoPayload(
286
+ positions=positions,
287
+ scales=scales,
288
+ rotations=rotations,
289
+ opacities=opacities,
290
+ features_dc=features_dc,
291
+ features_rest=features_rest,
292
+ extra=extra,
293
+ )
294
+
295
+ def draco_to_interframe_payload(self, draco_payload: DracoPayload) -> CombinedPayload:
296
+ """
297
+ Convert a DracoPayload back to VQ+XYZ interframe CombinedPayload.
298
+
299
+ Args:
300
+ draco_payload: DracoPayload with sparse changed data and mask in extra.
301
+
302
+ Returns:
303
+ CombinedPayload containing [XYZQuantInterframePayload, VQMergeMaskInterframePayload]
304
+ """
305
+ extra: VQXYZDracoInterframeExtra = draco_payload.extra
306
+
307
+ # Reconstruct sparse quantized XYZ
308
+ quantized_xyz = torch.from_numpy(draco_payload.positions.copy())
309
+
310
+ # Reconstruct sparse VQ ids_dict using helper function
311
+ ids_dict = draco_arrays_to_vq_ids_dict(
312
+ draco_payload.scales,
313
+ draco_payload.rotations,
314
+ draco_payload.opacities,
315
+ draco_payload.features_dc,
316
+ draco_payload.features_rest,
317
+ max_sh_degree=3,
318
+ )
319
+
320
+ # Create XYZ interframe payload with merged mask
321
+ xyz_payload = XYZQuantInterframePayload(
322
+ xyz_mask=extra.ids_mask,
323
+ quantized_xyz=quantized_xyz,
324
+ )
325
+
326
+ # Create VQ interframe payload with merged mask
327
+ vq_payload = VQMergeMaskInterframePayload(
328
+ ids_mask=extra.ids_mask,
329
+ ids_dict=ids_dict,
330
+ )
331
+
332
+ return CombinedPayload(payloads=[xyz_payload, vq_payload])
333
+
334
+
335
+ def VQXYZDracoEncoder(
336
+ vq_config: VQInterframeCodecConfig,
337
+ xyz_config: XYZQuantInterframeCodecConfig,
338
+ zstd_level: int = 7,
339
+ draco_level: int = 0,
340
+ qp: int = 0,
341
+ qscale: int = 0,
342
+ qrotation: int = 0,
343
+ qopacity: int = 0,
344
+ qfeaturedc: int = 0,
345
+ qfeaturerest: int = 0,
346
+ payload_device: Optional[str] = None,
347
+ ) -> InterframeEncoder:
348
+ """Create an encoder combining VQ + XYZ quantization + Draco compression."""
349
+
350
+ # Use merged mask interface for better compression efficiency
351
+ combined_interface = VQXYZQuantMergeMaskInterframeCodecInterface()
352
+
353
+ # Create transcoding interface
354
+ transcoder = VQXYZDracoInterframeCodecTranscodingInterface()
355
+
356
+ # Wrap with Draco interface
357
+ draco_interface = DracoInterframeCodecInterface(combined_interface, transcoder)
358
+
359
+ combined_config = CombinedInterframeEncoderInitConfig(
360
+ init_configs=[xyz_config, vq_config]
361
+ )
362
+
363
+ serializer = DracoSerializer(
364
+ zstd_level=zstd_level,
365
+ draco_level=draco_level,
366
+ qp=qp,
367
+ qscale=qscale,
368
+ qrotation=qrotation,
369
+ qopacity=qopacity,
370
+ qfeaturedc=qfeaturedc,
371
+ qfeaturerest=qfeaturerest,
372
+ )
373
+
374
+ return InterframeEncoder(
375
+ serializer=serializer,
376
+ interface=draco_interface,
377
+ init_config=combined_config,
378
+ payload_device=payload_device,
379
+ )
380
+
381
+
382
+ def VQXYZDracoDecoder(
383
+ payload_device: Optional[str] = None,
384
+ device: Optional[str] = None,
385
+ ) -> InterframeDecoder:
386
+ """Create a decoder for VQ + XYZ quantization + Draco compressed data."""
387
+
388
+ # Use merged mask interface for better compression efficiency
389
+ combined_interface = VQXYZQuantMergeMaskInterframeCodecInterface()
390
+
391
+ # Create transcoding interface
392
+ transcoder = VQXYZDracoInterframeCodecTranscodingInterface()
393
+
394
+ # Wrap with Draco interface
395
+ draco_interface = DracoInterframeCodecInterface(combined_interface, transcoder)
396
+
397
+ deserializer = DracoDeserializer()
398
+
399
+ return InterframeDecoder(
400
+ deserializer=deserializer,
401
+ interface=draco_interface,
402
+ payload_device=payload_device,
403
+ device=device,
404
+ )
405
+
406
+
407
+ @dataclass
408
+ class VQXYZDracoEncoderConfig:
409
+ """Configuration for VQ + XYZ + Draco encoder."""
410
+ vq: VQInterframeCodecConfig = field(default_factory=VQInterframeCodecConfig)
411
+ xyz: XYZQuantInterframeCodecConfig = field(default_factory=XYZQuantInterframeCodecConfig)
412
+ zstd_level: int = 7
413
+ draco_level: int = 0
414
+ qp: int = 30
415
+ qscale: int = 30
416
+ qrotation: int = 30
417
+ qopacity: int = 30
418
+ qfeaturedc: int = 30
419
+ qfeaturerest: int = 30
420
+ payload_device: Optional[str] = None
421
+
422
+
423
+ @dataclass
424
+ class VQXYZDracoDecoderConfig:
425
+ """Configuration for VQ + XYZ + Draco decoder."""
426
+ payload_device: Optional[str] = None
427
+ device: Optional[str] = None
428
+
429
+
430
+ def build_vqxyzdraco_encoder(config: VQXYZDracoEncoderConfig) -> InterframeEncoder:
431
+ """Build encoder from configuration."""
432
+ return VQXYZDracoEncoder(
433
+ vq_config=config.vq,
434
+ xyz_config=config.xyz,
435
+ zstd_level=config.zstd_level,
436
+ draco_level=config.draco_level,
437
+ qp=config.qp,
438
+ qscale=config.qscale,
439
+ qrotation=config.qrotation,
440
+ qopacity=config.qopacity,
441
+ qfeaturedc=config.qfeaturedc,
442
+ qfeaturerest=config.qfeaturerest,
443
+ payload_device=config.payload_device,
444
+ )
445
+
446
+
447
+ def build_vqxyzdraco_decoder(config: VQXYZDracoDecoderConfig) -> InterframeDecoder:
448
+ """Build decoder from configuration."""
449
+ return VQXYZDracoDecoder(
450
+ payload_device=config.payload_device,
451
+ device=config.device,
452
+ )
453
+
454
+
455
+ # Register
456
+ register_encoder(
457
+ "vqxyzdraco",
458
+ build_vqxyzdraco_encoder,
459
+ VQXYZDracoEncoderConfig,
460
+ "VQ + XYZ quantization + Draco compression",
461
+ )
462
+
463
+ register_decoder(
464
+ "vqxyzdraco",
465
+ build_vqxyzdraco_decoder,
466
+ VQXYZDracoDecoderConfig,
467
+ "VQ + XYZ quantization + Draco decompression",
468
+ )
@@ -0,0 +1,156 @@
1
+ """
2
+ VQ + XYZ quantization + Two-Pass Draco compression encoder/decoder combination.
3
+
4
+ This module uses two-pass Draco compression which accumulates all frames
5
+ and compresses them as a single block for better compression efficiency.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional
10
+
11
+ from ..draco.interface import DracoInterframeCodecInterface
12
+ from ..draco.twopass import TwoPassDracoSerializer, TwoPassDracoDeserializer
13
+ from ..interframe.combine import CombinedInterframeEncoderInitConfig
14
+ from ..interframe.encoder import InterframeEncoder
15
+ from ..interframe.decoder import InterframeDecoder
16
+ from ..vq.interface import VQInterframeCodecConfig
17
+ from ..xyz.interface import XYZQuantInterframeCodecConfig
18
+ from .registry import register_encoder, register_decoder
19
+ from .vq_xyz_1mask import VQXYZQuantMergeMaskInterframeCodecInterface
20
+ from .vq_xyz_draco import VQXYZDracoInterframeCodecTranscodingInterface
21
+
22
+
23
+ def VQXYZDraco2PassEncoder(
24
+ vq_config: VQInterframeCodecConfig,
25
+ xyz_config: XYZQuantInterframeCodecConfig,
26
+ zstd_level: int = 7,
27
+ draco_level: int = 0,
28
+ qp: int = 0,
29
+ qscale: int = 0,
30
+ qrotation: int = 0,
31
+ qopacity: int = 0,
32
+ qfeaturedc: int = 0,
33
+ qfeaturerest: int = 0,
34
+ payload_device: Optional[str] = None,
35
+ ) -> InterframeEncoder:
36
+ """Create an encoder combining VQ + XYZ quantization + Two-Pass Draco compression."""
37
+
38
+ # Use merged mask interface for better compression efficiency
39
+ combined_interface = VQXYZQuantMergeMaskInterframeCodecInterface()
40
+
41
+ # Create transcoding interface
42
+ transcoder = VQXYZDracoInterframeCodecTranscodingInterface()
43
+
44
+ # Wrap with Draco interface
45
+ draco_interface = DracoInterframeCodecInterface(combined_interface, transcoder)
46
+
47
+ combined_config = CombinedInterframeEncoderInitConfig(
48
+ init_configs=[xyz_config, vq_config]
49
+ )
50
+
51
+ serializer = TwoPassDracoSerializer(
52
+ zstd_level=zstd_level,
53
+ draco_level=draco_level,
54
+ qp=qp,
55
+ qscale=qscale,
56
+ qrotation=qrotation,
57
+ qopacity=qopacity,
58
+ qfeaturedc=qfeaturedc,
59
+ qfeaturerest=qfeaturerest,
60
+ )
61
+
62
+ return InterframeEncoder(
63
+ serializer=serializer,
64
+ interface=draco_interface,
65
+ init_config=combined_config,
66
+ payload_device=payload_device,
67
+ )
68
+
69
+
70
+ def VQXYZDraco2PassDecoder(
71
+ payload_device: Optional[str] = None,
72
+ device: Optional[str] = None,
73
+ ) -> InterframeDecoder:
74
+ """Create a decoder for VQ + XYZ quantization + Two-Pass Draco compressed data."""
75
+
76
+ # Use merged mask interface for better compression efficiency
77
+ combined_interface = VQXYZQuantMergeMaskInterframeCodecInterface()
78
+
79
+ # Create transcoding interface
80
+ transcoder = VQXYZDracoInterframeCodecTranscodingInterface()
81
+
82
+ # Wrap with Draco interface
83
+ draco_interface = DracoInterframeCodecInterface(combined_interface, transcoder)
84
+
85
+ deserializer = TwoPassDracoDeserializer()
86
+
87
+ return InterframeDecoder(
88
+ deserializer=deserializer,
89
+ interface=draco_interface,
90
+ payload_device=payload_device,
91
+ device=device,
92
+ )
93
+
94
+
95
+ @dataclass
96
+ class VQXYZDraco2PassEncoderConfig:
97
+ """Configuration for VQ + XYZ + Two-Pass Draco encoder."""
98
+ vq: VQInterframeCodecConfig = field(default_factory=VQInterframeCodecConfig)
99
+ xyz: XYZQuantInterframeCodecConfig = field(default_factory=XYZQuantInterframeCodecConfig)
100
+ zstd_level: int = 7
101
+ draco_level: int = 0
102
+ qp: int = 30
103
+ qscale: int = 30
104
+ qrotation: int = 30
105
+ qopacity: int = 30
106
+ qfeaturedc: int = 30
107
+ qfeaturerest: int = 30
108
+ payload_device: Optional[str] = None
109
+
110
+
111
+ @dataclass
112
+ class VQXYZDraco2PassDecoderConfig:
113
+ """Configuration for VQ + XYZ + Two-Pass Draco decoder."""
114
+ payload_device: Optional[str] = None
115
+ device: Optional[str] = None
116
+
117
+
118
+ def build_vqxyzdraco2pass_encoder(config: VQXYZDraco2PassEncoderConfig) -> InterframeEncoder:
119
+ """Build encoder from configuration."""
120
+ return VQXYZDraco2PassEncoder(
121
+ vq_config=config.vq,
122
+ xyz_config=config.xyz,
123
+ zstd_level=config.zstd_level,
124
+ draco_level=config.draco_level,
125
+ qp=config.qp,
126
+ qscale=config.qscale,
127
+ qrotation=config.qrotation,
128
+ qopacity=config.qopacity,
129
+ qfeaturedc=config.qfeaturedc,
130
+ qfeaturerest=config.qfeaturerest,
131
+ payload_device=config.payload_device,
132
+ )
133
+
134
+
135
+ def build_vqxyzdraco2pass_decoder(config: VQXYZDraco2PassDecoderConfig) -> InterframeDecoder:
136
+ """Build decoder from configuration."""
137
+ return VQXYZDraco2PassDecoder(
138
+ payload_device=config.payload_device,
139
+ device=config.device,
140
+ )
141
+
142
+
143
+ # Register
144
+ register_encoder(
145
+ "vqxyzdraco2pass",
146
+ build_vqxyzdraco2pass_encoder,
147
+ VQXYZDraco2PassEncoderConfig,
148
+ "VQ + XYZ quantization + Two-Pass Draco compression",
149
+ )
150
+
151
+ register_decoder(
152
+ "vqxyzdraco2pass",
153
+ build_vqxyzdraco2pass_decoder,
154
+ VQXYZDraco2PassDecoderConfig,
155
+ "VQ + XYZ quantization + Two-Pass Draco decompression",
156
+ )