Skip to content

Commit 99aa39a

Browse files
Correct the model retrieval
1 parent c24bd96 commit 99aa39a

4 files changed

Lines changed: 127 additions & 49 deletions

File tree

gsplatInterface/trainer.py

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
from typing import Literal, assert_never
4040
from utils import AppearanceOptModule, CameraOptModule, knn, rgb_to_sh, set_random_seed
4141

42+
from numpy.core.multiarray import scalar as npscalar
43+
from numpy.dtypes import Float64DType as npFloat64DType
44+
from numpy import dtype as npdtype
45+
4246
from gsplat import export_splats
4347
from gsplat.distributed import cli
4448
from gsplat.optimizers import SelectiveAdam
@@ -58,6 +62,9 @@ class Config:
5862
# Path to the .pt files to load and resume training
5963
resume_ckpt: Optional[str] = ""
6064

65+
# Whether to retrieve optimizer state from the checkpoint
66+
retrieve_optimizer_state: bool = True
67+
6168
# Path to the dataset
6269
sfm_file: str = "sfm.json"
6370
image_alpha: bool = False
@@ -127,6 +134,9 @@ class Config:
127134
# Use random background for training to discourage transparency
128135
random_bkgd: bool = False
129136

137+
# Whether to use schedulers for the learning rates (only for MCMC)
138+
use_scheduler: bool = True
139+
130140
# LR for 3D point positions
131141
means_lr: float = 1.6e-4
132142
# LR for Gaussian scale factors
@@ -222,6 +232,7 @@ def create_splats_with_optimizers(
222232
device: str = "cuda",
223233
world_rank: int = 0,
224234
world_size: int = 1,
235+
strategy: Union[DefaultStrategy, MCMCStrategy] = DefaultStrategy,
225236
) -> Tuple[torch.nn.ParameterDict, Dict[str, torch.optim.Optimizer]]:
226237

