├── .github
└── workflows
│ └── stale.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── bash
├── assets
│ ├── checksum.json
│ └── urls
│ │ ├── cropped_images.txt
│ │ ├── feat.txt
│ │ ├── images.txt
│ │ ├── mano.txt
│ │ ├── misc.txt
│ │ ├── mocap.txt
│ │ ├── models.txt
│ │ ├── smplx.txt
│ │ └── splits.txt
├── clean_downloads.sh
├── download_baselines.sh
├── download_body_models.sh
├── download_cropped_images.sh
├── download_dry_run.sh
├── download_feat.sh
├── download_images.sh
├── download_misc.sh
├── download_mocap.sh
└── download_splits.sh
├── common
├── .gitignore
├── ___init___.py
├── abstract_pl.py
├── args_utils.py
├── body_models.py
├── camera.py
├── comet_utils.py
├── data_utils.py
├── ld_utils.py
├── list_utils.py
├── mesh.py
├── metrics.py
├── np_utils.py
├── object_tensors.py
├── pl_utils.py
├── rend_utils.py
├── rot.py
├── sys_utils.py
├── thing.py
├── torch_utils.py
├── transforms.py
├── viewer.py
├── vis_utils.py
└── xdict.py
├── docs
├── data
│ ├── README.md
│ ├── data_doc.md
│ ├── mano_right.png
│ ├── processing.md
│ └── visualize.md
├── faq.md
├── leaderboard.md
├── leaderboard_format.md
├── model
│ ├── README.md
│ ├── extraction.md
│ └── train.md
├── purchase.md
├── setup.md
├── static
│ ├── aitviewer-logo.svg
│ ├── arctic-logo.svg
│ ├── dexterous.gif
│ ├── hold
│ │ ├── mug_ours.gif
│ │ └── mug_ref.png
│ ├── misalignment.png
│ ├── teaser.jpeg
│ └── viewer_demo.gif
└── stock_photos
│ ├── .DS_Store
│ ├── box.jpg
│ ├── coffee_machine.jpg
│ ├── expresso_machine.jpg
│ ├── google.png
│ ├── ketchup.jpg
│ ├── laptop.jpg
│ ├── microwave.jpg
│ ├── mixer.jpg
│ ├── notebook.jpg
│ ├── phone.jpg
│ ├── scissors.jpg
│ └── waffleiron.jpg
├── requirements.txt
├── scripts_data
├── build_splits.py
├── checksum.py
├── crop_images.py
├── download_data.py
├── mocap_viewer.py
├── process_seqs.py
├── unzip_download.py
└── visualizer.py
├── scripts_method
├── build_feat_split.py
├── evaluate_metrics.py
├── extract_predicts.py
├── train.py
└── visualizer.py
└── src
├── arctic
├── preprocess_dataset.py
├── processing.py
└── split.py
├── callbacks
├── loss
│ ├── loss_arctic_lstm.py
│ ├── loss_arctic_sf.py
│ └── loss_field.py
├── process
│ ├── process_arctic.py
│ ├── process_field.py
│ └── process_generic.py
└── vis
│ ├── visualize_arctic.py
│ └── visualize_field.py
├── datasets
├── arctic_dataset.py
├── arctic_dataset_eval.py
├── dataset_utils.py
├── tempo_dataset.py
├── tempo_inference_dataset.py
└── tempo_inference_dataset_eval.py
├── extraction
├── interface.py
└── keys
│ ├── eval_field.py
│ ├── eval_pose.py
│ ├── feat_field.py
│ ├── feat_pose.py
│ ├── submit_field.py
│ ├── submit_pose.py
│ ├── vis_field.py
│ └── vis_pose.py
├── factory.py
├── mesh_loaders
├── arctic.py
├── field.py
└── pose.py
├── models
├── __init__.py
├── arctic_lstm
│ ├── model.py
│ └── wrapper.py
├── arctic_sf
│ ├── model.py
│ └── wrapper.py
├── field_lstm
│ ├── model.py
│ └── wrapper.py
├── field_sf
│ ├── model.py
│ └── wrapper.py
└── generic
│ └── wrapper.py
├── nets
├── backbone
│ ├── __init__.py
│ ├── resnet.py
│ └── utils.py
├── hand_heads
│ ├── hand_hmr.py
│ └── mano_head.py
├── hmr_layer.py
├── obj_heads
│ ├── obj_head.py
│ └── obj_hmr.py
└── pointnet.py
├── parsers
├── configs
│ ├── arctic_lstm.py
│ ├── arctic_sf.py
│ ├── field_lstm.py
│ ├── field_sf.py
│ └── generic.py
├── generic_parser.py
└── parser.py
└── utils
├── const.py
├── eval_modules.py
├── interfield.py
├── loss_modules.py
└── mdev.py
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
2 | #
3 | # You can adjust the behavior by modifying this file.
4 | # For more information, see:
5 | # https://github.com/actions/stale
6 | name: Mark stale issues and pull requests
7 |
8 | on:
9 | schedule:
10 | - cron: '39 22 * * *'
11 |
12 | jobs:
13 | stale:
14 |
15 | runs-on: ubuntu-latest
16 | permissions:
17 | issues: write
18 | pull-requests: write
19 |
20 | steps:
21 | - uses: actions/stale@v5
22 | with:
23 | repo-token: ${{ secrets.GITHUB_TOKEN }}
24 | stale-issue-message: 'Stale issue message'
25 | stale-pr-message: 'Stale pull request message'
26 | stale-issue-label: 'no-issue-activity'
27 | stale-pr-label: 'no-pr-activity'
28 | days-before-stale: 30
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | __pycache__
3 | /data
4 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean fix imports sort
2 |
3 | ## Delete all compiled Python files
4 | clean:
5 | find . -type f -name "*.py[co]" -delete
6 | find . -type d -name "__pycache__" -delete
7 | rm -rf condor_logs/*
8 | find run_scripts/* -delete
9 | find logs/* -delete
10 | fix:
11 | black src common scripts_method scripts_data
12 | sort:
13 | isort src common scripts_method scripts_data --wrap-length=1 --combine-as --trailing-comma --use-parentheses
14 | imports:
15 | autoflake -i -r --remove-all-unused-imports src common scripts_method scripts_data
16 |
--------------------------------------------------------------------------------
/bash/assets/urls/feat.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/feat.zip
--------------------------------------------------------------------------------
/bash/assets/urls/mano.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=mano&resume=1&sfile=mano_v1_2.zip
--------------------------------------------------------------------------------
/bash/assets/urls/misc.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/splits_json.zip
2 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/raw_seqs.zip
3 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/meta.zip
4 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/images_zips/backgrounds.zip
5 |
--------------------------------------------------------------------------------
/bash/assets/urls/mocap.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/mocap/mocap_c3d.zip
2 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/mocap/mocap_npy.zip
3 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/mocap/smplx_corres.zip
4 |
--------------------------------------------------------------------------------
/bash/assets/urls/models.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/models.zip
2 |
--------------------------------------------------------------------------------
/bash/assets/urls/smplx.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=smplx&sfile=models_smplx_v1_1.zip
--------------------------------------------------------------------------------
/bash/assets/urls/splits.txt:
--------------------------------------------------------------------------------
1 | https://download.is.tue.mpg.de/download.php?domain=arctic&resume=1&sfile=arctic_release/c7216c3b205186106a1f8326ed7b948f838e4907e69b21c8b3c87bb69d87206e/v1_0/data/splits.zip
--------------------------------------------------------------------------------
/bash/clean_downloads.sh:
--------------------------------------------------------------------------------
1 | find downloads unpack render_out outputs -delete # clear dry run data
2 |
--------------------------------------------------------------------------------
/bash/download_baselines.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading model weights"
5 | mkdir -p downloads/
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/models.txt --out_folder downloads
7 |
--------------------------------------------------------------------------------
/bash/download_body_models.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading SMPLX"
5 | mkdir -p downloads
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/smplx.txt --out_folder downloads
7 | unzip downloads/models_smplx_v1_1.zip
8 | mv models body_models
9 |
10 | echo "Downloading MANO"
11 | python scripts_data/download_data.py --url_file ./bash/assets/urls/mano.txt --out_folder downloads
12 |
13 | mkdir -p unpack
14 | cd downloads
15 | unzip mano_v1_2.zip
16 | mv mano_v1_2/models ../body_models/mano
17 | cd ..
18 | mv body_models unpack
19 |
--------------------------------------------------------------------------------
/bash/download_cropped_images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading cropped images"
5 | mkdir -p downloads/data/cropped_images_zips
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/cropped_images.txt --out_folder downloads/data/cropped_images_zips
7 |
--------------------------------------------------------------------------------
/bash/download_dry_run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading smaller files"
5 | mkdir -p downloads/data
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/misc.txt --out_folder downloads/data --dry_run
7 |
8 | echo "Downloading model weights"
9 | mkdir -p downloads/
10 | python scripts_data/download_data.py --url_file ./bash/assets/urls/models.txt --out_folder downloads --dry_run
11 |
12 | echo "Downloading cropped images"
13 | mkdir -p downloads/data/cropped_images_zips
14 | python scripts_data/download_data.py --url_file ./bash/assets/urls/cropped_images.txt --out_folder downloads/data/cropped_images_zips --dry_run
15 |
16 | echo "Downloading full resolution images"
17 | mkdir -p downloads/data/images_zips
18 | python scripts_data/download_data.py --url_file ./bash/assets/urls/images.txt --out_folder downloads/data/images_zips --dry_run
19 |
20 | echo "Downloading SMPLX"
21 | mkdir -p downloads
22 | python scripts_data/download_data.py --url_file ./bash/assets/urls/smplx.txt --out_folder downloads
23 | unzip downloads/models_smplx_v1_1.zip
24 | mv models body_models
25 |
26 | echo "Downloading MANO"
27 | python scripts_data/download_data.py --url_file ./bash/assets/urls/mano.txt --out_folder downloads
28 |
29 | mkdir unpack
30 | cd downloads
31 | unzip mano_v1_2.zip
32 | mv mano_v1_2/models ../body_models/mano
33 | cd ..
34 | mv body_models unpack
35 |
--------------------------------------------------------------------------------
/bash/download_feat.sh:
--------------------------------------------------------------------------------
1 | echo "Downloading features files"
2 | mkdir -p downloads/data
3 | python scripts_data/download_data.py --url_file ./bash/assets/urls/feat.txt --out_folder downloads/data
--------------------------------------------------------------------------------
/bash/download_images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading full resolution images"
5 | mkdir -p downloads/data/images_zips
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/images.txt --out_folder downloads/data/images_zips
7 |
--------------------------------------------------------------------------------
/bash/download_misc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading smaller files"
5 | mkdir -p downloads/data
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/misc.txt --out_folder downloads/data
7 |
--------------------------------------------------------------------------------
/bash/download_mocap.sh:
--------------------------------------------------------------------------------
1 | echo "Downloading features files"
2 | mkdir -p downloads/data
3 | python scripts_data/download_data.py --url_file ./bash/assets/urls/mocap.txt --out_folder downloads/data
4 |
--------------------------------------------------------------------------------
/bash/download_splits.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Downloading preprocessed splits"
5 | mkdir -p downloads/data
6 | python scripts_data/download_data.py --url_file ./bash/assets/urls/splits.txt --out_folder downloads/data
--------------------------------------------------------------------------------
/common/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 |
--------------------------------------------------------------------------------
/common/___init___.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zc-alexfan/arctic/e904c9695911d0e4a2025ebaeff1d91e5caf4d69/common/___init___.py
--------------------------------------------------------------------------------
/common/args_utils.py:
--------------------------------------------------------------------------------
1 | from loguru import logger
2 |
3 |
4 | def set_default_params(args, default_args):
5 | # if a val is not set on argparse, use default val
6 | # else, use the one in the argparse
7 | custom_dict = {}
8 | for key, val in args.items():
9 | if val is None:
10 | args[key] = default_args[key]
11 | else:
12 | custom_dict[key] = val
13 |
14 | logger.info(f"Using custom values: {custom_dict}")
15 | return args
16 |
--------------------------------------------------------------------------------
/common/body_models.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import numpy as np
4 | import torch
5 | from smplx import MANO
6 |
7 | from common.mesh import Mesh
8 |
9 |
10 | class MANODecimator:
11 | def __init__(self):
12 | data = np.load(
13 | "./data/arctic_data/data/meta/mano_decimator_195.npy", allow_pickle=True
14 | ).item()
15 | mydata = {}
16 | for key, val in data.items():
17 | # only consider decimation matrix so far
18 | if "D" in key:
19 | mydata[key] = torch.FloatTensor(val)
20 | self.data = mydata
21 |
22 | def downsample(self, verts, is_right):
23 | dev = verts.device
24 | flag = "right" if is_right else "left"
25 | if self.data[f"D_{flag}"].device != dev:
26 | self.data[f"D_{flag}"] = self.data[f"D_{flag}"].to(dev)
27 | D = self.data[f"D_{flag}"]
28 | batch_size = verts.shape[0]
29 | D_batch = D[None, :, :].repeat(batch_size, 1, 1)
30 | verts_sub = torch.bmm(D_batch, verts)
31 | return verts_sub
32 |
33 |
34 | MODEL_DIR = "./data/body_models/mano"
35 |
36 | SEAL_FACES_R = [
37 | [120, 108, 778],
38 | [108, 79, 778],
39 | [79, 78, 778],
40 | [78, 121, 778],
41 | [121, 214, 778],
42 | [214, 215, 778],
43 | [215, 279, 778],
44 | [279, 239, 778],
45 | [239, 234, 778],
46 | [234, 92, 778],
47 | [92, 38, 778],
48 | [38, 122, 778],
49 | [122, 118, 778],
50 | [118, 117, 778],
51 | [117, 119, 778],
52 | [119, 120, 778],
53 | ]
54 |
55 | # vertex ids around the ring of the wrist
56 | CIRCLE_V_ID = np.array(
57 | [108, 79, 78, 121, 214, 215, 279, 239, 234, 92, 38, 122, 118, 117, 119, 120],
58 | dtype=np.int64,
59 | )
60 |
61 |
62 | def seal_mano_mesh(v3d, faces, is_rhand):
63 | # v3d: B, 778, 3
64 | # faces: 1538, 3
65 | # output: v3d(B, 779, 3); faces (1554, 3)
66 |
67 | seal_faces = torch.LongTensor(np.array(SEAL_FACES_R)).to(faces.device)
68 | if not is_rhand:
69 | # left hand
70 | seal_faces = seal_faces[:, np.array([1, 0, 2])] # invert face normal
71 | centers = v3d[:, CIRCLE_V_ID].mean(dim=1)[:, None, :]
72 | sealed_vertices = torch.cat((v3d, centers), dim=1)
73 | faces = torch.cat((faces, seal_faces), dim=0)
74 | return sealed_vertices, faces
75 |
76 |
77 | def build_layers(device=None):
78 | from common.object_tensors import ObjectTensors
79 |
80 | layers = {
81 | "right": build_mano_aa(True),
82 | "left": build_mano_aa(False),
83 | "object_tensors": ObjectTensors(),
84 | }
85 |
86 | if device is not None:
87 | layers["right"] = layers["right"].to(device)
88 | layers["left"] = layers["left"].to(device)
89 | layers["object_tensors"].to(device)
90 | return layers
91 |
92 |
93 | MANO_MODEL_DIR = "./data/body_models/mano"
94 | SMPLX_MODEL_P = {
95 | "male": "./data/body_models/smplx/SMPLX_MALE.npz",
96 | "female": "./data/body_models/smplx/SMPLX_FEMALE.npz",
97 | "neutral": "./data/body_models/smplx/SMPLX_NEUTRAL.npz",
98 | }
99 |
100 |
101 | def build_smplx(batch_size, gender, vtemplate):
102 | import smplx
103 |
104 | subj_m = smplx.create(
105 | model_path=SMPLX_MODEL_P[gender],
106 | model_type="smplx",
107 | gender=gender,
108 | num_pca_comps=45,
109 | v_template=vtemplate,
110 | flat_hand_mean=True,
111 | use_pca=False,
112 | batch_size=batch_size,
113 | # batch_size=320,
114 | )
115 | return subj_m
116 |
117 |
118 | def build_subject_smplx(batch_size, subject_id):
119 | with open("./data/arctic_data/data/meta/misc.json", "r") as f:
120 | misc = json.load(f)
121 | vtemplate_p = f"./data/arctic_data/data/meta/subject_vtemplates/{subject_id}.obj"
122 | mesh = Mesh(filename=vtemplate_p)
123 | vtemplate = mesh.v
124 | gender = misc[subject_id]["gender"]
125 | return build_smplx(batch_size, gender, vtemplate)
126 |
127 |
128 | def build_mano_aa(is_rhand, create_transl=False, flat_hand=False):
129 | return MANO(
130 | MODEL_DIR,
131 | create_transl=create_transl,
132 | use_pca=False,
133 | flat_hand_mean=flat_hand,
134 | is_rhand=is_rhand,
135 | )
136 |
137 |
138 | def construct_layers(dev):
139 | mano_layers = {
140 | "right": build_mano_aa(True, create_transl=True, flat_hand=False),
141 | "left": build_mano_aa(False, create_transl=True, flat_hand=False),
142 | "smplx": build_smplx(1, "neutral", None),
143 | }
144 | for layer in mano_layers.values():
145 | layer.to(dev)
146 | return mano_layers
147 |
--------------------------------------------------------------------------------
/common/comet_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import os.path as op
4 | import time
5 |
6 | import comet_ml
7 | import numpy as np
8 | import torch
9 | from loguru import logger
10 | from tqdm import tqdm
11 |
12 | from src.datasets.dataset_utils import copy_repo_arctic
13 |
14 | # folder used for debugging
15 | DUMMY_EXP = "xxxxxxxxx"
16 |
17 |
18 | def add_paths(args):
19 | exp_key = args.exp_key
20 | args_p = f"./logs/{exp_key}/args.json"
21 | ckpt_p = f"./logs/{exp_key}/checkpoints/last.ckpt"
22 | if not op.exists(ckpt_p) or DUMMY_EXP in ckpt_p:
23 | ckpt_p = ""
24 | if args.resume_ckpt != "":
25 | ckpt_p = args.resume_ckpt
26 | args.ckpt_p = ckpt_p
27 | args.log_dir = f"./logs/{exp_key}"
28 |
29 | if args.infer_ckpt != "":
30 | basedir = "/".join(args.infer_ckpt.split("/")[:2])
31 | basename = op.basename(args.infer_ckpt).replace(".ckpt", ".params.pt")
32 | args.interface_p = op.join(basedir, basename)
33 | args.args_p = args_p
34 | if args.cluster:
35 | args.run_p = op.join(args.log_dir, "condor", "run.sh")
36 | args.submit_p = op.join(args.log_dir, "condor", "submit.sub")
37 | args.repo_p = op.join(args.log_dir, "repo")
38 |
39 | return args
40 |
41 |
42 | def save_args(args, save_keys):
43 | args_save = {}
44 | for key, val in args.items():
45 | if key in save_keys:
46 | args_save[key] = val
47 | with open(args.args_p, "w") as f:
48 | json.dump(args_save, f, indent=4)
49 | logger.info(f"Saved args at {args.args_p}")
50 |
51 |
52 | def create_files(args):
53 | os.makedirs(args.log_dir, exist_ok=True)
54 | if args.cluster:
55 | os.makedirs(op.dirname(args.run_p), exist_ok=True)
56 | copy_repo_arctic(args.exp_key)
57 |
58 |
59 | def log_exp_meta(args):
60 | tags = [args.method]
61 | logger.info(f"Experiment tags: {tags}")
62 | args.experiment.set_name(args.exp_key)
63 | args.experiment.add_tags(tags)
64 | args.experiment.log_parameters(args)
65 |
66 |
67 | def init_experiment(args):
68 | if args.resume_ckpt != "":
69 | args.exp_key = args.resume_ckpt.split("/")[1]
70 | if args.fast_dev_run:
71 | args.exp_key = DUMMY_EXP
72 | if args.exp_key == "":
73 | args.exp_key = generate_exp_key()
74 | args = add_paths(args)
75 | if op.exists(args.args_p) and args.exp_key not in [DUMMY_EXP]:
76 | with open(args.args_p, "r") as f:
77 | args_disk = json.load(f)
78 | if "comet_key" in args_disk.keys():
79 | args.comet_key = args_disk["comet_key"]
80 |
81 | create_files(args)
82 |
83 | project_name = args.project
84 | disabled = args.mute
85 | comet_url = args["comet_key"] if "comet_key" in args.keys() else None
86 |
87 | api_key = os.environ["COMET_API_KEY"]
88 | workspace = os.environ["COMET_WORKSPACE"]
89 | if not args.cluster:
90 | if comet_url is None:
91 | experiment = comet_ml.Experiment(
92 | api_key=api_key,
93 | workspace=workspace,
94 | project_name=project_name,
95 | disabled=disabled,
96 | display_summary_level=0,
97 | )
98 | args.comet_key = experiment.get_key()
99 | else:
100 | experiment = comet_ml.ExistingExperiment(
101 | previous_experiment=comet_url,
102 | api_key=api_key,
103 | project_name=project_name,
104 | workspace=workspace,
105 | disabled=disabled,
106 | display_summary_level=0,
107 | )
108 |
109 | device = "cuda" if torch.cuda.is_available() else "cpu"
110 | logger.add(
111 | os.path.join(args.log_dir, "train.log"),
112 | level="INFO",
113 | colorize=True,
114 | )
115 | logger.info(torch.cuda.get_device_properties(device))
116 | args.gpu = torch.cuda.get_device_properties(device).name
117 | else:
118 | experiment = None
119 | args.experiment = experiment
120 | return experiment, args
121 |
122 |
123 | def log_dict(experiment, metric_dict, step, postfix=None):
124 | if experiment is None:
125 | return
126 | for key, value in metric_dict.items():
127 | if postfix is not None:
128 | key = key + postfix
129 | if isinstance(value, torch.Tensor) and len(value.view(-1)) == 1:
130 | value = value.item()
131 |
132 | if isinstance(value, (int, float, np.float32)):
133 | experiment.log_metric(key, value, step=step)
134 |
135 |
136 | def generate_exp_key():
137 | import random
138 |
139 | hash = random.getrandbits(128)
140 | key = "%032x" % (hash)
141 | key = key[:9]
142 | return key
143 |
144 |
145 | def push_images(experiment, all_im_list, global_step=None, no_tqdm=False, verbose=True):
146 | if verbose:
147 | print("Pushing PIL images")
148 | tic = time.time()
149 | iterator = all_im_list if no_tqdm else tqdm(all_im_list)
150 | for im in iterator:
151 | im_np = np.array(im["im"])
152 | if "fig_name" in im.keys():
153 | experiment.log_image(im_np, im["fig_name"], step=global_step)
154 | else:
155 | experiment.log_image(im_np, "unnamed", step=global_step)
156 | if verbose:
157 | toc = time.time()
158 | print("Done pushing PIL images (%.1fs)" % (toc - tic))
159 |
--------------------------------------------------------------------------------
/common/ld_utils.py:
--------------------------------------------------------------------------------
1 | import itertools
2 |
3 | import numpy as np
4 | import torch
5 |
6 |
7 | def sort_dict(disordered):
8 | sorted_dict = {k: disordered[k] for k in sorted(disordered)}
9 | return sorted_dict
10 |
11 |
12 | def prefix_dict(mydict, prefix):
13 | out = {prefix + k: v for k, v in mydict.items()}
14 | return out
15 |
16 |
17 | def postfix_dict(mydict, postfix):
18 | out = {k + postfix: v for k, v in mydict.items()}
19 | return out
20 |
21 |
22 | def unsort(L, sort_idx):
23 | assert isinstance(sort_idx, list)
24 | assert isinstance(L, list)
25 | LL = zip(sort_idx, L)
26 | LL = sorted(LL, key=lambda x: x[0])
27 | _, L = zip(*LL)
28 | return list(L)
29 |
30 |
31 | def cat_dl(out_list, dim, verbose=True, squeeze=True):
32 | out = {}
33 | for key, val in out_list.items():
34 | if isinstance(val[0], torch.Tensor):
35 | out[key] = torch.cat(val, dim=dim)
36 | if squeeze:
37 | out[key] = out[key].squeeze()
38 | elif isinstance(val[0], np.ndarray):
39 | out[key] = np.concatenate(val, axis=dim)
40 | if squeeze:
41 | out[key] = np.squeeze(out[key])
42 | elif isinstance(val[0], list):
43 | out[key] = sum(val, [])
44 | else:
45 | if verbose:
46 | print(f"Ignoring {key} undefined type {type(val[0])}")
47 | return out
48 |
49 |
50 | def stack_dl(out_list, dim, verbose=True, squeeze=True):
51 | out = {}
52 | for key, val in out_list.items():
53 | if isinstance(val[0], torch.Tensor):
54 | out[key] = torch.stack(val, dim=dim)
55 | if squeeze:
56 | out[key] = out[key].squeeze()
57 | elif isinstance(val[0], np.ndarray):
58 | out[key] = np.stack(val, axis=dim)
59 | if squeeze:
60 | out[key] = np.squeeze(out[key])
61 | elif isinstance(val[0], list):
62 | out[key] = sum(val, [])
63 | else:
64 | out[key] = val
65 | if verbose:
66 | print(f"Processing {key} undefined type {type(val[0])}")
67 | return out
68 |
69 |
70 | def add_prefix_postfix(mydict, prefix="", postfix=""):
71 | assert isinstance(mydict, dict)
72 | return dict((prefix + key + postfix, value) for (key, value) in mydict.items())
73 |
74 |
75 | def ld2dl(LD):
76 | assert isinstance(LD, list)
77 | assert isinstance(LD[0], dict)
78 | """
79 | A list of dict (same keys) to a dict of lists
80 | """
81 | dict_list = {k: [dic[k] for dic in LD] for k in LD[0]}
82 | return dict_list
83 |
84 |
85 | class NameSpace(object):
86 | def __init__(self, adict):
87 | self.__dict__.update(adict)
88 |
89 |
90 | def dict2ns(mydict):
91 | """
92 | Convert dict objec to namespace
93 | """
94 | return NameSpace(mydict)
95 |
96 |
97 | def ld2dev(ld, dev):
98 | """
99 | Convert tensors in a list or dict to a device recursively
100 | """
101 | if isinstance(ld, torch.Tensor):
102 | return ld.to(dev)
103 | if isinstance(ld, dict):
104 | for k, v in ld.items():
105 | ld[k] = ld2dev(v, dev)
106 | return ld
107 | if isinstance(ld, list):
108 | return [ld2dev(x, dev) for x in ld]
109 | return ld
110 |
111 |
112 | def all_comb_dict(hyper_dict):
113 | assert isinstance(hyper_dict, dict)
114 | keys, values = zip(*hyper_dict.items())
115 | permute_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
116 | return permute_dicts
117 |
--------------------------------------------------------------------------------
/common/list_utils.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 |
4 | def chunks_by_len(L, n):
5 | """
6 | Split a list into n chunks
7 | """
8 | num_chunks = int(math.ceil(float(len(L)) / n))
9 | splits = [L[x : x + num_chunks] for x in range(0, len(L), num_chunks)]
10 | return splits
11 |
12 |
13 | def chunks_by_size(L, n):
14 | """Yield successive n-sized chunks from lst."""
15 | seqs = []
16 | for i in range(0, len(L), n):
17 | seqs.append(L[i : i + n])
18 | return seqs
19 |
20 |
21 | def unsort(L, sort_idx):
22 | assert isinstance(sort_idx, list)
23 | assert isinstance(L, list)
24 | LL = zip(sort_idx, L)
25 | LL = sorted(LL, key=lambda x: x[0])
26 | _, L = zip(*LL)
27 | return list(L)
28 |
29 |
30 | def add_prefix_postfix(mydict, prefix="", postfix=""):
31 | assert isinstance(mydict, dict)
32 | return dict((prefix + key + postfix, value) for (key, value) in mydict.items())
33 |
34 |
35 | def ld2dl(LD):
36 | assert isinstance(LD, list)
37 | assert isinstance(LD[0], dict)
38 | """
39 | A list of dict (same keys) to a dict of lists
40 | """
41 | dict_list = {k: [dic[k] for dic in LD] for k in LD[0]}
42 | return dict_list
43 |
44 |
45 | def chunks(lst, n):
46 | """Yield successive n-sized chunks from lst."""
47 | seqs = []
48 | for i in range(0, len(lst), n):
49 | seqs.append(lst[i : i + n])
50 | seqs_chunked = sum(seqs, [])
51 | assert set(seqs_chunked) == set(lst)
52 | return seqs
53 |
--------------------------------------------------------------------------------
/common/mesh.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import trimesh
3 |
4 | colors = {
5 | "pink": [1.00, 0.75, 0.80],
6 | "purple": [0.63, 0.13, 0.94],
7 | "red": [1.0, 0.0, 0.0],
8 | "green": [0.0, 1.0, 0.0],
9 | "yellow": [1.0, 1.0, 0],
10 | "brown": [1.00, 0.25, 0.25],
11 | "blue": [0.0, 0.0, 1.0],
12 | "white": [1.0, 1.0, 1.0],
13 | "orange": [1.00, 0.65, 0.00],
14 | "grey": [0.75, 0.75, 0.75],
15 | "black": [0.0, 0.0, 0.0],
16 | }
17 |
18 |
19 | class Mesh(trimesh.Trimesh):
20 | def __init__(
21 | self,
22 | filename=None,
23 | v=None,
24 | f=None,
25 | vc=None,
26 | fc=None,
27 | process=False,
28 | visual=None,
29 | **kwargs
30 | ):
31 | if filename is not None:
32 | mesh = trimesh.load(filename, process=process)
33 | v = mesh.vertices
34 | f = mesh.faces
35 | visual = mesh.visual
36 |
37 | super(Mesh, self).__init__(
38 | vertices=v, faces=f, visual=visual, process=process, **kwargs
39 | )
40 |
41 | self.v = self.vertices
42 | self.f = self.faces
43 | assert self.v is self.vertices
44 | assert self.f is self.faces
45 |
46 | if vc is not None:
47 | self.set_vc(vc)
48 | self.vc = self.visual.vertex_colors
49 | assert self.vc is self.visual.vertex_colors
50 | if fc is not None:
51 | self.set_fc(fc)
52 | self.fc = self.visual.face_colors
53 | assert self.fc is self.visual.face_colors
54 |
55 | def rot_verts(self, vertices, rxyz):
56 | return np.array(vertices * rxyz.T)
57 |
58 | def colors_like(self, color, array, ids):
59 | color = np.array(color)
60 |
61 | if color.max() <= 1.0:
62 | color = color * 255
63 | color = color.astype(np.int8)
64 |
65 | n_color = color.shape[0]
66 | n_ids = ids.shape[0]
67 |
68 | new_color = np.array(array)
69 | if n_color <= 4:
70 | new_color[ids, :n_color] = np.repeat(color[np.newaxis], n_ids, axis=0)
71 | else:
72 | new_color[ids, :] = color
73 |
74 | return new_color
75 |
76 | def set_vc(self, vc, vertex_ids=None):
77 | all_ids = np.arange(self.vertices.shape[0])
78 | if vertex_ids is None:
79 | vertex_ids = all_ids
80 |
81 | vertex_ids = all_ids[vertex_ids]
82 | new_vc = self.colors_like(vc, self.visual.vertex_colors, vertex_ids)
83 | self.visual.vertex_colors[:] = new_vc
84 |
85 | def set_fc(self, fc, face_ids=None):
86 | if face_ids is None:
87 | face_ids = np.arange(self.faces.shape[0])
88 |
89 | new_fc = self.colors_like(fc, self.visual.face_colors, face_ids)
90 | self.visual.face_colors[:] = new_fc
91 |
92 | @staticmethod
93 | def cat(meshes):
94 | return trimesh.util.concatenate(meshes)
95 |
--------------------------------------------------------------------------------
/common/metrics.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | import numpy as np
4 | import torch
5 |
6 |
7 | def compute_v2v_dist_no_reduce(v3d_cam_gt, v3d_cam_pred, is_valid):
8 | assert isinstance(v3d_cam_gt, list)
9 | assert isinstance(v3d_cam_pred, list)
10 | assert len(v3d_cam_gt) == len(v3d_cam_pred)
11 | assert len(v3d_cam_gt) == len(is_valid)
12 | v2v = []
13 | for v_gt, v_pred, valid in zip(v3d_cam_gt, v3d_cam_pred, is_valid):
14 | if valid:
15 | dist = ((v_gt - v_pred) ** 2).sum(dim=1).sqrt().cpu().numpy() # meter
16 | else:
17 | dist = None
18 | v2v.append(dist)
19 | return v2v
20 |
21 |
22 | def compute_joint3d_error(joints3d_cam_gt, joints3d_cam_pred, valid_jts):
23 | valid_jts = valid_jts.view(-1)
24 | assert joints3d_cam_gt.shape == joints3d_cam_pred.shape
25 | assert joints3d_cam_gt.shape[0] == valid_jts.shape[0]
26 | dist = ((joints3d_cam_gt - joints3d_cam_pred) ** 2).sum(dim=2).sqrt()
27 | invalid_idx = torch.nonzero((1 - valid_jts).long()).view(-1)
28 | dist[invalid_idx, :] = float("nan")
29 | dist = dist.cpu().numpy()
30 | return dist
31 |
32 |
33 | def compute_mrrpe(root_r_gt, root_l_gt, root_r_pred, root_l_pred, is_valid):
34 | rel_vec_gt = root_l_gt - root_r_gt
35 | rel_vec_pred = root_l_pred - root_r_pred
36 |
37 | invalid_idx = torch.nonzero((1 - is_valid).long()).view(-1)
38 | mrrpe = ((rel_vec_pred - rel_vec_gt) ** 2).sum(dim=1).sqrt()
39 | mrrpe[invalid_idx] = float("nan")
40 | mrrpe = mrrpe.cpu().numpy()
41 | return mrrpe
42 |
43 |
44 | def compute_arti_deg_error(pred_radian, gt_radian):
45 | assert pred_radian.shape == gt_radian.shape
46 |
47 | # articulation error in degree
48 | pred_degree = pred_radian / math.pi * 180 # degree
49 | gt_degree = gt_radian / math.pi * 180 # degree
50 | err_deg = torch.abs(pred_degree - gt_degree).tolist()
51 | return np.array(err_deg, dtype=np.float32)
52 |
--------------------------------------------------------------------------------
/common/np_utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 |
4 | def permute_np(x, idx):
5 | original_perm = tuple(range(len(x.shape)))
6 | x = np.moveaxis(x, original_perm, idx)
7 | return x
8 |
--------------------------------------------------------------------------------
/common/pl_utils.py:
--------------------------------------------------------------------------------
1 | import random
2 | import time
3 |
4 | import torch
5 |
6 | import common.thing as thing
7 | from common.ld_utils import ld2dl
8 |
9 |
10 | def reweight_loss_by_keys(loss_dict, keys, alpha):
11 | for key in keys:
12 | val, weight = loss_dict[key]
13 | weight_new = weight * alpha
14 | loss_dict[key] = (val, weight_new)
15 | return loss_dict
16 |
17 |
18 | def select_loss_group(groups, agent_id, alphas):
19 | random.seed(1)
20 | random.shuffle(groups)
21 |
22 | keys = groups[agent_id % len(groups)]
23 |
24 | random.seed(time.time())
25 | alpha = random.choice(alphas)
26 | random.seed(1)
27 | return keys, alpha
28 |
29 |
30 | def push_checkpoint_metric(key, val):
31 | val = float(val)
32 | checkpt_metric = torch.FloatTensor([val])
33 | result = {key: checkpt_metric}
34 | return result
35 |
36 |
37 | def avg_losses_cpu(outputs):
38 | outputs = ld2dl(outputs)
39 | for key, val in outputs.items():
40 | val = [v.cpu() for v in val]
41 | val = torch.cat(val, dim=0).view(-1)
42 | outputs[key] = val.mean()
43 | return outputs
44 |
45 |
46 | def reform_outputs(out_list):
47 | out_list_dict = ld2dl(out_list)
48 | outputs = ld2dl(out_list_dict["out_dict"])
49 | losses = ld2dl(out_list_dict["loss"])
50 |
51 | for k, tensor in outputs.items():
52 | if isinstance(tensor[0], list):
53 | outputs[k] = sum(tensor, [])
54 | else:
55 | outputs[k] = torch.cat(tensor)
56 |
57 | for k, tensor in losses.items():
58 | tensor = [ten.view(-1) for ten in tensor]
59 | losses[k] = torch.cat(tensor)
60 |
61 | outputs = {k: thing.thing2np(v) for k, v in outputs.items()}
62 | loss_dict = {k: v.mean().item() for k, v in losses.items()}
63 | return outputs, loss_dict
64 |
--------------------------------------------------------------------------------
/common/rend_utils.py:
--------------------------------------------------------------------------------
1 | import copy
2 | import os
3 |
4 | import numpy as np
5 | import pyrender
6 | import trimesh
7 |
8 | # offline rendering
9 | os.environ["PYOPENGL_PLATFORM"] = "egl"
10 |
11 |
12 | def flip_meshes(meshes):
13 | rot = trimesh.transformations.rotation_matrix(np.radians(180), [1, 0, 0])
14 | for mesh in meshes:
15 | mesh.apply_transform(rot)
16 | return meshes
17 |
18 |
19 | def color2material(mesh_color: list):
20 | material = pyrender.MetallicRoughnessMaterial(
21 | metallicFactor=0.1,
22 | alphaMode="OPAQUE",
23 | baseColorFactor=(
24 | mesh_color[0] / 255.0,
25 | mesh_color[1] / 255.0,
26 | mesh_color[2] / 255.0,
27 | 0.5,
28 | ),
29 | )
30 | return material
31 |
32 |
33 | class Renderer:
34 | def __init__(self, img_res: int) -> None:
35 | self.renderer = pyrender.OffscreenRenderer(
36 | viewport_width=img_res, viewport_height=img_res, point_size=1.0
37 | )
38 |
39 | self.img_res = img_res
40 |
41 | def render_meshes_pose(
42 | self,
43 | meshes,
44 | image=None,
45 | cam_transl=None,
46 | cam_center=None,
47 | K=None,
48 | materials=None,
49 | sideview_angle=None,
50 | ):
51 | # unpack
52 | if cam_transl is not None:
53 | cam_trans = np.copy(cam_transl)
54 | cam_trans[0] *= -1.0
55 | else:
56 | cam_trans = None
57 | meshes = copy.deepcopy(meshes)
58 | meshes = flip_meshes(meshes)
59 |
60 | if sideview_angle is not None:
61 | # center around the final mesh
62 | anchor_mesh = meshes[-1]
63 | center = anchor_mesh.vertices.mean(axis=0)
64 |
65 | rot = trimesh.transformations.rotation_matrix(
66 | np.radians(sideview_angle), [0, 1, 0]
67 | )
68 | out_meshes = []
69 | for mesh in copy.deepcopy(meshes):
70 | mesh.vertices -= center
71 | mesh.apply_transform(rot)
72 | mesh.vertices += center
73 | # further away to see more
74 | mesh.vertices += np.array([0, 0, -0.10])
75 | out_meshes.append(mesh)
76 | meshes = out_meshes
77 |
78 | # setting up
79 | self.create_scene()
80 | self.setup_light()
81 | self.position_camera(cam_trans, K)
82 | if materials is not None:
83 | meshes = [
84 | pyrender.Mesh.from_trimesh(mesh, material=material)
85 | for mesh, material in zip(meshes, materials)
86 | ]
87 | else:
88 | meshes = [pyrender.Mesh.from_trimesh(mesh) for mesh in meshes]
89 |
90 | for mesh in meshes:
91 | self.scene.add(mesh)
92 |
93 | color, valid_mask = self.render_rgb()
94 | if image is None:
95 | output_img = color[:, :, :3]
96 | else:
97 | output_img = self.overlay_image(color, valid_mask, image)
98 | rend_img = (output_img * 255).astype(np.uint8)
99 | return rend_img
100 |
101 | def render_rgb(self):
102 | color, rend_depth = self.renderer.render(
103 | self.scene, flags=pyrender.RenderFlags.RGBA
104 | )
105 | color = color.astype(np.float32) / 255.0
106 | valid_mask = (rend_depth > 0)[:, :, None]
107 | return color, valid_mask
108 |
109 | def overlay_image(self, color, valid_mask, image):
110 | output_img = color[:, :, :3] * valid_mask + (1 - valid_mask) * image
111 | return output_img
112 |
113 | def position_camera(self, cam_transl, K):
114 | camera_pose = np.eye(4)
115 | if cam_transl is not None:
116 | camera_pose[:3, 3] = cam_transl
117 |
118 | fx = K[0, 0]
119 | fy = K[1, 1]
120 | cx = K[0, 2]
121 | cy = K[1, 2]
122 | camera = pyrender.IntrinsicsCamera(fx=fx, fy=fy, cx=cx, cy=cy)
123 | self.scene.add(camera, pose=camera_pose)
124 |
125 | def setup_light(self):
126 | light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=1)
127 | light_pose = np.eye(4)
128 |
129 | light_pose[:3, 3] = np.array([0, -1, 1])
130 | self.scene.add(light, pose=light_pose)
131 |
132 | light_pose[:3, 3] = np.array([0, 1, 1])
133 | self.scene.add(light, pose=light_pose)
134 |
135 | light_pose[:3, 3] = np.array([1, 1, 2])
136 | self.scene.add(light, pose=light_pose)
137 |
138 | def create_scene(self):
139 | self.scene = pyrender.Scene(ambient_light=(0.5, 0.5, 0.5))
140 |
--------------------------------------------------------------------------------
/common/sys_utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path as op
3 | import shutil
4 | from glob import glob
5 |
6 | from loguru import logger
7 |
8 |
9 | def copy(src, dst):
10 | if os.path.islink(src):
11 | linkto = os.readlink(src)
12 | os.symlink(linkto, dst)
13 | else:
14 | if os.path.isdir(src):
15 | shutil.copytree(src, dst)
16 | else:
17 | shutil.copy(src, dst)
18 |
19 |
20 | def copy_repo(src_files, dst_folder, filter_keywords):
21 | src_files = [
22 | f for f in src_files if not any(keyword in f for keyword in filter_keywords)
23 | ]
24 | dst_files = [op.join(dst_folder, op.basename(f)) for f in src_files]
25 | for src_f, dst_f in zip(src_files, dst_files):
26 | logger.info(f"FROM: {src_f}\nTO:{dst_f}")
27 | copy(src_f, dst_f)
28 |
29 |
30 | def mkdir(directory):
31 | if not os.path.exists(directory):
32 | os.makedirs(directory)
33 |
34 |
35 | def mkdir_p(exp_path):
36 | os.makedirs(exp_path, exist_ok=True)
37 |
38 |
39 | def count_files(path):
40 | """
41 | Non-recursively count number of files in a folder.
42 | """
43 | files = glob(path)
44 | return len(files)
45 |
--------------------------------------------------------------------------------
/common/thing.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import torch
3 |
4 | """
5 | This file stores functions for conversion between numpy and torch, torch, list, etc.
6 | Also deal with general operations such as to(dev), detach, etc.
7 | """
8 |
9 |
10 | def thing2list(thing):
11 | if isinstance(thing, torch.Tensor):
12 | return thing.tolist()
13 | if isinstance(thing, np.ndarray):
14 | return thing.tolist()
15 | if isinstance(thing, dict):
16 | return {k: thing2list(v) for k, v in md.items()}
17 | if isinstance(thing, list):
18 | return [thing2list(ten) for ten in thing]
19 | return thing
20 |
21 |
22 | def thing2dev(thing, dev):
23 | if hasattr(thing, "to"):
24 | thing = thing.to(dev)
25 | return thing
26 | if isinstance(thing, list):
27 | return [thing2dev(ten, dev) for ten in thing]
28 | if isinstance(thing, tuple):
29 | return tuple(thing2dev(list(thing), dev))
30 | if isinstance(thing, dict):
31 | return {k: thing2dev(v, dev) for k, v in thing.items()}
32 | if isinstance(thing, torch.Tensor):
33 | return thing.to(dev)
34 | return thing
35 |
36 |
37 | def thing2np(thing):
38 | if isinstance(thing, list):
39 | return np.array(thing)
40 | if isinstance(thing, torch.Tensor):
41 | return thing.cpu().detach().numpy()
42 | if isinstance(thing, dict):
43 | return {k: thing2np(v) for k, v in thing.items()}
44 | return thing
45 |
46 |
47 | def thing2torch(thing):
48 | if isinstance(thing, list):
49 | return torch.tensor(np.array(thing))
50 | if isinstance(thing, np.ndarray):
51 | return torch.from_numpy(thing)
52 | if isinstance(thing, dict):
53 | return {k: thing2torch(v) for k, v in thing.items()}
54 | return thing
55 |
56 |
57 | def detach_thing(thing):
58 | if isinstance(thing, torch.Tensor):
59 | return thing.cpu().detach()
60 | if isinstance(thing, list):
61 | return [detach_thing(ten) for ten in thing]
62 | if isinstance(thing, tuple):
63 | return tuple(detach_thing(list(thing)))
64 | if isinstance(thing, dict):
65 | return {k: detach_thing(v) for k, v in thing.items()}
66 | return thing
67 |
--------------------------------------------------------------------------------
/common/vis_utils.py:
--------------------------------------------------------------------------------
1 | import matplotlib.cm as cm
2 | import matplotlib.pyplot as plt
3 | import numpy as np
4 | from PIL import Image
5 |
6 | # connection between the 8 points of 3d bbox
7 | BONES_3D_BBOX = [
8 | (0, 1),
9 | (1, 2),
10 | (2, 3),
11 | (3, 0),
12 | (0, 4),
13 | (1, 5),
14 | (2, 6),
15 | (3, 7),
16 | (4, 5),
17 | (5, 6),
18 | (6, 7),
19 | (7, 4),
20 | ]
21 |
22 |
23 | def plot_2d_bbox(bbox_2d, bones, color, ax):
24 | if ax is None:
25 | axx = plt
26 | else:
27 | axx = ax
28 | colors = cm.rainbow(np.linspace(0, 1, len(bbox_2d)))
29 | for pt, c in zip(bbox_2d, colors):
30 | axx.scatter(pt[0], pt[1], color=c, s=50)
31 |
32 | if bones is None:
33 | bones = BONES_3D_BBOX
34 | for bone in bones:
35 | sidx, eidx = bone
36 | # bottom of bbox is white
37 | if min(sidx, eidx) >= 4:
38 | color = "w"
39 | axx.plot(
40 | [bbox_2d[sidx][0], bbox_2d[eidx][0]],
41 | [bbox_2d[sidx][1], bbox_2d[eidx][1]],
42 | color,
43 | )
44 | return axx
45 |
46 |
47 | # http://www.icare.univ-lille1.fr/tutorials/convert_a_matplotlib_figure
48 | def fig2data(fig):
49 | """
50 | @brief Convert a Matplotlib figure to a 4D
51 | numpy array with RGBA channels and return it
52 | @param fig a matplotlib figure
53 | @return a numpy 3D array of RGBA values
54 | """
55 | # draw the renderer
56 | fig.canvas.draw()
57 |
58 | # Get the RGBA buffer from the figure
59 | w, h = fig.canvas.get_width_height()
60 | buf = np.frombuffer(fig.canvas.tostring_argb(), dtype=np.uint8)
61 | buf.shape = (w, h, 4)
62 |
63 | # canvas.tostring_argb give pixmap in ARGB mode.
64 | # Roll the ALPHA channel to have it in RGBA mode
65 | buf = np.roll(buf, 3, axis=2)
66 | return buf
67 |
68 |
69 | # http://www.icare.univ-lille1.fr/tutorials/convert_a_matplotlib_figure
70 | def fig2img(fig):
71 | """
72 | @brief Convert a Matplotlib figure to a PIL Image
73 | in RGBA format and return it
74 | @param fig a matplotlib figure
75 | @return a Python Imaging Library ( PIL ) image
76 | """
77 | # put the figure pixmap into a numpy array
78 | buf = fig2data(fig)
79 | w, h, _ = buf.shape
80 | return Image.frombytes("RGBA", (w, h), buf.tobytes())
81 |
82 |
83 | def concat_pil_images(images):
84 | """
85 | Put a list of PIL images next to each other
86 | """
87 | assert isinstance(images, list)
88 | widths, heights = zip(*(i.size for i in images))
89 |
90 | total_width = sum(widths)
91 | max_height = max(heights)
92 |
93 | new_im = Image.new("RGB", (total_width, max_height))
94 |
95 | x_offset = 0
96 | for im in images:
97 | new_im.paste(im, (x_offset, 0))
98 | x_offset += im.size[0]
99 | return new_im
100 |
101 |
102 | def stack_pil_images(images):
103 | """
104 | Stack a list of PIL images next to each other
105 | """
106 | assert isinstance(images, list)
107 | widths, heights = zip(*(i.size for i in images))
108 |
109 | total_height = sum(heights)
110 | max_width = max(widths)
111 |
112 | new_im = Image.new("RGB", (max_width, total_height))
113 |
114 | y_offset = 0
115 | for im in images:
116 | new_im.paste(im, (0, y_offset))
117 | y_offset += im.size[1]
118 | return new_im
119 |
120 |
121 | def im_list_to_plt(image_list, figsize, title_list=None):
122 | fig, axes = plt.subplots(nrows=1, ncols=len(image_list), figsize=figsize)
123 | for idx, (ax, im) in enumerate(zip(axes, image_list)):
124 | ax.imshow(im)
125 | ax.set_title(title_list[idx])
126 | fig.tight_layout()
127 | im = fig2img(fig)
128 | plt.close()
129 | return im
130 |
--------------------------------------------------------------------------------
/docs/data/mano_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zc-alexfan/arctic/e904c9695911d0e4a2025ebaeff1d91e5caf4d69/docs/data/mano_right.png
--------------------------------------------------------------------------------
/docs/data/processing.md:
--------------------------------------------------------------------------------
1 | # Data processing & splits
2 |
3 | ## Data splits
4 |
5 | **CVPR paper splits**
6 |
7 | - protocol 1: allocentric split (test set GT is hidden)
8 | - protocol 2: egocentric split (test set GT is hidden)
9 |
10 | Note, allocentric training images in protocol 1 can be used for protocol 2 training as per our evaluation protocol in the paper. In our paper, we first pre-train on protocol 1 images and finetune on protocol 2 for the egocentric regressor. If one wants to directly train on allocentric and egocentric training images for protocol 2 evaluation, she can create a custom split.
11 |
12 | See [`docs/data_doc.md`](../data_doc.md) for an explanation of each file in the `arctic_data` folder.
13 |
14 | ## Advanced usage
15 |
16 | ### Process raw sequences
17 |
18 | ```bash
19 | # process a specific seq; do not save vertices for smaller storage
20 | python scripts_data/process_seqs.py --mano_p ./unpack/arctic_data/data/raw_seqs/s01/espressomachine_use_01.mano.npy
21 |
22 | # process all seqs; do not save vertices for smaller storage
23 | python scripts_data/process_seqs.py
24 |
25 | # process all seqs while exporting the vertices for visualization
26 | python scripts_data/process_seqs.py --export_verts
27 | ```
28 |
29 | ### Create data split from processed sequences
30 |
31 | Our baseline load the pre-processed split from `data/arctic_data/data/splits`. In case you need a custom split, you can build a data split from the example below (here we show validation set split), which generates the split files under `outputs/splits/`
32 |
33 | Build a data split from processed sequence:
34 |
35 | ```bash
36 | # Build validation set based on protocol p1 defined at arctic_data/data/splits_json/protocol_p1.json
37 | python scripts_data/build_splits.py --protocol p1 --split val --process_folder ./outputs/processed/seqs
38 |
39 | # Same as above, but build with vertices too
40 | python scripts_data/build_splits.py --protocol p1 --split val --process_folder ./outputs/processed_verts/seqs
41 | ```
42 |
43 | ⚠️ The dataloader for our models in our CVPR paper does not require vertices in the split files. If the processed sequences are built with `--export_verts`, this script will try to aggregate the vertices as well, leading to large storage requirement.
44 |
45 | ### Crop images for faster data loading
46 |
47 | Since our images are of high resolution, if reading speed is a limitation for your machine for training models, one can consider cropping the images around a larger region centered at the bounding boxes to reduce data loading requirement in training. We have provided data link for pre-cropped images. In case of a custom crop, one can use the script below:
48 |
49 | ```bash
50 | # crop all images from all sequences using bbox defined in the process folder on a single machine
51 | python scripts_data/crop_images.py --task_id -1 --process_folder ./outputs/processed/seqs
52 |
53 | # crop all images from one sequence using bbox defined in the process folder
54 | # this is used for cluster preprocessing where AGENT_ID is from 0 to num_nodes-1
55 | python scripts_data/crop_images.py --task_id AGENT_ID --process_folder ./outputs/processed/seqs
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/data/visualize.md:
--------------------------------------------------------------------------------
1 | # AIT Viewer with ARCTIC
2 |
3 | Our visualization is powered by:
4 |
5 |
6 |
7 | ## Examples
8 |
9 | ```bash
10 | # render object and MANO for a given sequence
11 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano
12 |
13 | # render object and MANO for a given sequence on view 2
14 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano --view_idx 2
15 |
16 | # render object and MANO for a given sequence on egocentric view
17 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano --view_idx 0
18 |
19 | # render object and MANO for a given sequence on egocentric view while taking lens distortion into account
20 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano --view_idx 0 --distort
21 |
22 | # render in headless mode to obtain RGB images (with meshes), depth, segmentation masks, and mp4 video of the visualization
23 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano --headless
24 |
25 | # render object and SMPLX for a given sequence without images
26 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --smplx --no_image
27 |
28 | # render all sequences into videos, RGB images with meshes, depth maps, and segmentation masks
29 | python scripts_data/visualizer.py --object --smplx --headless
30 |
31 | # visualize raw mocap data
32 | python scripts_data/mocap_viewer.py --mocap_p unpack/arctic_data/data/mocap_npy/s01_ketchup_use_01.npy
33 | ```
34 |
35 | ## Options
36 |
37 | - `view_idx`: camera view to visualize; `0` is for egocentric view; `{1, .., 8}` are for 3rd-person views.
38 | - `seq_p`: path to processed sequence to visualize. When this option is not specified, the program will run on all sequences (e.g., when you want to render depth masks for all sequences).
39 | - `headless`: when it is off, user will be have an interactive mode; when it is on, we render and save images with GT, depth maps, segmentation masks, and videos to disks.
40 | - `mano`: include MANO in the scene
41 | - `smplx`: include SMPLX in the scene
42 | - `object`: include object in the scene
43 | - `no_image`: do not show images.
44 | - `distort`: in egocentric view, lens distortion is servere as the camera is close to the 3D objects, leading to mismatch in 3D geometry and the images. When turned on, this option makes use of the lens distortion parameters for better GT-image overlaps by simulating the distortion effect using ["vertex displacement for distortion correction"](https://stackoverflow.com/questions/44489686/camera-lens-distortion-in-opengl). It uses the distortion parameters to distort the 3D geometry so that it has better 3D overlaps with the images. However, such a method creates artifacts when the 3D geometry is close to the camera.
45 |
46 | Segmentation mask IDs in the scene are defined [here](https://github.com/zc-alexfan/arctic-private/blob/arctic/common/viewer.py#L24).
47 |
48 | ## Controls to interact with the viewer
49 |
50 | [AITViewer](https://github.com/eth-ait/aitviewer) has lots of useful builtin controls. For an explanation of the frontend and control, visit [here](https://eth-ait.github.io/aitviewer/frontend.html). Here we assume you are in interactive mode (`--headless` is turned off).
51 |
52 | - To play/pause the animation, hit ``.
53 | - To center around an object, click the mesh you want to center, press `X`.
54 | - To go between the previous and the current frame, press `<` and `>`.
55 |
56 | More documentation can be found in [aitviewer github](https://github.com/eth-ait/aitviewer) and in [viewer docs](https://eth-ait.github.io/aitviewer/frontend.html).
57 |
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | QUESTION: **Why do groundtruth hands not have complete overlap with the image in the visualization (see below) via `scripts_method/train.py`?**
4 | ANSWER: Like most hand-object reconstruction methods, ArcticNet does not assume camera intrinsics and we use a weak perspective camera model by assuming a fixed focal length. The mismatch of 2D alignment between the groundtruth and the image is caused by the weak perspective camera intrinsics.
5 |
6 |
7 |
8 |
9 |
10 |
11 | QUESTION: **Why is there more misalignment in egocentric view?**
12 | ANSWER: Mainly caused by image distortion in the rendering as the 3D geometry is close to the camera. We estimated distortion parameters from camera calibration sequences, which can be used to apply distortion effect on the meshes with ["vertex displacement for distortion correction"](https://stackoverflow.com/questions/44489686/camera-lens-distortion-in-opengl).
13 |
14 | See the `--distort` flag for details:
15 |
16 | ```python
17 | python scripts_data/visualizer.py --seq_p ./outputs/processed_verts/seqs/s01/capsulemachine_use_01.npy --object --mano --view_idx 0 --distort
18 | ```
19 |
20 | QUESTION: **Assertion error related to 21 joints**
21 | ANSWER: This is because smplx gives 16 joints for the MANO hand by default. See [setup instruction](setup.md) to allow 21 joints.
22 |
23 |
24 | QUESTION: **How's 2D space, 3D camera space, 3D world space are related? How about cropping?**
25 | ANSWER: See [here](https://github.com/zc-alexfan/arctic/issues/29#issuecomment-1751657365)
26 |
--------------------------------------------------------------------------------
/docs/leaderboard.md:
--------------------------------------------------------------------------------
1 | # ARCTIC Leaderboard
2 |
3 | This page contains instructions to submit your results to the ARCTIC leaderboard. **The leaderboard is currently under beta release. Should you encounter any issues feel free to contact us.**
4 |
5 | ## Getting an account
6 |
7 | To get started, go to our [leaderboard website](https://arctic-leaderboard.is.tuebingen.mpg.de/). Click on the `Sign up` button at the top to register a new account. You will receive an email to confirm your account. Note that this is not the same account system as in the [ARCTIC website](https://arctic.is.tue.mpg.de/), so you have to register a separate one.
8 |
9 | > ICCV challenge participants: Use the same email you registered for the challenge to register the evaluation account. Further, your "Algorithm Name" should be your team name.
10 |
11 | After activating your account, you may now log into the website.
12 |
13 | ## Creating an algorithm
14 |
15 |
16 | After logging in, click "My algorithms" at the top to manage your algorithms. To add one, click "Add algorithm" and enter your algorithm details, with only the "short name" field being mandatory. This information will appear on the leaderboard if published. Algorithm scores remain private by default unless published. Click save to create the algorithm.
17 |
18 | **IMPORTANT: When an algorithm is created, you can submit on multiple sub-tasks. You do not need to create a separate algorithm for each sub-task**
19 |
20 | ## Submitting to leaderboard
21 |
22 | After the step above, you'll reach a page to upload prediction results. Initially, use our provided CVPR model zip files below for a trial evaluation based on your chosen task. Post trial, you can submit your own zip files. We recommend starting with egocentric tasks due to their smaller file sizes:
23 |
24 | - [Consistent motion reconstruction: allocentric](https://download.is.tue.mpg.de/arctic/submission/pose_p1_test.zip)
25 | - [Consistent motion reconstruction: egocentric](https://download.is.tue.mpg.de/arctic/submission/pose_p2_test.zip)
26 | - [Interaction field estimation: allocentric](https://download.is.tue.mpg.de/arctic/submission/field_p1_test.zip)
27 | - [Interaction field estimation: egocentric](https://download.is.tue.mpg.de/arctic/submission/field_p2_test.zip)
28 |
29 |
30 |
31 | Click "Upload" and select the relevant task to upload your zip file for evaluation. The evaluation time may vary based on the task and number of requests. You'll see the results in a table, which can be downloaded as a JSON file by clicking "evaluation result".
32 |
33 | Your numbers should closely align with our CVPR models, serving as a sanity check for the file format. Results remain private unless you select "publish", allowing evaluation against the test set ground truth.
34 |
35 | To generate zip files for evaluation, create a custom script using the provided zip files as a template. If using our original codebase, utilize the extraction scripts below to create the zip files. Find detailed data format documentation for the leaderboard [here](leaderboard_format.md).
36 |
37 | To avoid excessive hyperparameter tuning on the test set, each account can only submit to the server for **10 successful evaluations in total every month**.
38 |
39 | ## Preparing submission file with original codebase
40 |
41 | We demonstrate preparing submission files using ARCTIC models as an example. First, run inference on each sequence and save the model predictions to disk. These predictions are then compiled into a zip file for submission.
42 |
43 | > If you're using a different codebase, and prefer to write your own script for generating the zip files, you can inspect the example zip files above.
44 |
45 | To submit predictions, we need to use the extraction script `scripts_method/extract_predicts.py`. Detailed documentation on the extraction script is at [here](model/extraction.md).
46 |
47 | To perform a trial submission, you can try to reproduce numbers on our model `28bf3642f`. It is a ArcticNet-SF model for the egocentric setting in our CVPR paper. See details on the [data documentation](data/data_doc.md) page.
48 |
49 | If you have prepared the arctic data following our standard instructions [here](data/README.md), you can copy the pre-trained model `28bf3642f` via:
50 |
51 | ```bash
52 | cp -r data/arctic_data/models/28bf3642f logs/
53 | ```
54 |
55 | Then run this command to perform inference on the test set:
56 |
57 | ```bash
58 | python scripts_method/extract_predicts.py --setup p2 --method arctic_sf --load_ckpt logs/28bf3642f/checkpoints/last.ckpt --run_on test --extraction_mode submit_pose
59 | ```
60 |
61 | A zip file will be produced, which can be used to upload to the evaluation server.
62 |
63 | Explanation on the options above:
64 |
65 | - `--setup`: allocentric setting (`p1`) or egocentric setting (`p2`) to run on.
66 | - `--method`: the model to construct
67 | - `--load_ckpt`: path to model checkpoint
68 | - `--run_on`: test set evaluation
69 | - `--extraction_mode {submit_pose, submit_field}`: this specifies the extraction is for submission
70 |
--------------------------------------------------------------------------------
/docs/leaderboard_format.md:
--------------------------------------------------------------------------------
1 | # Submission format
2 |
3 | ## Consistent motion reconstruction
4 |
5 | ### File structure
6 |
7 | To submit for evaluation, you need to prepare prediction files and store them in a folder (here the folder is `pose_p2_test`), and zip the folder for submission. The following shows the tree structure of a folder before zipping to `pose_p2_test.zip`.
8 |
9 | The folder contains a single subfolder named `eval`. We refer `pose_p2_test` as the `TASK_NAME` to indicate different tasks to evaluate your submission on. The `$TASK_NAME/eval` folder then stores prediction from each sequence in a particular view.
10 |
11 | ```
12 | pose_p2_test
13 | -- eval
14 | |-- s03_box_grab_01_0
15 | | |-- meta_info
16 | | | `-- meta_info.imgname.pt
17 | | `-- preds
18 | | |-- pred.mano.beta.l.pt
19 | | |-- pred.mano.beta.r.pt
20 | | |-- pred.mano.cam_t.l.pt
21 | | |-- pred.mano.cam_t.r.pt
22 | | |-- pred.mano.pose.l.pt
23 | | |-- pred.mano.pose.r.pt
24 | | |-- pred.object.cam_t.pt
25 | | |-- pred.object.radian.pt
26 | | `-- pred.object.rot.pt
27 | |-- s03_box_use_01_0
28 | | |-- meta_info
29 | | | `-- meta_info.imgname.pt
30 | | `-- preds
31 | | |-- pred.mano.beta.l.pt
32 | | |-- pred.mano.beta.r.pt
33 | | |-- pred.mano.cam_t.l.pt
34 | | |-- pred.mano.cam_t.r.pt
35 | | |-- pred.mano.pose.l.pt
36 | | |-- pred.mano.pose.r.pt
37 | | |-- pred.object.cam_t.pt
38 | | |-- pred.object.radian.pt
39 | | `-- pred.object.rot.pt
40 | ...
41 | ```
42 |
43 | Lets take `pose_p2_test/eval/s03_box_use_01_0` as an example. The `TASK_NAME` is `pose_p2_test` and `s03_box_use_01_0` means that the folder is for predictions of the sequence `s03_box_use_01` in camera view `0`. Since this is an egocentric task, you will expect the view is always 0, but for allocentric tasks it will range from 1 to 8.
44 |
45 | You will use one of the following `TASK_NAME`:
46 | - `pose_p1_test`: motion reconstruction task, allocentric setting evaluation on the test set
47 | - `pose_p2_test`: motion reconstruction task, egocentric setting evaluation on the test set
48 | - `field_p1_test`: interaction field estimation task, allocentric setting evaluation on the test set
49 | - `field_p2_test`: interaction field estimation task, egocentric setting evaluation on the test set
50 |
51 | Say you want to store your prediction on the motion reconstruction task in allocentric camera setting on the test set for camera 2 and the sequence `s03_capsulemachine_use_04`. The folder to store the prediction will be `pose_p1_test/eval/s03_capsulemachine_use_04_2`.
52 |
53 | ### File formats
54 |
55 | Looking at the tree structure above, you can see that there are two folders `meta_info` and `preds`. The former stores information that is not prediction. In this case, it is only the image paths. The latter folder stores the predictions of the MANO model and the object model. Each `.pt` file is from `torch.save`.
56 |
57 | - `pred.mano.beta.l.pt`: (num_frames, 10); MANO betas for left hand for each frame; FloatTensor
58 | - `pred.mano.cam_t.l.pt`: (num_frames, 3); MANO [translation](https://github.com/zc-alexfan/arctic/blob/08c5e9396087c4529b448cdf736b65fae600866e/src/nets/hand_heads/mano_head.py#L51) for left hand; FloatTensor
59 | - `pred.mano.pose.l.pt`: (num_frames, 16, 3, 3); MANO hand rotations for left hand; FloatTensor; assume `flat_hand_mean=False`; this includes the global orientation; rotation matrix format.
60 | - `pred.object.cam_t.pt`: (num_frames, 3); Object [translation](https://github.com/zc-alexfan/arctic/blob/08c5e9396087c4529b448cdf736b65fae600866e/src/nets/obj_heads/obj_head.py#L60C27-L60C32); FloatTensor
61 | - `pred.object.radian.pt`: (num_frames); Object articulation radian.
62 | - `pred.object.rot.pt`: (num_frames, 3); Object orientation in axis-angle; FloatTensor
63 | - `meta_info.imgname.pt`: (num_frames); A list of strings for image paths
64 |
65 | Example of the first image path:
66 |
67 | ```
68 | './data/arctic_data/data/cropped_images/s03/box_use_01/0/00010.jpg'
69 | ```
70 |
71 | You can also refer to our hand and object model classes for a reference of these variables.
72 |
73 |
--------------------------------------------------------------------------------
/docs/model/extraction.md:
--------------------------------------------------------------------------------
1 |
2 | # Extraction
3 |
4 | To run our training (for LSTM models), evaluation, and visualization pipelines, we need to save certain predictions to disk in advance. Here we detail the extraction script options.
5 |
6 | ## Script options
7 |
8 | Options:
9 | - `--setup`: the split to use; `{p1, p2}`
10 | - `--method`: model name; `{arctic_sf, arctic_lstm, field_sf, field_lstm}`
11 | - `--load_ckpt`: checkpoint path
12 | - `--run_on`: split to extract prediction on; `{train, val, test}`
13 | - `--extraction_mode`: this defines what predicted variables to extract
14 |
15 | Explanation of `setup`:
16 | - `p1`: allocentric split in our CVPR paper
17 | - `p2`: egocentric split in our CVPR paper
18 |
19 | Explanation of `--extraction_mode`:
20 | - `eval_pose`: dump predicted variables that are related for evaluating pose reconstruction. The evaluation will be done locally (assume GT is provided).
21 | - `eval_field`: dump predicted variables that are related for evaluating interaction field estimation. The evaluation will be done locally (assume GT is provided).
22 | - `submit_pose`: dump predicted variables that are related for evaluating pose reconstruction. The evaluation will be done via a submission server for test set evaluation.
23 | - `submit_field`: dump predicted variables that are related for evaluating interaction field estimation. The evaluation will be done via a submission serverfor test set evaluation.
24 | - `feat_pose`: extract image feature vectors for pose estimation (e.g., these features are inputs of the LSTM model to avoid a backbone in the training process for speedup).
25 | - `feat_field`: extract image feature vectors for interaction field estimation
26 | - `vis_pose`: extract prediction for visualizing pose prediction in our viewer.
27 | - `vis_field`: extract prediction for visualizing interaction field prediction in our viewer.
28 |
29 | ## Extraction examples
30 |
31 | Here we show extraction examples using our pre-trained models. To start, copy our pre-trained models to `./logs`:
32 |
33 | ```bash
34 | mkdir -p logs
35 | cp -r data/arctic_data/models/* logs/
36 | ```
37 |
38 | **Example**: Suppose that I want to:
39 | - evaluate the *ArcticNet-SF* pose estimation model (`3558f1342`)
40 | - run on the *val* set
41 | - use the split `p1` to evaluate locally (therefore, `eval_pose`)
42 | - use the checkpoint at `logs/3558f1342/checkpoints/last.ckpt`
43 |
44 | ```bash
45 | python scripts_method/extract_predicts.py --setup p1 --method arctic_sf --load_ckpt logs/3558f1342/checkpoints/last.ckpt --run_on val --extraction_mode eval_pose
46 | ```
47 |
48 | **Example**: Suppose that I want to:
49 | - evaluate the *ArcticNet-SF* pose estimation model (`3558f1342`)
50 | - run on the *test* set
51 | - use the CVPR split `p1` to evaluate so that we submit to the evaluation server later (therefore, `submit_pose`)
52 | - use the checkpoint at `logs/3558f1342/checkpoints/last.ckpt`
53 |
54 | ```bash
55 | python scripts_method/extract_predicts.py --setup p1 --method arctic_sf --load_ckpt logs/3558f1342/checkpoints/last.ckpt --run_on test --extraction_mode submit_pose
56 | ```
57 |
58 | **Example**: Suppose that I want to:
59 | - visualize the prediction of the *ArcticNet-SF* pose estimation model (`3558f1342`); therefore, `vis_pose`
60 | - run on the *val* set
61 | - use the split `p1` to evaluate
62 | - use the checkpoint at `logs/3558f1342/checkpoints/last.ckpt`
63 |
64 | ```bash
65 | python scripts_method/extract_predicts.py --setup p1 --method arctic_sf --load_ckpt logs/3558f1342/checkpoints/last.ckpt --run_on val --extraction_mode vis_pose
66 | ```
67 |
68 | **Example**: Suppose that I want to:
69 | - Extract images features of the *ArcticNet-LSTM* pose estimation model (`3558f1342`) on training and val sets.
70 | - use the split `p1`
71 | - we need to first save the visual features of *ArcticNet-SF* model to disks; Therefore, `feat_pose`
72 |
73 | ```bash
74 | # extract for training
75 | python scripts_method/extract_predicts.py --setup p1 --method arctic_sf --load_ckpt logs/3558f1342/checkpoints/last.ckpt --run_on train --extraction_mode feat_pose
76 |
77 | # extract for evaluation on val set
78 | python scripts_method/extract_predicts.py --setup p1 --method arctic_sf --load_ckpt logs/3558f1342/checkpoints/last.ckpt --run_on val --extraction_mode feat_pose
79 | ```
80 |
81 |
--------------------------------------------------------------------------------
/docs/purchase.md:
--------------------------------------------------------------------------------
1 | # Guide to purchase ARCTIC objects
2 |
3 | Here are a list of links that I used to purchase the ARCTIC objects:
4 |
5 | - [Small Foot Toy Kitchen Set](https://www.amazon.de/-/en/dp/B0756C59FR?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1)
6 | - [Titanium Scissors, Non-Stick, 205 mm, SB, Black](https://www.amazon.de/-/en/dp/B00P1F7QVU?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1)
7 | - [Xucker Tomato Ketchup with Xylitol, No Added Sugar: 1 x 500 ml - GMO Free, Vegan](https://www.amazon.de/dp/B07YQ987P4?ref=ppx_yo2ov_dt_b_fed_asin_title)
8 | - [Small Foot Toy Kitchen Set](https://www.amazon.de/dp/B08HK7ZDYL?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1)
9 | - [Autorenplaner | Buch schreiben und veröffentlichen | Handbuch für Autoren & Schriftsteller | Buch schreiben lernen | mit vielen Tipps & Checklisten | für Anfänger geeignet: Der All-in-one Planer](https://www.amazon.de/dp/3966985934?ref=ppx_yo2ov_dt_b_fed_asin_title)
10 | - [Eichhorn 100002575 Wooden Laptop with Puzzle, 14 Pieces, Screen Surface for Writing on with Chalk, Keyboard Consisting of 6 Puzzle Pieces, 32 x 20 cm, Includes 6 Chalks and Sponge](https://www.amazon.de/dp/B00BLEG3SW?ref=ppx_yo2ov_dt_b_fed_asin_title)
11 | - [Creative Deco A4 Wooden Box with Lid | 33.8 x 24.8 x 10 cm (+/- 1 cm) | Unfinished Storage Box | Large Box | Large Wooden Box Ideal for Storing Valuables, Toys and Tools](https://www.amazon.de/dp/B075X4YHZB?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1)
12 | - [Baby Lips Balm Crayon](https://www.amazon.de/dp/B006PG68EU?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1)
13 |
14 | Unfortunatley, links for other objects do not work anymore. However, you can find the stock photos of the objects [here](stock_photos/). Using these photos, you can then use Google Image Search to find other vendors selling the same items. For example,
15 |
16 |