Skip to content

Commit 188cc9e

Browse files
committed
feat(eval): update how to submit results on the 2rd leaderboard version.
1 parent 62a8c44 commit 188cc9e

9 files changed

Lines changed: 140 additions & 241 deletions

File tree

2_eval.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ def main(cfg):
2929
sys.exit(1)
3030

3131
checkpoint_params = DictConfig(torch.load(cfg.checkpoint)["hyper_parameters"])
32-
cfg.output = checkpoint_params.cfg.output + f"-{cfg.av2_mode}"
32+
cfg.output = checkpoint_params.cfg.output + f"-{cfg.av2_mode}-v{cfg.leaderboard_version}"
3333
cfg.model.update(checkpoint_params.cfg.model)
3434
mymodel = ModelWrapper.load_from_checkpoint(cfg.checkpoint, cfg=cfg, eval=True)
35+
print(f"\n---LOG[eval]: Loaded model from {cfg.checkpoint}. The model is {checkpoint_params.cfg.model.name}.\n")
3536

3637
wandb_logger = WandbLogger(save_dir=output_dir,
3738
entity="kth-rpl",
@@ -42,7 +43,7 @@ def main(cfg):
4243
trainer = pl.Trainer(logger=wandb_logger, devices=1)
4344
# NOTE(Qingwen): search & check: def eval_only_step_(self, batch, res_dict)
4445
trainer.validate(model = mymodel, \
45-
dataloaders = DataLoader(HDF5Dataset(cfg.dataset_path + f"/{cfg.av2_mode}", eval=True), batch_size=1, shuffle=False))
46+
dataloaders = DataLoader(HDF5Dataset(cfg.dataset_path + f"/{cfg.av2_mode}", eval=True, leaderboard_version=cfg.leaderboard_version), batch_size=1, shuffle=False))
4647
wandb.finish()
4748

