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,339 @@
1
+ """
2
+ Draco-capable interframe codec interface.
3
+
4
+ This module provides interfaces for interframe codecs that can output
5
+ Draco-compatible payloads for efficient compression.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass
10
+ from typing import Optional, Self
11
+
12
+ import numpy as np
13
+
14
+ from gaussian_splatting import GaussianModel
15
+
16
+ from ..payload import Payload
17
+ from ..interframe.interface import (
18
+ InterframeCodecInterface,
19
+ InterframeCodecContext,
20
+ InterframeEncoderInitConfig,
21
+ )
22
+
23
+
24
+ @dataclass
25
+ class DracoPayload(Payload):
26
+ """
27
+ Payload containing data in Draco-compatible format.
28
+
29
+ This payload structure matches the format expected by Draco compression
30
+ for reduced 3DGS data. All arrays should be numpy arrays.
31
+
32
+ Attributes:
33
+ positions: Point positions, shape (N, 3), dtype float32/float64
34
+ scales: Scale indices or values, shape (N, 1), dtype int32 for VQ indices
35
+ rotations: Rotation indices or values, shape (N, 2), dtype int32 for VQ indices
36
+ (rotation_re, rotation_im)
37
+ opacities: Opacity indices or values, shape (N, 1), dtype int32 for VQ indices
38
+ features_dc: DC feature indices or values, shape (N, 1), dtype int32 for VQ indices
39
+ features_rest: Rest feature indices or values, shape (N, 9),
40
+ dtype int32 for VQ indices (3 sh_degrees * 3)
41
+ extra: Optional additional payload for codec-specific data not covered by Draco format
42
+ """
43
+ positions: np.ndarray # (N, 3) float
44
+ scales: np.ndarray # (N, 1) int32
45
+ rotations: np.ndarray # (N, 2) int32
46
+ opacities: np.ndarray # (N, 1) int32
47
+ features_dc: np.ndarray # (N, 1) int32
48
+ features_rest: np.ndarray # (N, 9) int32
49
+ extra: Optional[Payload] = None
50
+
51
+ def to(self, device) -> Self:
52
+ """
53
+ Move the Payload to the specified device.
54
+
55
+ Since DracoPayload uses numpy arrays (CPU-only), only the extra
56
+ payload (if present) is moved to the target device.
57
+
58
+ Args:
59
+ device: The target device (e.g., 'cpu', 'cuda', torch.device).
60
+
61
+ Returns:
62
+ A new DracoPayload with the extra payload moved to the target device.
63
+ """
64
+ return DracoPayload(
65
+ positions=self.positions,
66
+ scales=self.scales,
67
+ rotations=self.rotations,
68
+ opacities=self.opacities,
69
+ features_dc=self.features_dc,
70
+ features_rest=self.features_rest,
71
+ extra=self.extra.to(device) if self.extra is not None else None,
72
+ )
73
+
74
+
75
+ class DracoInterframeCodecTranscodingInterface(ABC):
76
+ """
77
+ Abstract interface for transcoding between internal payloads and DracoPayload.
78
+
79
+ This interface defines the conversion methods needed to transform codec-specific
80
+ payloads to/from DracoPayload format. Implementations of this interface handle
81
+ the data format conversion without implementing the actual encoding/decoding logic.
82
+ """
83
+
84
+ @abstractmethod
85
+ def interframe_payload_to_draco(self, payload: Payload) -> DracoPayload:
86
+ """
87
+ Convert an interframe payload to a DracoPayload.
88
+
89
+ Args:
90
+ payload: The internal payload from encode_interframe.
91
+
92
+ Returns:
93
+ A DracoPayload containing the data in Draco-compatible format.
94
+ """
95
+ pass
96
+
97
+ @abstractmethod
98
+ def keyframe_payload_to_draco(self, payload: Payload) -> DracoPayload:
99
+ """
100
+ Convert a keyframe payload to a DracoPayload.
101
+
102
+ Args:
103
+ payload: The internal payload from encode_keyframe.
104
+
105
+ Returns:
106
+ A DracoPayload containing the data in Draco-compatible format.
107
+ """
108
+ pass
109
+
110
+ @abstractmethod
111
+ def draco_to_interframe_payload(self, draco_payload: DracoPayload) -> Payload:
112
+ """
113
+ Convert a DracoPayload to an interframe payload.
114
+
115
+ Args:
116
+ draco_payload: The DracoPayload to convert.
117
+
118
+ Returns:
119
+ An internal payload suitable for decode_interframe.
120
+ """
121
+ pass
122
+
123
+ @abstractmethod
124
+ def draco_to_keyframe_payload(self, draco_payload: DracoPayload) -> Payload:
125
+ """
126
+ Convert a DracoPayload to a keyframe payload.
127
+
128
+ Args:
129
+ draco_payload: The DracoPayload to convert.
130
+
131
+ Returns:
132
+ An internal payload suitable for decode_keyframe.
133
+ """
134
+ pass
135
+
136
+
137
+ class DracoInterframeCodecInterface(InterframeCodecInterface):
138
+ """
139
+ Interframe codec that wraps another codec and transcodes to/from DracoPayload.
140
+
141
+ This class uses composition to combine an InterframeCodecInterface (for the
142
+ actual encoding/decoding logic) with a DracoInterframeCodecTranscodingInterface
143
+ (for payload format conversion).
144
+
145
+ The workflow is:
146
+ Encoding:
147
+ 1. Call inner codec's encode method to get internal payload
148
+ 2. Convert internal payload to DracoPayload via transcoding interface
149
+
150
+ Decoding:
151
+ 1. Convert DracoPayload to internal payload via transcoding interface
152
+ 2. Call inner codec's decode method with internal payload
153
+ """
154
+
155
+ def __init__(
156
+ self,
157
+ codec: InterframeCodecInterface,
158
+ transcoder: DracoInterframeCodecTranscodingInterface,
159
+ ):
160
+ """
161
+ Initialize the DracoInterframeCodecInterface.
162
+
163
+ Args:
164
+ codec: The inner codec that performs actual encoding/decoding.
165
+ transcoder: The transcoding interface for payload conversion.
166
+ """
167
+ self._codec = codec
168
+ self._transcoder = transcoder
169
+
170
+ # =========================================================================
171
+ # Encode/decode methods - transcode then delegate
172
+ # =========================================================================
173
+
174
+ def decode_interframe(
175
+ self, payload: DracoPayload, prev_context: InterframeCodecContext
176
+ ) -> InterframeCodecContext:
177
+ """
178
+ Decode a DracoPayload to reconstruct the next frame's context.
179
+
180
+ This method first converts the DracoPayload to the internal format,
181
+ then delegates to the inner codec's decode_interframe.
182
+
183
+ Args:
184
+ payload: The DracoPayload containing the delta data.
185
+ prev_context: The context of the previous frame.
186
+
187
+ Returns:
188
+ The reconstructed context for the current frame.
189
+ """
190
+ internal_payload = self._transcoder.draco_to_interframe_payload(payload)
191
+ return self._codec.decode_interframe(internal_payload, prev_context)
192
+
193
+ def encode_interframe(
194
+ self,
195
+ prev_context: InterframeCodecContext,
196
+ next_context: InterframeCodecContext,
197
+ ) -> DracoPayload:
198
+ """
199
+ Encode the difference between two consecutive frames as a DracoPayload.
200
+
201
+ This method delegates to the inner codec's encode_interframe,
202
+ then converts the result to a DracoPayload.
203
+
204
+ Args:
205
+ prev_context: The context of the previous frame.
206
+ next_context: The context of the next frame.
207
+
208
+ Returns:
209
+ A DracoPayload containing the delta information.
210
+ """
211
+ internal_payload = self._codec.encode_interframe(prev_context, next_context)
212
+ return self._transcoder.interframe_payload_to_draco(internal_payload)
213
+
214
+ def decode_keyframe(self, payload: DracoPayload) -> InterframeCodecContext:
215
+ """
216
+ Decode a DracoPayload keyframe to create initial context.
217
+
218
+ This method first converts the DracoPayload to the internal format,
219
+ then delegates to the inner codec's decode_keyframe.
220
+
221
+ Args:
222
+ payload: The DracoPayload containing full keyframe data.
223
+
224
+ Returns:
225
+ The context for the first/key frame.
226
+ """
227
+ internal_payload = self._transcoder.draco_to_keyframe_payload(payload)
228
+ return self._codec.decode_keyframe(internal_payload)
229
+
230
+ def decode_keyframe_for_encode(
231
+ self, payload: DracoPayload, context: InterframeCodecContext
232
+ ) -> InterframeCodecContext:
233
+ """
234
+ Decode a DracoPayload keyframe during encoding to avoid error accumulation.
235
+
236
+ This method first converts the DracoPayload to the internal format,
237
+ then delegates to the inner codec's decode_keyframe_for_encode.
238
+
239
+ Args:
240
+ payload: The DracoPayload that was just encoded.
241
+ context: The original context used for encoding this keyframe.
242
+
243
+ Returns:
244
+ The reconstructed context as the decoder would produce it.
245
+ """
246
+ internal_payload = self._transcoder.draco_to_keyframe_payload(payload)
247
+ return self._codec.decode_keyframe_for_encode(internal_payload, context)
248
+
249
+ def decode_interframe_for_encode(
250
+ self, payload: DracoPayload, prev_context: InterframeCodecContext
251
+ ) -> InterframeCodecContext:
252
+ """
253
+ Decode a DracoPayload interframe during encoding to avoid error accumulation.
254
+
255
+ This method first converts the DracoPayload to the internal format,
256
+ then delegates to the inner codec's decode_interframe_for_encode.
257
+
258
+ Args:
259
+ payload: The DracoPayload that was just encoded.
260
+ prev_context: The previous frame's context (reconstructed version).
261
+
262
+ Returns:
263
+ The reconstructed context as the decoder would produce it.
264
+ """
265
+ internal_payload = self._transcoder.draco_to_interframe_payload(payload)
266
+ return self._codec.decode_interframe_for_encode(internal_payload, prev_context)
267
+
268
+ def encode_keyframe(self, context: InterframeCodecContext) -> DracoPayload:
269
+ """
270
+ Encode the first frame as a DracoPayload keyframe.
271
+
272
+ This method delegates to the inner codec's encode_keyframe,
273
+ then converts the result to a DracoPayload.
274
+
275
+ Args:
276
+ context: The context of the first frame.
277
+
278
+ Returns:
279
+ A DracoPayload containing the full keyframe data.
280
+ """
281
+ internal_payload = self._codec.encode_keyframe(context)
282
+ return self._transcoder.keyframe_payload_to_draco(internal_payload)
283
+
284
+ # =========================================================================
285
+ # Other methods - delegate directly to inner codec
286
+ # =========================================================================
287
+
288
+ def keyframe_to_context(
289
+ self, frame: GaussianModel, init_config: InterframeEncoderInitConfig
290
+ ) -> InterframeCodecContext:
291
+ """
292
+ Convert a keyframe to a Context.
293
+
294
+ Delegates directly to the inner codec.
295
+
296
+ Args:
297
+ frame: The GaussianModel frame to convert.
298
+ init_config: Encoder initialization configuration.
299
+
300
+ Returns:
301
+ The corresponding Context representation.
302
+ """
303
+ return self._codec.keyframe_to_context(frame, init_config)
304
+
305
+ def interframe_to_context(
306
+ self,
307
+ frame: GaussianModel,
308
+ prev_context: InterframeCodecContext,
309
+ ) -> InterframeCodecContext:
310
+ """
311
+ Convert a frame to a Context using the previous context as reference.
312
+
313
+ Delegates directly to the inner codec.
314
+
315
+ Args:
316
+ frame: The GaussianModel frame to convert.
317
+ prev_context: The context from the previous frame.
318
+
319
+ Returns:
320
+ The corresponding Context representation.
321
+ """
322
+ return self._codec.interframe_to_context(frame, prev_context)
323
+
324
+ def context_to_frame(
325
+ self, context: InterframeCodecContext, frame: GaussianModel
326
+ ) -> GaussianModel:
327
+ """
328
+ Convert a Context back to a frame.
329
+
330
+ Delegates directly to the inner codec.
331
+
332
+ Args:
333
+ context: The Context to convert.
334
+ frame: An empty GaussianModel or one from previous pipeline steps.
335
+
336
+ Returns:
337
+ The modified GaussianModel with the frame data.
338
+ """
339
+ return self._codec.context_to_frame(context, frame)
@@ -0,0 +1,235 @@
1
+ """
2
+ Draco-based streaming serialization and deserialization.
3
+
4
+ Uses dracoreduced3dgs encoding + cloudpickle for extra data + length-prefix
5
+ framing + zstd compression for efficient streaming of DracoPayload objects.
6
+
7
+ WARNING: cloudpickle uses pickle under the hood. Do NOT use with untrusted
8
+ data sources. Only use with trusted data (local files, same-process, etc.).
9
+ """
10
+
11
+ import struct
12
+ from typing import Iterator
13
+
14
+ import cloudpickle as pickle
15
+ import zstandard as zstd
16
+
17
+ from ..deserializer import AbstractDeserializer
18
+ from ..serializer import AbstractSerializer
19
+ from .interface import DracoPayload
20
+
21
+ from . import dracoreduced3dgs
22
+
23
+ # 4-byte big-endian length prefix (max 4GB per object)
24
+ _LEN = struct.Struct(">I")
25
+
26
+
27
+ class DracoSerializer(AbstractSerializer):
28
+ """
29
+ Streaming serializer using Draco encoding + cloudpickle + length-prefix framing + zstd.
30
+
31
+ Each DracoPayload is encoded as follows:
32
+ 1. The main point cloud data is encoded using dracoreduced3dgs.encode
33
+ 2. The extra field (if present) is pickled with cloudpickle
34
+ 3. Both are combined with length-prefix framing:
35
+ [draco_len][draco_bytes][extra_len][extra_bytes]
36
+ (extra_len is 0 if no extra data)
37
+ 4. The combined data is compressed incrementally using zstd streaming compression.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ zstd_level: int = 7,
43
+ draco_level: int = 0,
44
+ qp: int = 30,
45
+ qscale: int = 30,
46
+ qrotation: int = 30,
47
+ qopacity: int = 30,
48
+ qfeaturedc: int = 30,
49
+ qfeaturerest: int = 30,
50
+ ):
51
+ """
52
+ Initialize the serializer.
53
+
54
+ Args:
55
+ zstd_level: Zstd compression level (1-22). Default is 7.
56
+ draco_level: Draco compression level (0-10). Default is 0.
57
+ qp: Quantization bits for positions (0 to disable).
58
+ qscale: Quantization bits for scales (0 to disable).
59
+ qrotation: Quantization bits for rotations (0 to disable).
60
+ qopacity: Quantization bits for opacities (0 to disable).
61
+ qfeaturedc: Quantization bits for features_dc (0 to disable).
62
+ qfeaturerest: Quantization bits for features_rest (0 to disable).
63
+ """
64
+ self._compressor = zstd.ZstdCompressor(level=zstd_level).compressobj()
65
+ self._draco_level = draco_level
66
+ self._qp = qp
67
+ self._qscale = qscale
68
+ self._qrotation = qrotation
69
+ self._qopacity = qopacity
70
+ self._qfeaturedc = qfeaturedc
71
+ self._qfeaturerest = qfeaturerest
72
+
73
+ def serialize_frame(self, payload: DracoPayload) -> Iterator[bytes]:
74
+ """
75
+ Serialize and compress a DracoPayload object.
76
+
77
+ Args:
78
+ payload: A DracoPayload instance to serialize.
79
+
80
+ Yields:
81
+ Compressed byte chunks (may yield zero chunks if buffered).
82
+ """
83
+ # Encode point cloud data using Draco
84
+ draco_encoded = dracoreduced3dgs.encode(
85
+ payload.positions,
86
+ payload.scales,
87
+ payload.rotations,
88
+ payload.opacities,
89
+ payload.features_dc,
90
+ payload.features_rest,
91
+ self._draco_level,
92
+ self._qp,
93
+ self._qscale,
94
+ self._qrotation,
95
+ self._qopacity,
96
+ self._qfeaturedc,
97
+ self._qfeaturerest,
98
+ )
99
+
100
+ # Pickle the extra field if present
101
+ if payload.extra is not None:
102
+ extra_pickled = pickle.dumps(payload.extra, protocol=pickle.DEFAULT_PROTOCOL)
103
+ else:
104
+ extra_pickled = b""
105
+
106
+ # Combine with length-prefix framing:
107
+ # [draco_len][draco_bytes][extra_len][extra_bytes]
108
+ framed = (
109
+ _LEN.pack(len(draco_encoded))
110
+ + draco_encoded
111
+ + _LEN.pack(len(extra_pickled))
112
+ + extra_pickled
113
+ )
114
+
115
+ # Compress incrementally
116
+ out = self._compressor.compress(framed)
117
+ if out:
118
+ yield out
119
+
120
+ def flush(self) -> Iterator[bytes]:
121
+ """
122
+ Flush remaining compressed data.
123
+
124
+ Yields:
125
+ Final compressed byte chunks.
126
+ """
127
+ tail = self._compressor.flush()
128
+ if tail:
129
+ yield tail
130
+
131
+
132
+ class DracoDeserializer(AbstractDeserializer):
133
+ """
134
+ Streaming deserializer using zstd + length-prefix framing + Draco decoding + cloudpickle.
135
+
136
+ Decompresses incoming bytes incrementally, buffers until a complete
137
+ length-prefixed frame is available, then decodes and yields DracoPayloads.
138
+ """
139
+
140
+ def __init__(self):
141
+ """Initialize the deserializer."""
142
+ self._decompressor = zstd.ZstdDecompressor().decompressobj()
143
+ self._buffer = bytearray()
144
+
145
+ def deserialize_frame(self, data: bytes) -> Iterator[DracoPayload]:
146
+ """
147
+ Decompress and deserialize bytes to DracoPayload objects.
148
+
149
+ Args:
150
+ data: Compressed bytes to deserialize.
151
+
152
+ Yields:
153
+ Complete DracoPayload objects as they become available.
154
+ """
155
+ # Decompress and add to buffer
156
+ decompressed = self._decompressor.decompress(data)
157
+ if decompressed:
158
+ self._buffer.extend(decompressed)
159
+
160
+ # Yield complete frames
161
+ yield from self._extract_payloads()
162
+
163
+ def flush(self) -> Iterator[DracoPayload]:
164
+ """
165
+ Flush any remaining buffered data.
166
+
167
+ Yields:
168
+ Any remaining DracoPayload objects in the buffer.
169
+ """
170
+ # Try to extract any remaining complete payloads
171
+ yield from self._extract_payloads()
172
+
173
+ # If there's leftover data, it's incomplete/corrupted
174
+ if self._buffer:
175
+ raise ValueError(
176
+ f"Incomplete data in buffer: {len(self._buffer)} bytes remaining"
177
+ )
178
+
179
+ def _extract_payloads(self) -> Iterator[DracoPayload]:
180
+ """
181
+ Extract complete payloads from the buffer.
182
+
183
+ Yields:
184
+ Complete DracoPayload objects.
185
+ """
186
+ while True:
187
+ # Need at least draco length prefix
188
+ if len(self._buffer) < _LEN.size:
189
+ break
190
+
191
+ # Read draco length
192
+ (draco_len,) = _LEN.unpack(self._buffer[: _LEN.size])
193
+
194
+ # Check if we have complete draco data + extra length prefix
195
+ min_frame_size = _LEN.size + draco_len + _LEN.size
196
+ if len(self._buffer) < min_frame_size:
197
+ break
198
+
199
+ # Read extra length
200
+ extra_len_offset = _LEN.size + draco_len
201
+ (extra_len,) = _LEN.unpack(
202
+ self._buffer[extra_len_offset: extra_len_offset + _LEN.size]
203
+ )
204
+
205
+ # Check if we have complete frame
206
+ total_frame_size = min_frame_size + extra_len
207
+ if len(self._buffer) < total_frame_size:
208
+ break
209
+
210
+ # Extract draco bytes
211
+ draco_bytes = bytes(self._buffer[_LEN.size: _LEN.size + draco_len])
212
+
213
+ # Extract extra bytes
214
+ extra_offset = extra_len_offset + _LEN.size
215
+ extra_bytes = bytes(self._buffer[extra_offset: extra_offset + extra_len])
216
+
217
+ # Remove consumed data from buffer
218
+ del self._buffer[:total_frame_size]
219
+
220
+ # Decode draco data
221
+ pc = dracoreduced3dgs.decode(draco_bytes)
222
+
223
+ # Unpickle extra if present
224
+ extra = pickle.loads(extra_bytes) if extra_bytes else None
225
+
226
+ # Construct and yield DracoPayload
227
+ yield DracoPayload(
228
+ positions=pc.positions,
229
+ scales=pc.scales,
230
+ rotations=pc.rotations,
231
+ opacities=pc.opacities,
232
+ features_dc=pc.features_dc,
233
+ features_rest=pc.features_rest,
234
+ extra=extra,
235
+ )