227238
#initialize from sfm
@@ -328,6 +339,12 @@ def __init__(
328339

329340
logging.info(f"Scene scale: {self.scene_scale}")
330341

342+
retrieve_optimizer_state = cfg.retrieve_optimizer_state and cfg.resume_ckpt != ""
343+
if retrieve_optimizer_state and isinstance(cfg.strategy, MCMCStrategy):
344+
# In case of MCMC, the scheduler reduces the effective learning rate by 100
345+
# This line updates the learning rate accordingly
346+
cfg.means_lr = 0.01 * cfg.means_lr
347+
331348
# Model
332349
feature_dim = 32 if cfg.app_opt else None
333350
self.splats, self.optimizers = create_splats_with_optimizers(
@@ -349,6 +366,7 @@ def __init__(
349366
device=self.device,
350367
world_rank=world_rank,
351368
world_size=world_size,
369+
strategy=cfg.strategy,
352370
)
353371
logging.info(f"Model initialized. Number of GS: {len(self.splats['means'])}")
354372

@@ -517,37 +535,42 @@ def train(self):
517535
#Compute effective steps
518536
max_steps = cfg.max_epochs * len(self.trainset)
519537

520-
schedulers = [
521-
# means has a learning rate schedule, that end at 0.01 of the initial value
522-
torch.optim.lr_scheduler.ExponentialLR(
523-
self.optimizers["means"], gamma=0.01 ** (1.0 / max_steps)
524-
),
525-
]
538+
schedulers = []
539+
if isinstance(cfg.strategy, MCMCStrategy) and cfg.use_scheduler:
526540

527-
if cfg.pose_opt:
528-
# pose optimization has a learning rate schedule
529-
schedulers.append(
541+
logging.info("Create schedulers")
542+
543+
schedulers = [
544+
# means has a learning rate schedule that ends at 0.01 of the initial value
530545
torch.optim.lr_scheduler.ExponentialLR(
531-
self.pose_optimizers[0], gamma=0.01 ** (1.0 / max_steps)
546+
self.optimizers["means"], gamma=0.01 ** (1.0 / max_steps)
547+
),
548+
]
549+
550+
if cfg.pose_opt:
551+
# pose optimization has a learning rate schedule
552+
schedulers.append(
553+
torch.optim.lr_scheduler.ExponentialLR(
554+
self.pose_optimizers[0], gamma=0.01 ** (1.0 / max_steps)
555+
)
532556
)
533-
)
534557

535-
if cfg.use_bilateral_grid:
536-
# bilateral grid has a learning rate schedule. Linear warmup for 1000 steps.
537-
schedulers.append(
538-
torch.optim.lr_scheduler.ChainedScheduler(
539-
[
540-
torch.optim.lr_scheduler.LinearLR(
541-
self.bil_grid_optimizers[0],
542-
start_factor=0.01,
543-
total_iters=1000,
544-
),
545-
torch.optim.lr_scheduler.ExponentialLR(
546-
self.bil_grid_optimizers[0], gamma=0.01 ** (1.0 / max_steps)
547-
),
548-
]
558+
if cfg.use_bilateral_grid:
559+
# bilateral grid has a learning rate schedule. Linear warmup for 1000 steps.
560+
schedulers.append(
561+
torch.optim.lr_scheduler.ChainedScheduler(
562+
[
563+
torch.optim.lr_scheduler.LinearLR(
564+
self.bil_grid_optimizers[0],
565+
start_factor=0.01,
566+
total_iters=1000,
567+
),
568+
torch.optim.lr_scheduler.ExponentialLR(
569+
self.bil_grid_optimizers[0], gamma=0.01 ** (1.0 / max_steps)
570+
),
571+
]
572+
)
549573
)
550-
)
551574

552575
trainloader = torch.utils.data.DataLoader(
553576
self.trainset,
@@ -560,6 +583,34 @@ def train(self):
560583

561584
step = -1
562585

586+
if cfg.resume_ckpt != "":
587+
torch.serialization.add_safe_globals([npscalar, npdtype, npFloat64DType])
588+
logging.info("Loading Gaussians from checkpoint")
589+
ckpt = torch.load(cfg.resume_ckpt, map_location=device, weights_only=True)
590+
for k in self.splats.keys():
591+
self.splats[k].data = torch.cat([ckpt["splats"][k]])
592+
if cfg.pose_opt and "pose_adjust" in ckpt:
593+
pose_adjust = self.pose_adjust.module if world_size > 1 else self.pose_adjust
594+
pose_adjust.load_state_dict(ckpt["pose_adjust"])
595+
if cfg.app_opt and "app_module" in ckpt:
596+
app_module = self.app_module.module if world_size > 1 else self.app_module
597+
app_module.load_state_dict(ckpt["app_module"])
598+
if cfg.retrieve_optimizer_state:
599+
logging.info("Loading optimizer state from checkpoint")
600+
self.strategy_state = ckpt["strategy_state"]
601+
for name, opt_state_dict in ckpt["optimizer_state_dicts"].items():
602+
self.optimizers[name].load_state_dict(opt_state_dict)
603+
if cfg.pose_opt and "pose_optimizer_state_dicts" in ckpt:
604+
for optimizer, opt_state_dict in zip(self.pose_optimizers, ckpt["pose_optimizer_state_dicts"]):
605+
optimizer.load_state_dict(opt_state_dict)
606+
if cfg.app_opt and "app_optimizer_state_dicts" in ckpt:
607+
for optimizer, opt_state_dict in zip(self.app_optimizers, ckpt["app_optimizer_state_dicts"]):
608+
optimizer.load_state_dict(opt_state_dict)
609+
if cfg.use_bilateral_grid and "bil_grid_optimizer_state_dicts" in ckpt:
610+
for optimizer, opt_state_dict in zip(self.bil_grid_optimizers, ckpt["bil_grid_optimizer_state_dicts"]):
611+
optimizer.load_state_dict(opt_state_dict)
612+
step = ckpt["step"] - 1
613+
563614
if not cfg.use_progress_bar:
564615
logging.info("Training started")
565616

@@ -746,13 +797,14 @@ def train(self):
746797
packed=cfg.packed,
747798
)
748799
elif isinstance(self.cfg.strategy, MCMCStrategy):
800+
means_lr = schedulers[0].get_last_lr()[0] if cfg.use_scheduler else self.optimizers["means"].param_groups[0]["lr"]
749801
self.cfg.strategy.step_post_backward(
750802
params=self.splats,
751803
optimizers=self.optimizers,
752804
state=self.strategy_state,
753805
step=step,
754806
info=info,
755-
lr=schedulers[0].get_last_lr()[0],
807+
lr=means_lr,
756808
)
757809
else:
758810
assert_never(self.cfg.strategy)
@@ -763,7 +815,16 @@ def train(self):
763815
if epoch in [i - 1 for i in cfg.save_epochs] or epoch == cfg.max_epochs - 1:
764816

765817
path = f"{self.ckpt_dir}/ckpt_{epoch}_rank{self.world_rank}.pt"
766-
data = {"step": step, "splats": self.splats.state_dict()}
818+
data = {"step": step,
819+
"splats": self.splats.state_dict(),
820+
"strategy_state": self.strategy_state,
821+
"optimizer_state_dicts": {name: opt.state_dict() for name, opt in self.optimizers.items()}}
822+
if cfg.pose_opt:
823+
data["pose_optimizer_state_dicts"] = [optimizer.state_dict() for optimizer in self.pose_optimizers]
824+
if cfg.app_opt:
825+
data["app_optimizer_state_dicts"] = [optimizer.state_dict() for optimizer in self.app_optimizers]
826+
if cfg.use_bilateral_grid:
827+
data["bil_grid_optimizer_state_dicts"] = [optimizer.state_dict() for optimizer in self.bil_grid_optimizers]
767828

768829
if cfg.pose_opt:
769830
if world_size > 1:
@@ -784,13 +845,6 @@ def main(local_rank: int, world_rank, world_size: int, cfg: Config):
784845
#Create object for computation
785846
runner = Runner(local_rank, world_rank, world_size, cfg)
786847

787-
#if a checkpoint was passed, will load the gaussians from there
788-
if cfg.resume_ckpt != "":
789-
logging.info("Loading from checkpoint")
790-
ckpt = torch.load(cfg.resume_ckpt, map_location=runner.device, weights_only=True)
791-
for k in runner.splats.keys():
792-
runner.splats[k].data = torch.cat([ckpt["splats"][k]])
793-
794848
#Launch training
795849
runner.train()
796850

gsplatInterface/viewer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
from datasets.sfm.sceneManager import PoseParser
2020
# from cameraPosesParser import CameraParser
21+
from numpy.core.multiarray import scalar as npscalar
22+
from numpy.dtypes import Float64DType as npFloat64DType
23+
from numpy import dtype as npdtype
2124

2225

2326
def get_cameras_from_sfm(sfmFile):
@@ -56,7 +59,8 @@ def get_cameras_from_sfm(sfmFile):
5659
def main(local_rank: int, world_rank, world_size: int, args):
5760
torch.manual_seed(42)
5861
device = torch.device("cuda", local_rank)
59-
62+
torch.serialization.add_safe_globals([npscalar, npdtype, npFloat64DType])
63+
6064
means, quats, scales, opacities, sh0, shN = [], [], [], [], [], []
6165
for ckpt_path in args.ckpt:
6266
ckpt = torch.load(ckpt_path, map_location=device, weights_only=True)["splats"]

meshroom/gaussianSplattingPhotogrammetry.mg

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
{
22
"header": {
33
"releaseVersion": "2026.1.0+develop",
4-
"fileVersion": "2.0",
4+
"fileVersion": "2.1",
55
"nodesVersions": {
6-
"CameraInit": "12.0",
6+
"CameraInit": "12.1",
77
"ExportImages": "1.1",
88
"FeatureExtraction": "1.3",
9-
"FeatureMatching": "2.0",
10-
"GaussianSplattingOptim": "1.0",
9+
"FeatureMatching": "2.1",
10+
"GaussianSplattingOptim": "1.1",
1111
"GaussianSplattingRender": "1.0",
1212
"ImageMatching": "2.0",
1313
"IntrinsicsTransforming": "1.1",
1414
"RelativePoseEstimating": "3.1",
15-
"SfMBootStrapping": "4.1",
15+
"SfMBootStrapping": "4.2",
1616
"SfMColorizing": "1.0",
17-
"SfMExpanding": "2.3",
17+
"SfMExpanding": "2.4",
1818
"SfMTransform": "3.2",
1919
"TracksBuilding": "1.0"
2020
},
@@ -118,9 +118,14 @@
118118
],
119119
"inputs": {
120120
"sfm_file": "{ExportImages_2.outputSfMData}",
121-
"image_alpha": true,
121+
"image_alpha": "{GaussianSplattingOptim_1.image_alpha}",
122122
"resume_ckpt": "{GaussianSplattingOptim_1.model}",
123-
"max_epochs": 100
123+
"strategy": "{GaussianSplattingOptim_1.strategy}",
124+
"max_epochs": 100,
125+
"refine_start_iter": "{GaussianSplattingOptim_1.refine_start_iter}",
126+
"refine_stop_iter": "{GaussianSplattingOptim_1.refine_stop_iter}",
127+
"reset_every": "{GaussianSplattingOptim_1.reset_every}",
128+
"refine_every": "{GaussianSplattingOptim_1.refine_every}"
124129
}
125130
},
126131
"GaussianSplattingOptim_3": {
@@ -131,9 +136,14 @@
131136
],
132137
"inputs": {
133138
"sfm_file": "{ExportImages_3.outputSfMData}",
134-
"image_alpha": true,
139+
"image_alpha": "{GaussianSplattingOptim_2.image_alpha}",
135140
"resume_ckpt": "{GaussianSplattingOptim_2.model}",
136-
"max_epochs": 100
141+
"strategy": "{GaussianSplattingOptim_2.strategy}",
142+
"max_epochs": 100,
143+
"refine_start_iter": "{GaussianSplattingOptim_2.refine_start_iter}",
144+
"refine_stop_iter": "{GaussianSplattingOptim_2.refine_stop_iter}",
145+
"reset_every": "{GaussianSplattingOptim_2.reset_every}",
146+
"refine_every": "{GaussianSplattingOptim_2.refine_every}"
137147
}
138148
},
139149
"GaussianSplattingRender_1": {
@@ -265,4 +275,4 @@
265275
}
266276
}
267277
}
268-
}
278+
}