4849
if __name__ == "__main__":

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ DeFlow: Decoder of Scene Flow Network in Autonomous Driving
77
[![video](https://img.shields.io/badge/video-YouTube-FF0000?logo=youtube&logoColor=white)](https://youtu.be/bZ4uUv0nDa0)
88

99
Task: Scene Flow Estimation in Autonomous Driving.
10+
11+
🔥 2024/07/02: Check the self-supervised version in our new ECCV'24 [SeFlow](https://github.com/KTH-RPL/SeFlow). The 1st ranking in new leaderboard among self-supervise methods.
12+
1013
Pre-trained weights for models are available in [Zenodo](https://zenodo.org/records/12173874) or [Onedrive link](https://hkustconnect-my.sharepoint.com/:f:/g/personal/qzhangcb_connect_ust_hk/Et85xv7IGMRKgqrVeJEVkMoB_vxlcXk6OZUyiPjd4AArIg?e=lqRGhx).
1114
Check usage in [2. Evaluation](#2-evaluation) or [3. Visualization](#3-visualization).
1215

@@ -72,10 +75,6 @@ Benchmarking and baseline methods:
7275
```bash
7376
python 1_train.py model=fastflow3d lr=2e-6 epochs=50 batch_size=16
7477
python 1_train.py model=deflow lr=2e-6 epochs=50 batch_size=16
75-
76-
# for nsfp no need train but optimize iteration running
77-
python 2_eval.py model=nsfp
78-
python 2_eval.py model=fast_nsfp
7978
```
8079

8180
To help community benchmarking, we provide our weights including fastflow3d, deflow [Onedrive link](https://hkustconnect-my.sharepoint.com/:f:/g/personal/qzhangcb_connect_ust_hk/Et85xv7IGMRKgqrVeJEVkMoB_vxlcXk6OZUyiPjd4AArIg?e=lqRGhx). These checkpoints also include parameters and status of that epoch inside it. If you are interested in weights of ablation studies, please contact us.
@@ -92,23 +91,28 @@ Since in training, we save all hyper-parameters and model checkpoints, the only
9291
wget https://zenodo.org/records/12173874/files/deflow_best.ckpt
9392

9493
python 2_eval.py checkpoint=/home/kin/deflow_best.ckpt av2_mode=val # it will directly prints all metric
95-
python 2_eval.py checkpoint=/home/kin/deflow_best.ckpt av2_mode=test # it will output the av2_submit.zip for you to submit to leaderboard
94+
# it will output the av2_submit.zip or av2_submit_v2.zip for you to submit to leaderboard
95+
python 2_eval.py checkpoint=/home/kin/deflow_best.ckpt av2_mode=test leaderboard_version=1
96+
python 2_eval.py checkpoint=/home/kin/deflow_best.ckpt av2_mode=test leaderboard_version=2
9697
```
9798

9899
Check all detailed result files (presented in our paper Table 1) in [this discussion](https://github.com/KTH-RPL/DeFlow/discussions/2).
99100

100-
To submit to the Online Leaderboard, the last step will tell you the resulting path, copy it here:
101+
To submit to the Online Leaderboard, if you select `av2_mode=test`, it should be a zip file for you to submit to the leaderboard.
102+
Note: The leaderboard result in DeFlow main paper is [version 1](https://eval.ai/web/challenges/challenge-page/2010/evaluation), as [version 2](https://eval.ai/web/challenges/challenge-page/2210/overview) is updated after DeFlow paper.
103+
101104
```bash
102-
# you will find there is a av2_submit.zip in the folder now. since the env is different and conflict we set new one:
105+
# since the env may conflict we set new on deflow, we directly create new one:
103106
mamba create -n py37 python=3.7
104107
mamba activate py37
105108
pip install "evalai"
106109

107110
# Step 2: login in eval and register your team
108-
evalai set_token <your token>
111+
evalai set-token <your token>
109112

110113
# Step 3: Submit to leaderboard
111114
evalai challenge 2010 phase 4018 submit --file av2_submit.zip --large --private
115+
evalai challenge 2210 phase 4396 submit --file av2_submit_v2.zip --large --private
112116
```
113117

114118
## 3. Visualization

assets/docs/index_eval_v2.pkl

144 KB
Binary file not shown.

conf/eval.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ checkpoint: /home/kin/model_zoo/deflow.ckpt
44
av2_mode: val # [val, test]
55
save_res: False # [True, False]
66

7+
leaderboard_version: 1 # [1, 2]
8+
supervised_flag: True # [True, False], whether you use any label from the dataset
9+
710
# no need to change
811
slurm_id: 00000
912
output: ${model.name}-${slurm_id}

scripts/network/dataloader.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def collate_fn_pad(batch):
4343

4444
return res_dict
4545
class HDF5Dataset(Dataset):
46-
def __init__(self, directory, eval = False):
46+
def __init__(self, directory, eval = False, leaderboard_version=1):
4747
'''
4848
directory: the directory of the dataset
4949
eval: if True, use the eval index
@@ -56,10 +56,14 @@ def __init__(self, directory, eval = False):
5656

5757
self.eval_index = False
5858
if eval:
59-
if not os.path.exists(os.path.join(self.directory, 'index_eval.pkl')):
59+
index_file_name = 'index_eval.pkl'
60+
if leaderboard_version == 2:
61+
print("Using index to leaderboard version 2!!")
62+
index_file_name = 'index_eval_v2.pkl'
63+
if not os.path.exists(os.path.join(self.directory, index_file_name)):
6064
raise Exception(f"No eval index file found! Please check {self.directory}")
6165
self.eval_index = eval
62-
with open(os.path.join(self.directory, 'index_eval.pkl'), 'rb') as f:
66+
with open(os.path.join(self.directory, index_file_name), 'rb') as f:
6367
self.eval_data_index = pickle.load(f)
6468

6569
self.scene_id_bounds = {} # 存储每个scene_id的最大最小timestamp和位置
@@ -140,9 +144,9 @@ def __getitem__(self, index_):
140144
res_dict['ego_motion'] = ego_motion
141145

142146
if self.eval_index:
143-
eval_mask = torch.tensor(f[key]['eval_mask'][:])
147+
# looks like v2 not follow the same rule as v1 with eval_mask provided
148+
eval_mask = torch.tensor(f[key]['eval_mask'][:]) if 'eval_mask' in f[key] else torch.ones_like(pc0[:, 0], dtype=torch.bool)
144149
res_dict['eval_mask'] = eval_mask
145-
146150
return res_dict
147151

148152
if __name__ == "__main__":

scripts/pl_model.py

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -56,26 +56,29 @@ def __init__(self, cfg, eval=False):
5656

5757
self.metrics = OfficialMetrics()
5858

59-
if 'checkpoint' in cfg:
60-
self.load_checkpoint_path = cfg.checkpoint
59+
self.load_checkpoint_path = cfg.checkpoint if 'checkpoint' in cfg else None
6160

61+
62+
self.leaderboard_version = cfg.leaderboard_version if 'leaderboard_version' in cfg else 1
63+
# NOTE(Qingwen): since we have seflow version which is unsupervised, we need to set the flag to false.
64+
self.supervised_flag = cfg.supervised_flag if 'supervised_flag' in cfg else True
65+
self.save_res = False
6266
if 'av2_mode' in cfg:
6367
self.av2_mode = cfg.av2_mode
64-
self.save_res = cfg.save_res
65-
if self.save_res:
68+
self.save_res = cfg.save_res if 'save_res' in cfg else False
69+
70+
if self.save_res or self.av2_mode == 'test':
6671
self.save_res_path = Path(cfg.dataset_path).parent / "results" / cfg.output
6772
os.makedirs(self.save_res_path, exist_ok=True)
68-
print(f"We are in {cfg.av2_mode}, results will be saved in: {self.save_res_path}")
73+
print(f"We are in {cfg.av2_mode}, results will be saved in: {self.save_res_path} with version: {self.leaderboard_version} format for online leaderboard.")
6974
else:
7075
self.av2_mode = None
7176
if 'pretrained_weights' in cfg:
7277
if cfg.pretrained_weights is not None:
7378
self.model.load_from_checkpoint(cfg.pretrained_weights)
7479

75-
if 'dataset_path' in cfg:
76-
self.dataset_path = cfg.dataset_path
77-
if 'res_name' in cfg:
78-
self.vis_name = cfg.res_name
80+
self.dataset_path = cfg.dataset_path if 'dataset_path' in cfg else None
81+
self.vis_name = cfg.res_name if 'res_name' in cfg else 'default'
7982
self.save_hyperparameters()
8083

8184
def training_step(self, batch, batch_idx):
@@ -130,14 +133,24 @@ def train_validation_step_(self, batch, res_dict):
130133
self.metrics.step(v1_dict, v2_dict)
131134
else:
132135
pass
133-
136+
137+
def configure_optimizers(self):
138+
optimizer = optim.Adam(self.model.parameters(), lr=self.lr)
139+
return optimizer
140+
141+
def on_train_epoch_start(self):
142+
self.time_start_train_epoch = time.time()
143+
144+
def on_train_epoch_end(self):
145+
self.log("pre_epoch_cost (mins)", (time.time()-self.time_start_train_epoch)/60.0, on_step=False, on_epoch=True, sync_dist=True)
146+
134147
def on_validation_epoch_end(self):
135148
self.model.timer.print(random_colors=False, bold=False)
136149

137150
if self.av2_mode == 'test':
138151
print(f"\nModel: {self.model.__class__.__name__}, Checkpoint from: {self.load_checkpoint_path}")
139-
print(f"Test results saved in: {self.save_res_path}, Please run submit to zip the results and upload to online leaderboard.")
140-
output_file = zip_res(self.save_res_path)
152+
print(f"Test results saved in: {self.save_res_path}, Please run submit to zip the results and upload to online leaderboard. You processed to {self.leaderboard_version} version.")
153+
output_file = zip_res(self.save_res_path, leaderboard_version=self.leaderboard_version)
141154
# wandb.log_artifact(output_file)
142155
return
143156

@@ -157,31 +170,29 @@ def on_validation_epoch_end(self):
157170
self.metrics.print()
158171

159172
self.metrics = OfficialMetrics()
173+
174+
if self.save_res:
175+
print(f"We already write the flow_est into the dataset, please run following commend to visualize the flow. Copy and paste it to your terminal:")
176+
print(f"python tests/scene_flow.py --flow_mode '{self.vis_name}' --data_dir {self.dataset_path}")
177+
print(f"Enjoy! ^v^ ------ \n")
160178

161179
def eval_only_step_(self, batch, res_dict):
162-
batch = {key: batch[key][0] for key in batch if len(batch[key])>0}
163-
res_dict = {key: res_dict[key][0] for key in res_dict if len(res_dict[key])>0}
164-
165180
eval_mask = batch['eval_mask'].squeeze()
166181
pc0 = batch['origin_pc0']
167182
pose_0to1 = cal_pose0to1(batch["pose0"], batch["pose1"])
168183
transform_pc0 = pc0 @ pose_0to1[:3, :3].T + pose_0to1[:3, 3]
169184
pose_flow = transform_pc0 - pc0
170185

186+
final_flow = pose_flow.clone()
171187
if 'pc0_valid_point_idxes' in res_dict:
172188
valid_from_pc2res = res_dict['pc0_valid_point_idxes']
173189

174190
# flow in the original pc0 coordinate
175191
pred_flow = pose_flow[~batch['gm0']].clone()
176-
pred_flow[valid_from_pc2res] = pose_flow[~batch['gm0']][valid_from_pc2res] + res_dict['flow']
177-
178-
final_flow = pose_flow.clone()
192+
pred_flow[valid_from_pc2res] = res_dict['flow'] + pose_flow[~batch['gm0']][valid_from_pc2res]
179193
final_flow[~batch['gm0']] = pred_flow
180-
# else:
181-
# # pose_flow = pose_flows
182-
# # pred_flow_ = res_dict['flow'].cpu().detach()
183-
# # TODO: for other methods....
184-
# pred_flow = pose_flows.clone()
194+
else:
195+
final_flow[~batch['gm0']] = res_dict['flow'] + pose_flow[~batch['gm0']]
185196

186197
if self.av2_mode == 'val': # since only val we have ground truth flow to eval
187198
gt_flow = batch["flow"]
@@ -199,59 +210,52 @@ def eval_only_step_(self, batch, res_dict):
199210
rigid_flow = pose_flow[eval_mask, :3].cpu().detach().numpy()
200211
is_dynamic = np.linalg.norm(save_pred_flow - rigid_flow, axis=1, ord=2) >= 0.05
201212
sweep_uuid = (batch['scene_id'], batch['timestamp'])
202-
write_output_file(save_pred_flow, is_dynamic, sweep_uuid, self.save_res_path)
213+
if self.leaderboard_version == 2:
214+
save_pred_flow = (final_flow - pose_flow).cpu().detach().numpy() # all points here... since 2rd version we need to save the relative flow.
215+
write_output_file(save_pred_flow, is_dynamic, sweep_uuid, self.save_res_path, leaderboard_version=self.leaderboard_version)
216+
217+
def run_model_wo_ground_data(self, batch):
218+
# NOTE (Qingwen): only needed when val or test mode, since train we will go through collate_fn to remove.
219+
batch['origin_pc0'] = batch['pc0'].clone()
220+
batch['pc0'] = batch['pc0'][~batch['gm0']].unsqueeze(0)
221+
batch['pc1'] = batch['pc1'][~batch['gm1']].unsqueeze(0)
222+
if 'pcb0' in batch:
223+
batch['pcb0'] = batch['pcb0'][~batch['gmb0']].unsqueeze(0)
224+
self.model.timer[12].start("One Scan")
225+
res_dict = self.model(batch)
226+
self.model.timer[12].stop()
203227

228+
# NOTE (Qingwen): Since val and test, we will force set batch_size = 1
229+
batch = {key: batch[key][0] for key in batch if len(batch[key])>0}
230+
res_dict = {key: res_dict[key][0] for key in res_dict if len(res_dict[key])>0}
231+
return batch, res_dict
232+
204233
def validation_step(self, batch, batch_idx):
205234
if self.av2_mode == 'val' or self.av2_mode == 'test':
206-
batch['origin_pc0'] = batch['pc0'].clone()
207-
batch['pc0'] = batch['pc0'][~batch['gm0']].unsqueeze(0)
208-
batch['pc1'] = batch['pc1'][~batch['gm1']].unsqueeze(0)
209-
self.model.timer[12].start("One Scan")
210-
res_dict = self.model(batch)
211-
self.model.timer[12].stop()
235+
batch, res_dict = self.run_model_wo_ground_data(batch)
212236
self.eval_only_step_(batch, res_dict)
213237
else:
214238
res_dict = self.model(batch)
215239
self.train_validation_step_(batch, res_dict)
216240

217-
def configure_optimizers(self):
218-
optimizer = optim.Adam(self.model.parameters(), lr=self.lr)
219-
return optimizer
220-
221-
def on_train_epoch_start(self):
222-
self.time_start_train_epoch = time.time()
223-
224-
def on_train_epoch_end(self):
225-
self.log("pre_epoch_cost (mins)", (time.time()-self.time_start_train_epoch)/60.0, on_step=False, on_epoch=True, sync_dist=True)
226-
227241
def test_step(self, batch, batch_idx):
228-
# NOTE (Qingwen): again, val and test we only allow batch_size = 1
229-
batch['origin_pc0'] = batch['pc0'].clone()
230-
batch['pc0'] = batch['pc0'][~batch['gm0']].unsqueeze(0)
231-
batch['pc1'] = batch['pc1'][~batch['gm1']].unsqueeze(0)
232-
res_dict = self.model(batch)
233-
batch = {key: batch[key][0] for key in batch if len(batch[key])>0}
234-
res_dict = {key: res_dict[key][0] for key in res_dict if len(res_dict[key])>0}
235-
242+
batch, res_dict = self.run_model_wo_ground_data(batch)
236243
pc0 = batch['origin_pc0']
237244
pose_0to1 = cal_pose0to1(batch["pose0"], batch["pose1"])
238245
transform_pc0 = pc0 @ pose_0to1[:3, :3].T + pose_0to1[:3, 3]
239246
pose_flow = transform_pc0 - pc0
240247

248+
final_flow = pose_flow.clone()
241249
if 'pc0_valid_point_idxes' in res_dict:
242250
valid_from_pc2res = res_dict['pc0_valid_point_idxes']
243251

244252
# flow in the original pc0 coordinate
245253
pred_flow = pose_flow[~batch['gm0']].clone()
246254
pred_flow[valid_from_pc2res] = pose_flow[~batch['gm0']][valid_from_pc2res] + res_dict['flow']
247255

248-
final_flow = pose_flow.clone()
249256
final_flow[~batch['gm0']] = pred_flow
250-
# else:
251-
# # pose_flow = pose_flows
252-
# # pred_flow_ = res_dict['flow'].cpu().detach()
253-
# # TODO: for other methods....
254-
# pred_flow = pose_flows.clone()
257+
else:
258+
final_flow[~batch['gm0']] = res_dict['flow'] + pose_flow[~batch['gm0']]
255259

256260
# write final_flow into the dataset.
257261
key = str(batch['timestamp'])
@@ -262,7 +266,8 @@ def test_step(self, batch, batch_idx):
262266
f[key].create_dataset(self.vis_name, data=final_flow.cpu().detach().numpy().astype(np.float32))
263267

264268
def on_test_epoch_end(self):
269+
self.model.timer.print(random_colors=False, bold=False)
265270
print(f"\n\nModel: {self.model.__class__.__name__}, Checkpoint from: {self.load_checkpoint_path}")
266-
print(f"We already write the estimate flow: {self.vis_name} into the dataset, please run following commend to visualize the flow. Copy and paste it to your terminal:")
271+
print(f"We already write the flow_est into the dataset, please run following commend to visualize the flow. Copy and paste it to your terminal:")
267272
print(f"python tests/scene_flow.py --flow_mode '{self.vis_name}' --data_dir {self.dataset_path}")
268273
print(f"Enjoy! ^v^ ------ \n")

0 commit comments

Comments
 (0)