Skip to content

Commit f0b5ba2

Browse files
committed
docs(readme): update opensf link and a source branch.
* save still core file for reader to quick check.
1 parent d007e20 commit f0b5ba2

4 files changed

Lines changed: 335 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ DeFlow: Decoder of Scene Flow Network in Autonomous Driving
99

1010
Task: Scene Flow Estimation in Autonomous Driving.
1111

12-
📜 2025/02/18: Merging all scene flow code to a codebase to update one general repo only. This repo still saved DeFlow README and [cluster slurm files](assets/slurm).
12+
📜 2025/02/18: Merging all scene flow code to [OpenSceneFLow codebase](https://github.com/KTH-RPL/OpenSceneFlow) for afterward code maintenance. This repo saved README, [cluster slurm files](assets/slurm), and [quick core file](decoder.py) in DeFlow. The old source code branch is also [available here](https://github.com/KTH-RPL/DeFlow/tree/source).
1313

1414
🤗 2024/11/18 16:17: Update model and demo data download link through HuggingFace, personally I found that `wget` from the HuggingFace link is much faster than Zenodo.
1515

decoder.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import torch
2+
import torch.nn as nn
3+
from typing import List, Tuple, Dict
4+
from . import ConvWithNorms
5+
6+
SPLIT_BATCH_SIZE = 512
7+
8+
class MMHeadDecoder(nn.Module):
9+
10+
def __init__(self, pseudoimage_channels: int = 64):
11+
super().__init__()
12+
13+
self.offset_encoder = nn.Linear(3, 128)
14+
15+
# FIXME: figure out how to set nheads and num_layers properly
16+
# ref: https://pytorch.org/docs/stable/generated/torch.nn.TransformerDecoder.html
17+
# https://pytorch.org/docs/stable/generated/torch.nn.TransformerDecoderLayer.html
18+
transform_decoder_layers = nn.TransformerDecoderLayer(d_model=128, nhead=4)
19+
self.pts_off_transformer = nn.TransformerDecoder(transform_decoder_layers, num_layers=4)
20+
21+
self.decoder = nn.Sequential(
22+
nn.Linear(pseudoimage_channels*2, 32), nn.GELU(),
23+
nn.Linear(32, 3))
24+
25+
def forward_single(self, before_pseudoimage: torch.Tensor,
26+
after_pseudoimage: torch.Tensor,
27+
point_offsets: torch.Tensor,
28+
voxel_coords: torch.Tensor) -> torch.Tensor:
29+
voxel_coords = voxel_coords.long()
30+
# assert (voxel_coords[:, 0] == 0).all(), "Z index must be 0"
31+
32+
# Voxel coords are Z, Y, X, and the pseudoimage is Channel, Y, X
33+
# I have confirmed via visualization that these coordinates are correct.
34+
after_voxel_vectors = after_pseudoimage[:, voxel_coords[:, 1],
35+
voxel_coords[:, 2]].T
36+
before_voxel_vectors = before_pseudoimage[:, voxel_coords[:, 1],
37+
voxel_coords[:, 2]].T
38+
39+
# [N, 64] [N, 64] -> [N, 128]
40+
concatenated_vectors = torch.cat([before_voxel_vectors, after_voxel_vectors], dim=1)
41+
42+
# [N, 128] [N, 128] -> [N, 1, 128]
43+
voxel_feature = concatenated_vectors.unsqueeze(1)
44+
point_offsets_feature = self.offset_encoder(point_offsets).unsqueeze(1)
45+
concatenated_feature = torch.zeros_like(voxel_feature)
46+
47+
for spilt_range in range(0, concatenated_feature.shape[0], SPLIT_BATCH_SIZE):
48+
concatenated_feature[spilt_range:spilt_range+SPLIT_BATCH_SIZE] = self.pts_off_transformer(
49+
voxel_feature[spilt_range:spilt_range+SPLIT_BATCH_SIZE],
50+
point_offsets_feature[spilt_range:spilt_range+SPLIT_BATCH_SIZE]
51+
)
52+
53+
flow = self.decoder(concatenated_feature.squeeze(1))
54+
return flow
55+
56+
def forward(
57+
self, before_pseudoimages: torch.Tensor,
58+
after_pseudoimages: torch.Tensor,
59+
voxelizer_infos: List[Dict[str,
60+
torch.Tensor]]) -> List[torch.Tensor]:
61+
62+
flow_results = []
63+
for before_pseudoimage, after_pseudoimage, voxelizer_info in zip(
64+
before_pseudoimages, after_pseudoimages, voxelizer_infos):
65+
point_offsets = voxelizer_info["point_offsets"]
66+
voxel_coords = voxelizer_info["voxel_coords"]
67+
flow = self.forward_single(before_pseudoimage, after_pseudoimage,
68+
point_offsets, voxel_coords)
69+
flow_results.append(flow)
70+
return flow_results
71+
72+
class LinearDecoder(nn.Module):
73+
74+
def __init__(self, pseudoimage_channels: int = 64):
75+
super().__init__()
76+
77+
self.offset_encoder = nn.Linear(3, 128)
78+
79+
self.decoder = nn.Sequential(
80+
nn.Linear(pseudoimage_channels*4, 32), nn.GELU(),
81+
nn.Linear(32, 3))
82+
83+
def forward_single(self, before_pseudoimage: torch.Tensor,
84+
after_pseudoimage: torch.Tensor,
85+
point_offsets: torch.Tensor,
86+
voxel_coords: torch.Tensor) -> torch.Tensor:
87+
voxel_coords = voxel_coords.long()
88+
# assert (voxel_coords[:, 0] == 0).all(), "Z index must be 0"
89+
90+
# Voxel coords are Z, Y, X, and the pseudoimage is Channel, Y, X
91+
# I have confirmed via visualization that these coordinates are correct.
92+
after_voxel_vectors = after_pseudoimage[:, voxel_coords[:, 1],
93+
voxel_coords[:, 2]].T
94+
before_voxel_vectors = before_pseudoimage[:, voxel_coords[:, 1],
95+
voxel_coords[:, 2]].T
96+
97+
# [N, 64] [N, 64] -> [N, 128]
98+
concatenated_vectors = torch.cat([before_voxel_vectors, after_voxel_vectors], dim=1)
99+
100+
# [N, 3] -> [N, 128]
101+
point_offsets_feature = self.offset_encoder(point_offsets)
102+
103+
flow = self.decoder(torch.cat([concatenated_vectors, point_offsets_feature], dim=1))
104+
return flow
105+
106+
def forward(
107+
self, before_pseudoimages: torch.Tensor,
108+
after_pseudoimages: torch.Tensor,
109+
voxelizer_infos: List[Dict[str,
110+
torch.Tensor]]) -> List[torch.Tensor]:
111+
112+
flow_results = []
113+
for before_pseudoimage, after_pseudoimage, voxelizer_info in zip(
114+
before_pseudoimages, after_pseudoimages, voxelizer_infos):
115+
point_offsets = voxelizer_info["point_offsets"]
116+
voxel_coords = voxelizer_info["voxel_coords"]
117+
flow = self.forward_single(before_pseudoimage, after_pseudoimage,
118+
point_offsets, voxel_coords)
119+
flow_results.append(flow)
120+
return flow_results
121+
122+
# from https://github.com/weiyithu/PV-RAFT/blob/main/model/update.py
123+
class ConvGRU(nn.Module):
124+
def __init__(self, input_dim=64, hidden_dim=128):
125+
super(ConvGRU, self).__init__()
126+
self.convz = nn.Conv1d(input_dim+hidden_dim, hidden_dim, 1)
127+
self.convr = nn.Conv1d(input_dim+hidden_dim, hidden_dim, 1)
128+
self.convq = nn.Conv1d(input_dim+hidden_dim, hidden_dim, 1)
129+
130+
def forward(self, h, x):
131+
hx = torch.cat([h, x], dim=1)
132+
133+
z = torch.sigmoid(self.convz(hx))
134+
r = torch.sigmoid(self.convr(hx))
135+
rh_x = torch.cat([r*h, x], dim=1)
136+
q = torch.tanh(self.convq(rh_x))
137+
138+
h = (1 - z) * h + z * q
139+
return h
140+
141+
class ConvGRUDecoder(nn.Module):
142+
143+
def __init__(self, pseudoimage_channels: int = 64, num_iters: int = 4):
144+
super().__init__()
145+
146+
self.offset_encoder = nn.Linear(3, pseudoimage_channels)
147+
148+
# NOTE: voxel feature is hidden input, point offset is input, check paper's Fig. 3
149+
self.gru = ConvGRU(input_dim=pseudoimage_channels, hidden_dim=pseudoimage_channels*2)
150+
151+
self.decoder = nn.Sequential(
152+
nn.Linear(pseudoimage_channels*3, pseudoimage_channels//2), nn.GELU(),
153+
nn.Linear(pseudoimage_channels//2, 3))
154+
self.num_iters = num_iters
155+
156+
def forward_single(self, before_pseudoimage: torch.Tensor,
157+
after_pseudoimage: torch.Tensor,
158+
point_offsets: torch.Tensor,
159+
voxel_coords: torch.Tensor) -> torch.Tensor:
160+
voxel_coords = voxel_coords.long()
161+
# assert (voxel_coords[:, 0] == 0).all(), "Z index must be 0"
162+
163+
# Voxel coords are Z, Y, X, and the pseudoimage is Channel, Y, X
164+
# I have confirmed via visualization that these coordinates are correct.
165+
after_voxel_vectors = after_pseudoimage[:, voxel_coords[:, 1],
166+
voxel_coords[:, 2]].T
167+
before_voxel_vectors = before_pseudoimage[:, voxel_coords[:, 1],
168+
voxel_coords[:, 2]].T
169+
170+
# [N, 64] [N, 64] -> [N, 128]
171+
concatenated_vectors = torch.cat([before_voxel_vectors, after_voxel_vectors], dim=1)
172+
173+
# [N, 3] -> [N, 64]
174+
point_offsets_feature = self.offset_encoder(point_offsets)
175+
176+
# [N, 128] -> [N, 128, 1]
177+
concatenated_vectors = concatenated_vectors.unsqueeze(2)
178+
179+
for itr in range(self.num_iters):
180+
concatenated_vectors = self.gru(concatenated_vectors, point_offsets_feature.unsqueeze(2))
181+
182+
flow = self.decoder(torch.cat([concatenated_vectors.squeeze(2), point_offsets_feature], dim=1))
183+
return flow
184+
185+
def forward(
186+
self, before_pseudoimages: torch.Tensor,
187+
after_pseudoimages: torch.Tensor,
188+
voxelizer_infos: List[Dict[str,
189+
torch.Tensor]]) -> List[torch.Tensor]:
190+
191+
flow_results = []
192+
for before_pseudoimage, after_pseudoimage, voxelizer_info in zip(
193+
before_pseudoimages, after_pseudoimages, voxelizer_infos):
194+
point_offsets = voxelizer_info["point_offsets"]
195+
voxel_coords = voxelizer_info["voxel_coords"]
196+
flow = self.forward_single(before_pseudoimage, after_pseudoimage,
197+
point_offsets, voxel_coords)
198+
flow_results.append(flow)
199+
return flow_results
200+
201+
202+
class ConvWithNorms(nn.Module):
203+
204+
def __init__(self, in_num_channels: int, out_num_channels: int,
205+
kernel_size: int, stride: int, padding: int):
206+
super().__init__()
207+
self.conv = nn.Conv2d(in_num_channels, out_num_channels, kernel_size,
208+
stride, padding)
209+
self.batchnorm = nn.BatchNorm2d(out_num_channels)
210+
self.nonlinearity = nn.GELU()
211+
212+
def forward(self, x: torch.Tensor) -> torch.Tensor:
213+
conv_res = self.conv(x)
214+
if conv_res.shape[2] == 1 and conv_res.shape[3] == 1:
215+
# This is a hack to get around the fact that batchnorm doesn't support
216+
# 1x1 convolutions
217+
batchnorm_res = conv_res
218+
else:
219+
batchnorm_res = self.batchnorm(conv_res)
220+
return self.nonlinearity(batchnorm_res)

deflow.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
"""
3+
# Created: 2023-07-18 15:08
4+
# Copyright (C) 2023-now, RPL, KTH Royal Institute of Technology
5+
# Author: Qingwen Zhang (https://kin-zhang.github.io/)
6+
#
7+
# This file is part of OpenSceneFlow (https://github.com/KTH-RPL/OpenSceneFlow).
8+
# If you find this repo helpful, please cite the respective publication as
9+
# listed on the above website.
10+
"""
11+
12+
import torch.nn as nn
13+
import dztimer, torch
14+
15+
from .basic.unet import FastFlow3DUNet
16+
from .basic.encoder import DynamicEmbedder
17+
from .basic.decoder import LinearDecoder, ConvGRUDecoder
18+
from .basic import cal_pose0to1
19+
20+
class DeFlow(nn.Module):
21+
def __init__(self, voxel_size = [0.2, 0.2, 6],
22+
point_cloud_range = [-51.2, -51.2, -3, 51.2, 51.2, 3],
23+
grid_feature_size = [512, 512],
24+
decoder_option = "gru",
25+
num_iters = 4):
26+
super().__init__()
27+
self.embedder = DynamicEmbedder(voxel_size=voxel_size,
28+
pseudo_image_dims=grid_feature_size,
29+
point_cloud_range=point_cloud_range,
30+
feat_channels=32)
31+
32+
self.backbone = FastFlow3DUNet()
33+
if decoder_option == "gru":
34+
self.head = ConvGRUDecoder(num_iters = num_iters)
35+
elif decoder_option == "linear":
36+
self.head = LinearDecoder()
37+
38+
self.timer = dztimer.Timing()
39+
self.timer.start("Total")
40+
41+
def load_from_checkpoint(self, ckpt_path):
42+
ckpt = torch.load(ckpt_path, map_location="cpu")["state_dict"]
43+
state_dict = {
44+
k[len("model.") :]: v for k, v in ckpt.items() if k.startswith("model.")
45+
}
46+
print("\nLoading... model weight from: ", ckpt_path, "\n")
47+
return self.load_state_dict(state_dict=state_dict, strict=False)
48+
49+
def forward(self, batch):
50+
"""
51+
input: using the batch from dataloader, which is a dict
52+
Detail: [pc0, pc1, pose0, pose1]
53+
output: the predicted flow, pose_flow, and the valid point index of pc0
54+
"""
55+
self.timer[0].start("Data Preprocess")
56+
batch_sizes = len(batch["pose0"])
57+
58+
pose_flows = []
59+
transform_pc0s = []
60+
for batch_id in range(batch_sizes):
61+
selected_pc0 = batch["pc0"][batch_id]
62+
self.timer[0][0].start("pose")
63+
with torch.no_grad():
64+
if 'ego_motion' in batch:
65+
pose_0to1 = batch['ego_motion'][batch_id]
66+
else:
67+
pose_0to1 = cal_pose0to1(batch["pose0"][batch_id], batch["pose1"][batch_id])
68+
self.timer[0][0].stop()
69+
70+
self.timer[0][1].start("transform")
71+
# transform selected_pc0 to pc1
72+
transform_pc0 = selected_pc0 @ pose_0to1[:3, :3].T + pose_0to1[:3, 3]
73+
self.timer[0][1].stop()
74+
pose_flows.append(transform_pc0 - selected_pc0)
75+
transform_pc0s.append(transform_pc0)
76+
77+
pc0s = torch.stack(transform_pc0s, dim=0)
78+
pc1s = batch["pc1"]
79+
self.timer[0].stop()
80+
81+
self.timer[1].start("Voxelization")
82+
pc0_before_pseudoimages, pc0_voxel_infos_lst = self.embedder(pc0s)
83+
pc1_before_pseudoimages, pc1_voxel_infos_lst = self.embedder(pc1s)
84+
self.timer[1].stop()
85+
86+
self.timer[2].start("Encoder")
87+
grid_flow_pseudoimage = self.backbone(pc0_before_pseudoimages,
88+
pc1_before_pseudoimages)
89+
self.timer[2].stop()
90+
91+
self.timer[3].start("Decoder")
92+
flows = self.head(
93+
torch.cat((pc0_before_pseudoimages, pc1_before_pseudoimages),
94+
dim=1), grid_flow_pseudoimage, pc0_voxel_infos_lst)
95+
self.timer[3].stop()
96+
97+
pc0_points_lst = [e["points"] for e in pc0_voxel_infos_lst]
98+
pc1_points_lst = [e["points"] for e in pc1_voxel_infos_lst]
99+
100+
pc0_valid_point_idxes = [e["point_idxes"] for e in pc0_voxel_infos_lst]
101+
pc1_valid_point_idxes = [e["point_idxes"] for e in pc1_voxel_infos_lst]
102+
103+
model_res = {
104+
"flow": flows,
105+
'pose_flow': pose_flows,
106+
107+
"pc0_valid_point_idxes": pc0_valid_point_idxes,
108+
"pc0_points_lst": pc0_points_lst,
109+
110+
"pc1_valid_point_idxes": pc1_valid_point_idxes,
111+
"pc1_points_lst": pc1_points_lst,
112+
}
113+
return model_res

0 commit comments

Comments
 (0)