gsvvcompressor 1.2.0__cp310-cp310-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.
- gsvvcompressor/__init__.py +13 -0
- gsvvcompressor/__main__.py +243 -0
- gsvvcompressor/combinations/__init__.py +84 -0
- gsvvcompressor/combinations/registry.py +52 -0
- gsvvcompressor/combinations/vq_xyz_1mask.py +89 -0
- gsvvcompressor/combinations/vq_xyz_1mask_zstd.py +103 -0
- gsvvcompressor/combinations/vq_xyz_draco.py +468 -0
- gsvvcompressor/combinations/vq_xyz_draco_2pass.py +156 -0
- gsvvcompressor/combinations/vq_xyz_zstd.py +106 -0
- gsvvcompressor/compress/__init__.py +5 -0
- gsvvcompressor/compress/zstd.py +144 -0
- gsvvcompressor/decoder.py +155 -0
- gsvvcompressor/deserializer.py +42 -0
- gsvvcompressor/draco/__init__.py +34 -0
- gsvvcompressor/draco/draco_decoder.exe +0 -0
- gsvvcompressor/draco/draco_encoder.exe +0 -0
- gsvvcompressor/draco/dracoreduced3dgs.cp310-win_amd64.pyd +0 -0
- gsvvcompressor/draco/interface.py +339 -0
- gsvvcompressor/draco/serialize.py +235 -0
- gsvvcompressor/draco/twopass.py +359 -0
- gsvvcompressor/encoder.py +122 -0
- gsvvcompressor/interframe/__init__.py +11 -0
- gsvvcompressor/interframe/combine.py +271 -0
- gsvvcompressor/interframe/decoder.py +99 -0
- gsvvcompressor/interframe/encoder.py +92 -0
- gsvvcompressor/interframe/interface.py +221 -0
- gsvvcompressor/interframe/twopass.py +226 -0
- gsvvcompressor/io/__init__.py +31 -0
- gsvvcompressor/io/bytes.py +103 -0
- gsvvcompressor/io/config.py +78 -0
- gsvvcompressor/io/gaussian_model.py +127 -0
- gsvvcompressor/movecameras.py +33 -0
- gsvvcompressor/payload.py +34 -0
- gsvvcompressor/serializer.py +42 -0
- gsvvcompressor/vq/__init__.py +15 -0
- gsvvcompressor/vq/interface.py +324 -0
- gsvvcompressor/vq/singlemask.py +127 -0
- gsvvcompressor/vq/twopass.py +1 -0
- gsvvcompressor/xyz/__init__.py +26 -0
- gsvvcompressor/xyz/dense.py +39 -0
- gsvvcompressor/xyz/interface.py +382 -0
- gsvvcompressor/xyz/knn.py +141 -0
- gsvvcompressor/xyz/quant.py +143 -0
- gsvvcompressor/xyz/size.py +44 -0
- gsvvcompressor/xyz/twopass.py +1 -0
- gsvvcompressor-1.2.0.dist-info/METADATA +690 -0
- gsvvcompressor-1.2.0.dist-info/RECORD +50 -0
- gsvvcompressor-1.2.0.dist-info/WHEEL +5 -0
- gsvvcompressor-1.2.0.dist-info/licenses/LICENSE +21 -0
- gsvvcompressor-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Iterator, List, Optional
|
|
4
|
+
|
|
5
|
+
from gaussian_splatting import GaussianModel
|
|
6
|
+
|
|
7
|
+
from ..encoder import AbstractEncoder
|
|
8
|
+
from ..payload import Payload
|
|
9
|
+
from ..serializer import AbstractSerializer
|
|
10
|
+
from .interface import (
|
|
11
|
+
InterframeCodecContext,
|
|
12
|
+
InterframeCodecInterface,
|
|
13
|
+
InterframeEncoderInitConfig,
|
|
14
|
+
)
|
|
15
|
+
from .encoder import InterframeEncoder
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class PassOneContext:
|
|
20
|
+
"""
|
|
21
|
+
Context data from the first pass of two-pass encoding.
|
|
22
|
+
|
|
23
|
+
This dataclass holds the information gathered during the first pass
|
|
24
|
+
over all frames, which is then used to optimize the encoding in the
|
|
25
|
+
second pass. For example, this could contain statistics about the
|
|
26
|
+
entire video sequence, codebook data, or other global information.
|
|
27
|
+
|
|
28
|
+
Subclasses should define specific fields for their encoding scheme.
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TwoPassInterframeCodecInterface(InterframeCodecInterface):
|
|
34
|
+
"""
|
|
35
|
+
Abstract interface for two-pass inter-frame encoding/decoding algorithms.
|
|
36
|
+
|
|
37
|
+
This interface extends InterframeCodecInterface to support two-pass encoding,
|
|
38
|
+
where the first pass gathers information about all frames, and the second
|
|
39
|
+
pass uses this information to optimize encoding.
|
|
40
|
+
|
|
41
|
+
Design Guidelines:
|
|
42
|
+
- PassOneContext: Store information gathered during the first pass
|
|
43
|
+
(e.g., global statistics, codebook data, quality parameters).
|
|
44
|
+
- The keyframe_to_context method receives PassOneContext instead of
|
|
45
|
+
InterframeEncoderInitConfig, allowing it to use first-pass data.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def keyframe_to_context_pass_one(
|
|
50
|
+
self,
|
|
51
|
+
frame: GaussianModel,
|
|
52
|
+
init_config: InterframeEncoderInitConfig,
|
|
53
|
+
) -> PassOneContext:
|
|
54
|
+
"""
|
|
55
|
+
Process the keyframe during the first pass to initialize PassOneContext.
|
|
56
|
+
|
|
57
|
+
This method is called by the encoder when processing the first frame
|
|
58
|
+
during the first pass. It initializes the PassOneContext with information
|
|
59
|
+
from the keyframe.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
frame: The GaussianModel keyframe to process.
|
|
63
|
+
init_config: Encoder initialization configuration.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
PassOneContext initialized from the keyframe.
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def interframe_to_context_pass_one(
|
|
72
|
+
self,
|
|
73
|
+
frame: GaussianModel,
|
|
74
|
+
prev_pass_one_context: PassOneContext,
|
|
75
|
+
) -> PassOneContext:
|
|
76
|
+
"""
|
|
77
|
+
Process a frame during the first pass to update PassOneContext.
|
|
78
|
+
|
|
79
|
+
This method is called by the encoder when processing subsequent frames
|
|
80
|
+
during the first pass. It updates the PassOneContext with information
|
|
81
|
+
gathered from the current frame.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
frame: The GaussianModel frame to process.
|
|
85
|
+
prev_pass_one_context: The PassOneContext from processing previous frames.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Updated PassOneContext incorporating information from this frame.
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def keyframe_to_context(
|
|
94
|
+
self,
|
|
95
|
+
frame: GaussianModel,
|
|
96
|
+
pass_one_context: PassOneContext,
|
|
97
|
+
) -> InterframeCodecContext:
|
|
98
|
+
"""
|
|
99
|
+
Convert a keyframe to a Context using first-pass information.
|
|
100
|
+
|
|
101
|
+
This method is called by the encoder when processing the first frame
|
|
102
|
+
in the second pass. The pass_one_context provides information gathered
|
|
103
|
+
during the first pass for optimized encoding.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
frame: The GaussianModel frame to convert.
|
|
107
|
+
pass_one_context: Context from the first pass containing
|
|
108
|
+
global encoding information.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The corresponding Context representation.
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TwoPassInterframeEncoder(InterframeEncoder):
|
|
117
|
+
"""
|
|
118
|
+
Encoder that uses two-pass inter-frame compression.
|
|
119
|
+
|
|
120
|
+
This encoder collects frames during pack() calls (pass one), then
|
|
121
|
+
performs the actual encoding in flush_pack() (pass two).
|
|
122
|
+
|
|
123
|
+
Pass one: Gather information from all frames to build PassOneContext.
|
|
124
|
+
Pass two: Encode all frames using the gathered information.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
serializer: AbstractSerializer,
|
|
130
|
+
interface: TwoPassInterframeCodecInterface,
|
|
131
|
+
init_config: InterframeEncoderInitConfig,
|
|
132
|
+
payload_device=None,
|
|
133
|
+
):
|
|
134
|
+
"""
|
|
135
|
+
Initialize the two-pass inter-frame encoder.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
serializer: The serializer to use for converting Payload to bytes.
|
|
139
|
+
interface: The TwoPassInterframeCodecInterface instance that
|
|
140
|
+
provides encoding methods.
|
|
141
|
+
init_config: Configuration parameters for encoder initialization.
|
|
142
|
+
"""
|
|
143
|
+
super().__init__(serializer=serializer, interface=interface, init_config=init_config, payload_device=payload_device)
|
|
144
|
+
self._interface = interface
|
|
145
|
+
self._frames: List[GaussianModel] = []
|
|
146
|
+
self._pass_one_context: Optional[PassOneContext] = None
|
|
147
|
+
|
|
148
|
+
def pack(self, frame: GaussianModel) -> Iterator[Payload]:
|
|
149
|
+
"""
|
|
150
|
+
Perform pass one on the frame and store it for pass two.
|
|
151
|
+
|
|
152
|
+
During pass one, this method processes each frame to gather encoding
|
|
153
|
+
information and stores the frame for later encoding in flush_pack().
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
frame: A GaussianModel instance to process.
|
|
157
|
+
|
|
158
|
+
Yields:
|
|
159
|
+
No payloads during pass one (empty iterator).
|
|
160
|
+
"""
|
|
161
|
+
# Pass one: gather information
|
|
162
|
+
if self._pass_one_context is None:
|
|
163
|
+
# First frame: keyframe
|
|
164
|
+
self._pass_one_context = self._interface.keyframe_to_context_pass_one(
|
|
165
|
+
frame, self._init_config
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
# Subsequent frames: interframe
|
|
169
|
+
self._pass_one_context = self._interface.interframe_to_context_pass_one(
|
|
170
|
+
frame, self._pass_one_context
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Store frame for pass two
|
|
174
|
+
self._frames.append(frame)
|
|
175
|
+
|
|
176
|
+
# No payloads during pass one
|
|
177
|
+
return
|
|
178
|
+
yield # Make this a generator
|
|
179
|
+
|
|
180
|
+
def flush_pack(self) -> Iterator[Payload]:
|
|
181
|
+
"""
|
|
182
|
+
Perform pass two: encode all stored frames using pass one information.
|
|
183
|
+
|
|
184
|
+
This method encodes all frames that were collected during pack() calls,
|
|
185
|
+
using the PassOneContext gathered during pass one.
|
|
186
|
+
|
|
187
|
+
Yields:
|
|
188
|
+
Packed Payload instances for all frames.
|
|
189
|
+
"""
|
|
190
|
+
if not self._frames or self._pass_one_context is None:
|
|
191
|
+
return
|
|
192
|
+
yield # Make this a generator
|
|
193
|
+
|
|
194
|
+
prev_context: Optional[InterframeCodecContext] = None
|
|
195
|
+
|
|
196
|
+
for frame in self._frames:
|
|
197
|
+
if prev_context is None:
|
|
198
|
+
# First frame: convert and encode as keyframe using pass_one_context
|
|
199
|
+
current_context = self._interface.keyframe_to_context(
|
|
200
|
+
frame, self._pass_one_context
|
|
201
|
+
)
|
|
202
|
+
payload = self._interface.encode_keyframe(current_context)
|
|
203
|
+
# Decode back to get reconstructed context (avoid error accumulation)
|
|
204
|
+
reconstructed_context = self._interface.decode_keyframe_for_encode(
|
|
205
|
+
payload, current_context
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
# Subsequent frames: convert and encode as delta from previous
|
|
209
|
+
current_context = self._interface.interframe_to_context(
|
|
210
|
+
frame, prev_context
|
|
211
|
+
)
|
|
212
|
+
payload = self._interface.encode_interframe(
|
|
213
|
+
prev_context, current_context
|
|
214
|
+
)
|
|
215
|
+
# Decode back to get reconstructed context (avoid error accumulation)
|
|
216
|
+
reconstructed_context = self._interface.decode_interframe_for_encode(
|
|
217
|
+
payload, prev_context
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Use reconstructed context as previous for next frame
|
|
221
|
+
prev_context = reconstructed_context
|
|
222
|
+
|
|
223
|
+
yield payload
|
|
224
|
+
|
|
225
|
+
# Clear stored frames after encoding
|
|
226
|
+
self._frames.clear()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IO module for reading and writing GaussianModel frames and bytes data.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .gaussian_model import FrameReader, FrameWriter
|
|
6
|
+
from .bytes import BytesReader, BytesWriter
|
|
7
|
+
from .config import (
|
|
8
|
+
FrameReaderConfig,
|
|
9
|
+
FrameWriterConfig,
|
|
10
|
+
build_frame_reader,
|
|
11
|
+
build_frame_writer,
|
|
12
|
+
BytesReaderConfig,
|
|
13
|
+
BytesWriterConfig,
|
|
14
|
+
build_bytes_reader,
|
|
15
|
+
build_bytes_writer,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"FrameReader",
|
|
20
|
+
"FrameWriter",
|
|
21
|
+
"FrameReaderConfig",
|
|
22
|
+
"FrameWriterConfig",
|
|
23
|
+
"build_frame_reader",
|
|
24
|
+
"build_frame_writer",
|
|
25
|
+
"BytesReader",
|
|
26
|
+
"BytesWriter",
|
|
27
|
+
"BytesReaderConfig",
|
|
28
|
+
"BytesWriterConfig",
|
|
29
|
+
"build_bytes_reader",
|
|
30
|
+
"build_bytes_writer",
|
|
31
|
+
]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bytes data reader and writer classes.
|
|
3
|
+
|
|
4
|
+
This module provides classes for reading and writing bytes data from/to files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BytesReader:
|
|
12
|
+
"""
|
|
13
|
+
Reader for bytes data from a file.
|
|
14
|
+
|
|
15
|
+
This class reads bytes data from a file and yields it as an iterator.
|
|
16
|
+
Each yielded item is a bytes chunk from the file.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
reader = BytesReader("data/compressed.bin")
|
|
20
|
+
for chunk in reader.read():
|
|
21
|
+
process(chunk)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Default chunk size for reading (64KB)
|
|
25
|
+
DEFAULT_CHUNK_SIZE = 64 * 1024
|
|
26
|
+
|
|
27
|
+
def __init__(self, path: str, chunk_size: int = DEFAULT_CHUNK_SIZE):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the bytes reader.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
path: Path to the file to read.
|
|
33
|
+
chunk_size: Size of each chunk to yield in bytes.
|
|
34
|
+
Default is 64KB.
|
|
35
|
+
"""
|
|
36
|
+
self._path = path
|
|
37
|
+
self._chunk_size = chunk_size
|
|
38
|
+
|
|
39
|
+
def read(self) -> Iterator[bytes]:
|
|
40
|
+
"""
|
|
41
|
+
Read bytes data from the file.
|
|
42
|
+
|
|
43
|
+
Yields:
|
|
44
|
+
bytes chunks from the file.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
FileNotFoundError: If the file does not exist.
|
|
48
|
+
"""
|
|
49
|
+
if not os.path.exists(self._path):
|
|
50
|
+
raise FileNotFoundError(f"File not found: {self._path}")
|
|
51
|
+
|
|
52
|
+
with open(self._path, "rb") as f:
|
|
53
|
+
while True:
|
|
54
|
+
chunk = f.read(self._chunk_size)
|
|
55
|
+
if not chunk:
|
|
56
|
+
break
|
|
57
|
+
yield chunk
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BytesWriter:
|
|
61
|
+
"""
|
|
62
|
+
Writer for bytes data to a file.
|
|
63
|
+
|
|
64
|
+
This class writes bytes data from an iterator to a file.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
writer = BytesWriter("output/compressed.bin")
|
|
68
|
+
writer.write(bytes_iterator)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, path: str, overwrite: bool = False):
|
|
72
|
+
"""
|
|
73
|
+
Initialize the bytes writer.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
path: Path to the file to write.
|
|
77
|
+
overwrite: If True, overwrite existing file. Default is False.
|
|
78
|
+
"""
|
|
79
|
+
self._path = path
|
|
80
|
+
self._overwrite = overwrite
|
|
81
|
+
|
|
82
|
+
def write(self, data: Iterator[bytes]) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Write bytes data to the file.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
data: An iterator yielding bytes chunks to write.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
FileExistsError: If the target file already exists and overwrite is False.
|
|
91
|
+
"""
|
|
92
|
+
# Check if file exists before writing
|
|
93
|
+
if not self._overwrite and os.path.exists(self._path):
|
|
94
|
+
raise FileExistsError(f"File already exists: {self._path}")
|
|
95
|
+
|
|
96
|
+
# Create parent directory if needed
|
|
97
|
+
parent_dir = os.path.dirname(self._path)
|
|
98
|
+
if parent_dir and not os.path.exists(parent_dir):
|
|
99
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
100
|
+
|
|
101
|
+
with open(self._path, "wb") as f:
|
|
102
|
+
for chunk in data:
|
|
103
|
+
f.write(chunk)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from omegaconf import MISSING
|
|
5
|
+
from .gaussian_model import FrameReader, FrameWriter
|
|
6
|
+
from .bytes import BytesReader, BytesWriter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class FrameReaderConfig:
|
|
11
|
+
"""Configuration for reading GaussianModel frame sequences."""
|
|
12
|
+
first_frame_path: str = MISSING
|
|
13
|
+
subsequent_format: str = MISSING
|
|
14
|
+
start_index: int = 2
|
|
15
|
+
sh_degree: int = 3
|
|
16
|
+
max_frames: Optional[int] = None
|
|
17
|
+
device: Optional[str] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class FrameWriterConfig:
|
|
22
|
+
"""Configuration for writing GaussianModel frame sequences."""
|
|
23
|
+
first_frame_path: str = MISSING
|
|
24
|
+
subsequent_format: str = MISSING
|
|
25
|
+
start_index: int = 2
|
|
26
|
+
overwrite: bool = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_frame_reader(config: FrameReaderConfig) -> FrameReader:
|
|
30
|
+
"""Build a FrameReader from configuration."""
|
|
31
|
+
return FrameReader(
|
|
32
|
+
first_frame_path=config.first_frame_path,
|
|
33
|
+
subsequent_format=config.subsequent_format,
|
|
34
|
+
start_index=config.start_index,
|
|
35
|
+
sh_degree=config.sh_degree,
|
|
36
|
+
max_frames=config.max_frames,
|
|
37
|
+
device=config.device,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_frame_writer(config: FrameWriterConfig) -> FrameWriter:
|
|
42
|
+
"""Build a FrameWriter from configuration."""
|
|
43
|
+
return FrameWriter(
|
|
44
|
+
first_frame_path=config.first_frame_path,
|
|
45
|
+
subsequent_format=config.subsequent_format,
|
|
46
|
+
start_index=config.start_index,
|
|
47
|
+
overwrite=config.overwrite,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class BytesReaderConfig:
|
|
53
|
+
"""Configuration for reading bytes data from a file."""
|
|
54
|
+
path: str = MISSING
|
|
55
|
+
chunk_size: int = BytesReader.DEFAULT_CHUNK_SIZE
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class BytesWriterConfig:
|
|
60
|
+
"""Configuration for writing bytes data to a file."""
|
|
61
|
+
path: str = MISSING
|
|
62
|
+
overwrite: bool = False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build_bytes_reader(config: BytesReaderConfig) -> BytesReader:
|
|
66
|
+
"""Build a BytesReader from configuration."""
|
|
67
|
+
return BytesReader(
|
|
68
|
+
path=config.path,
|
|
69
|
+
chunk_size=config.chunk_size,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_bytes_writer(config: BytesWriterConfig) -> BytesWriter:
|
|
74
|
+
"""Build a BytesWriter from configuration."""
|
|
75
|
+
return BytesWriter(
|
|
76
|
+
path=config.path,
|
|
77
|
+
overwrite=config.overwrite,
|
|
78
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Frame reader and writer classes for GaussianModel sequences.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Iterator
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
from gaussian_splatting import GaussianModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FrameReader:
|
|
13
|
+
"""
|
|
14
|
+
Reader for GaussianModel frames from a series of files.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
reader = FrameReader(
|
|
18
|
+
first_frame_path="data/frame_0000.ply",
|
|
19
|
+
subsequent_format="data/frame_{:04d}.ply",
|
|
20
|
+
start_index=1,
|
|
21
|
+
device="cuda"
|
|
22
|
+
)
|
|
23
|
+
for frame in reader.read():
|
|
24
|
+
process(frame)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
first_frame_path: str,
|
|
30
|
+
subsequent_format: str,
|
|
31
|
+
start_index: int,
|
|
32
|
+
sh_degree: int = 3,
|
|
33
|
+
max_frames: int | None = None,
|
|
34
|
+
device: str | torch.device | None = None,
|
|
35
|
+
):
|
|
36
|
+
self._first_frame_path = first_frame_path
|
|
37
|
+
self._subsequent_format = subsequent_format
|
|
38
|
+
self._start_index = start_index
|
|
39
|
+
self._sh_degree = sh_degree
|
|
40
|
+
self._max_frames = max_frames
|
|
41
|
+
self._device = device
|
|
42
|
+
|
|
43
|
+
def to(self, device: str | torch.device | None) -> "FrameReader":
|
|
44
|
+
"""Set the device for loaded models.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
device: The device to move models to after loading.
|
|
48
|
+
Can be a string like "cuda" or "cpu", a torch.device, or None.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
self for method chaining.
|
|
52
|
+
"""
|
|
53
|
+
self._device = device
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def read(self) -> Iterator[GaussianModel]:
|
|
57
|
+
"""Read GaussianModel frames from the file sequence."""
|
|
58
|
+
model = GaussianModel(sh_degree=self._sh_degree)
|
|
59
|
+
model.load_ply(self._first_frame_path)
|
|
60
|
+
if self._device is not None:
|
|
61
|
+
model.to(self._device)
|
|
62
|
+
yield model
|
|
63
|
+
|
|
64
|
+
frame_count = 1
|
|
65
|
+
if self._max_frames is not None and frame_count >= self._max_frames:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
index = self._start_index
|
|
69
|
+
while True:
|
|
70
|
+
path = self._subsequent_format.format(index)
|
|
71
|
+
model = GaussianModel(sh_degree=self._sh_degree)
|
|
72
|
+
model.load_ply(path)
|
|
73
|
+
if self._device is not None:
|
|
74
|
+
model.to(self._device)
|
|
75
|
+
yield model
|
|
76
|
+
frame_count += 1
|
|
77
|
+
if self._max_frames is not None and frame_count >= self._max_frames:
|
|
78
|
+
break
|
|
79
|
+
index += 1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class FrameWriter:
|
|
83
|
+
"""
|
|
84
|
+
Writer for GaussianModel frames to a series of files.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
writer = FrameWriter(
|
|
88
|
+
first_frame_path="output/frame_0000.ply",
|
|
89
|
+
subsequent_format="output/frame_{:04d}.ply",
|
|
90
|
+
start_index=1
|
|
91
|
+
)
|
|
92
|
+
writer.write(frame_iterator)
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
first_frame_path: str,
|
|
98
|
+
subsequent_format: str,
|
|
99
|
+
start_index: int,
|
|
100
|
+
overwrite: bool = False,
|
|
101
|
+
):
|
|
102
|
+
self._first_frame_path = first_frame_path
|
|
103
|
+
self._subsequent_format = subsequent_format
|
|
104
|
+
self._start_index = start_index
|
|
105
|
+
self._overwrite = overwrite
|
|
106
|
+
|
|
107
|
+
def write(self, frames: Iterator[GaussianModel]) -> None:
|
|
108
|
+
"""Write GaussianModel frames to the file sequence."""
|
|
109
|
+
is_first = True
|
|
110
|
+
index = self._start_index
|
|
111
|
+
|
|
112
|
+
for frame in frames:
|
|
113
|
+
if is_first:
|
|
114
|
+
path = self._first_frame_path
|
|
115
|
+
is_first = False
|
|
116
|
+
else:
|
|
117
|
+
path = self._subsequent_format.format(index)
|
|
118
|
+
index += 1
|
|
119
|
+
|
|
120
|
+
if not self._overwrite and os.path.exists(path):
|
|
121
|
+
raise FileExistsError(f"File already exists: {path}")
|
|
122
|
+
|
|
123
|
+
parent_dir = os.path.dirname(path)
|
|
124
|
+
if parent_dir and not os.path.exists(parent_dir):
|
|
125
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
126
|
+
|
|
127
|
+
frame.save_ply(path)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""复制 cameras.json 和 cfg_args 文件从输入路径到输出路径"""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def copy_configs(src: Path, dst: Path):
|
|
9
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
for name in ["cameras.json", "cfg_args"]:
|
|
11
|
+
if (src / name).exists():
|
|
12
|
+
shutil.copy2(src / name, dst / name)
|
|
13
|
+
print(f"{src / name} -> {dst / name}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
p = argparse.ArgumentParser()
|
|
18
|
+
p.add_argument("--first-frame-in")
|
|
19
|
+
p.add_argument("--first-frame-out")
|
|
20
|
+
p.add_argument("--subsequent-format-in")
|
|
21
|
+
p.add_argument("--subsequent-format-out")
|
|
22
|
+
p.add_argument("--max-frames", type=int)
|
|
23
|
+
args = p.parse_args()
|
|
24
|
+
|
|
25
|
+
if args.first_frame_in:
|
|
26
|
+
copy_configs(Path(args.first_frame_in), Path(args.first_frame_out))
|
|
27
|
+
|
|
28
|
+
if args.subsequent_format_in:
|
|
29
|
+
for i in range(2, args.max_frames + 1):
|
|
30
|
+
copy_configs(
|
|
31
|
+
Path(args.subsequent_format_in.format(i)),
|
|
32
|
+
Path(args.subsequent_format_out.format(i)),
|
|
33
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Payload(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract base class for frame payload data.
|
|
10
|
+
|
|
11
|
+
This represents the intermediate data structure after processing a frame,
|
|
12
|
+
before serialization. Subclasses should define specific fields for their
|
|
13
|
+
encoding scheme.
|
|
14
|
+
|
|
15
|
+
The name "Payload" reflects its role as the processed data that will be
|
|
16
|
+
passed to a serializer for conversion to bytes.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def to(self, device) -> Self:
|
|
21
|
+
"""
|
|
22
|
+
Move the Payload to the specified device for further processing.
|
|
23
|
+
|
|
24
|
+
Subclasses must implement this method to handle device transfer
|
|
25
|
+
(e.g., moving tensors to GPU/CPU).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
device: The target device (e.g., 'cpu', 'cuda', torch.device).
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A new Payload instance on the target device, or self if already
|
|
32
|
+
on the target device.
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
from .payload import Payload
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AbstractSerializer(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract base class for serializing Payload objects to bytes.
|
|
10
|
+
|
|
11
|
+
Subclasses must implement `serialize_frame` and `flush` methods to define
|
|
12
|
+
the specific serialization logic for Payload objects.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def serialize_frame(self, payload: Payload) -> Iterator[bytes]:
|
|
17
|
+
"""
|
|
18
|
+
Serialize a single Payload object to bytes.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
payload: A Payload instance to serialize.
|
|
22
|
+
|
|
23
|
+
Yields:
|
|
24
|
+
Serialized byte chunks. May yield zero, one, or multiple chunks.
|
|
25
|
+
When the iterator is exhausted, all data for this payload has been
|
|
26
|
+
serialized.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def flush(self) -> Iterator[bytes]:
|
|
32
|
+
"""
|
|
33
|
+
Flush any remaining buffered data.
|
|
34
|
+
|
|
35
|
+
This method should be called after all payloads have been serialized
|
|
36
|
+
to ensure any remaining buffered data is output.
|
|
37
|
+
|
|
38
|
+
Yields:
|
|
39
|
+
Remaining buffered byte chunks. May yield zero, one, or multiple
|
|
40
|
+
chunks until all buffered data has been flushed.
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .interface import (
|
|
2
|
+
VQInterframeCodecConfig,
|
|
3
|
+
VQInterframeCodecContext,
|
|
4
|
+
VQKeyframePayload,
|
|
5
|
+
VQInterframePayload,
|
|
6
|
+
VQInterframeCodecInterface,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'VQInterframeCodecConfig',
|
|
11
|
+
'VQInterframeCodecContext',
|
|
12
|
+
'VQKeyframePayload',
|
|
13
|
+
'VQInterframePayload',
|
|
14
|
+
'VQInterframeCodecInterface',
|
|
15
|
+
]
|