meshroom/mrGSplat/GaussianSplattingOptim.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.0"
1+
__version__ = "1.1"
22

33
from meshroom.core import desc
44

@@ -73,12 +73,14 @@ def buildCommandLine(self, chunk):
7373
if node.revisedOpacity.value:
7474
cmdLine += " --revised_opacity"
7575

76+
if not node.retrieveOptimState.value:
77+
cmdLine += " --no-retrieve_optimizer_state"
78+
7679
if node.useProgressBar.value:
7780
cmdLine += f" --use_progress_bar"
7881

7982
node.nodeDesc.commandLine = cmdLine
8083

81-
8284
return super().buildCommandLine(chunk)
8385

8486
inputs = [
@@ -101,6 +103,14 @@ def buildCommandLine(self, chunk):
101103
description="Resume from Model",
102104
value="",
103105
),
106+
desc.BoolParam(
107+
name="retrieveOptimState",
108+
label="Retrieve Optimizer State",
109+
description="Whether to retrieve optimizer state from the checkpoint.",
110+
value=True,
111+
commandLineGroup=None,
112+
enabled=lambda node: node.resume_ckpt.value != ""
113+
),
104114
desc.ChoiceParam(
105115
name='strategy',
106116
label='Densification Strategy',

0 commit comments

Comments
 (0)