├── screenshot.png ├── requirements.txt ├── README.md └── main.py /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShenhanQian/nersemble-data-viewer/HEAD/screenshot.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dearpygui 2 | numpy 3 | pillow 4 | pandas 5 | tyro 6 | opencv-python 7 | matplotlib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeRSemble Data Viewer 2 | 3 | A GUI program to inspect images in the [NeRSemble](https://tobias-kirschstein.github.io/nersemble/) dataset. 4 | 5 | > [!NOTE] 6 | > This program has been adapted to NeRSemble Dataset V2. The folder structure is no longer compatible with NeRSemble Dataset V1. 7 | 8 |
9 | 10 |
11 | 12 | ## Installation 13 | 14 | ```shell 15 | pip install -r requirements.txt 16 | ``` 17 | 18 | ## Usage 19 | 20 | Please first extract frames from the raw videos with [this script](https://github.com/ShenhanQian/VHAP/blob/main/doc/nersemble_v2.md#1-preprocess). Then you can view them with 21 | 22 | ```shell 23 | python main.py --root_folder 24 | ``` 25 | 26 | ## Expected folder hierarchy 27 | 28 | ``` 29 | 30 | |- 017 31 | |- sequences 32 | |- EXP-1-head 33 | |- images 34 | |- cam_220700191_000000.jpg 35 | |- cam_220700191_000001.jpg 36 | |- ... 37 | ``` 38 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | import dearpygui.dearpygui as dpg 3 | from pathlib import Path 4 | import glob 5 | import numpy as np 6 | from PIL import Image 7 | import pandas as pd 8 | import tyro 9 | from dataclasses import dataclass 10 | import cv2 11 | import matplotlib.pyplot as plt 12 | 13 | 14 | region2seg_class = { 15 | 'bg': 0, 16 | 'skin': 1, 17 | 'l_brow': 2, 18 | 'r_brow': 3, 19 | 'l_eye': 4, 20 | 'r_eye': 5, 21 | 'eye_g': 6, 22 | 'l_ear': 7, 23 | 'r_ear': 8, 24 | 'ear_r': 9, 25 | 'nose': 10, 26 | 'mouth': 11, 27 | 'u_lip': 12, 28 | 'l_lip': 13, 29 | 'neck': 14, 30 | 'neck_l': 15, 31 | 'cloth': 16, 32 | 'hair': 17, 33 | 'hat': 18 34 | } 35 | 36 | 37 | @dataclass 38 | class NersembleDataViewerConfig: 39 | root_folder: Path 40 | width: int=1000 41 | height: int=1000 42 | 43 | class NersembleDataViewer(object): 44 | def __init__(self, cfg: NersembleDataViewerConfig): 45 | self.root_folder = cfg.root_folder 46 | self.width = cfg.width 47 | self.height = cfg.height 48 | 49 | self.width_nav = 390 50 | self.height_nav = 400 51 | self.need_update = False 52 | 53 | # load csv 54 | csv_path = self.root_folder / 'metadata_sequences.csv' 55 | self.data = pd.read_csv(csv_path, sep=',') 56 | 57 | self.data[self.data.columns[0]] = self.data[self.data.columns[0]].astype(str) 58 | self.data.iloc[:, 0] = self.data.iloc[:, 0].map(lambda x: f'{int(x):03d}') 59 | self.data.iloc[:, 0] 60 | self.data.set_index("ID", inplace=True) 61 | 62 | self.data = self.data.drop(self.data.columns[0:2], axis=1) # drop 'wears_glasses' and 'BACKGROUND' 63 | 64 | # init variables 65 | self.subjects = self.data.index.tolist() 66 | self.subjects = [x for x in self.subjects if len(list((self.root_folder / x).glob(f"sequences/*/images*/*.jpg"))) > 0] 67 | 68 | self.sequences = self.data.columns.tolist() 69 | self.sequences = [x for x in self.sequences if len(list((self.root_folder).glob(f"*/sequences/{x}/images*/*.jpg"))) > 0] 70 | 71 | self.reset_subject_sequence(update_items=False) 72 | 73 | def reset_subject_sequence(self, update_items=True): 74 | self.selected_subject = '-' 75 | self.selected_sequence = '-' 76 | self.available_subjects = self.subjects 77 | self.available_sequences = self.sequences 78 | 79 | if update_items: 80 | dpg.configure_item("combo_subject", items=['-'] + self.available_subjects, default_value=self.selected_subject) 81 | dpg.configure_item("combo_sequence", items=['-'] + self.available_sequences, default_value=self.selected_sequence) 82 | 83 | def reset_folder_tree(self, update_items=True): 84 | self.filetypes = [] 85 | self.cameras = [] 86 | self.timesteps = [] 87 | self.selected_filetype = '' 88 | self.selected_camera = '' 89 | self.selected_timestep = '' 90 | self.selected_timestep_idx = 0 91 | if update_items: 92 | dpg.configure_item("combo_filetype", items=self.filetypes, default_value=self.selected_filetype) 93 | dpg.configure_item("combo_camera", items=self.cameras, default_value=self.selected_camera) 94 | dpg.configure_item("slider_timestep", default_value=self.selected_timestep_idx, max_value=0, min_value=0) 95 | 96 | def define_gui(self): 97 | dpg.create_context() 98 | 99 | with dpg.texture_registry(show=False): 100 | dpg.add_raw_texture(width=self.width, height=self.height, default_value=np.zeros([self.height, self.width, 3]), format=dpg.mvFormat_Float_rgb, tag="texture_tag") 101 | 102 | # viewer window 103 | with dpg.window(label="Viewer", pos=[0, 0], tag='viewer_tag', width=self.width, height=self.height, no_title_bar=True, no_move=True, no_bring_to_front_on_focus=True): 104 | dpg.add_image("texture_tag", tag='image_tag', width=self.width, height=self.height) 105 | 106 | # navigator window 107 | with dpg.window(label="Navigator", tag='navigator_tag', width=self.width_nav, pos=[self.width-self.width_nav-15, 0], autosize=True): 108 | 109 | # subject switch 110 | with dpg.group(horizontal=True): 111 | def set_subject(sender, data): 112 | self.selected_subject = data 113 | if data == '-': 114 | self.available_sequences = self.sequences 115 | else: 116 | self.available_sequences = [x for x in self.sequences if len(list((self.root_folder).glob(f"{self.selected_subject}/sequences/{x}/images*/"))) > 0] 117 | 118 | if self.selected_sequence not in self.available_sequences: 119 | if len(self.available_sequences) > 0: 120 | self.selected_sequence = self.available_sequences[0] 121 | else: 122 | self.selected_sequence == '-' 123 | 124 | dpg.configure_item("combo_sequence", items=['-'] + self.available_sequences, default_value=self.selected_sequence) 125 | self.update_folder_tree(level='sequence') 126 | self.need_update = True 127 | dpg.add_combo(['-'] + self.available_subjects, default_value=self.selected_subject, label="subject ", height_mode=dpg.mvComboHeight_Large, callback=set_subject, tag='combo_subject') 128 | 129 | def prev_subject(sender, data): 130 | if self.selected_subject == '-': 131 | self.selected_subject = self.available_subjects[-1] 132 | else: 133 | idx = self.available_subjects.index(self.selected_subject) 134 | if idx > 0: 135 | self.selected_subject = self.available_subjects[idx-1] 136 | else: 137 | self.selected_subject = self.available_subjects[-1] 138 | dpg.set_value("combo_subject", value=self.selected_subject) 139 | set_subject(None, self.selected_subject) 140 | dpg.add_button(label="W", callback=prev_subject, width=19) 141 | 142 | def next_subject(sender, data): 143 | if self.selected_subject == '-': 144 | self.selected_subject = self.available_subjects[0] 145 | else: 146 | idx = self.available_subjects.index(self.selected_subject) 147 | if idx < len(self.available_subjects)-1: 148 | self.selected_subject = self.available_subjects[idx+1] 149 | else: 150 | self.selected_subject = self.available_subjects[0] 151 | dpg.set_value("combo_subject", value=self.selected_subject) 152 | set_subject(None, self.selected_subject) 153 | dpg.add_button(label="S", callback=next_subject, width=19) 154 | 155 | # sequence switch 156 | with dpg.group(horizontal=True): 157 | def set_sequence(sender, data): 158 | self.selected_sequence = data 159 | if data == '-': 160 | self.available_subjects = self.subjects 161 | else: 162 | self.available_subjects = [x for x in self.subjects if len(list((self.root_folder / x).glob(f"sequences/{self.selected_sequence}/images*/"))) > 0] 163 | 164 | if self.selected_subject not in self.available_subjects: 165 | if len(self.available_subjects) > 0: 166 | self.selected_subject = self.available_subjects[0] 167 | else: 168 | self.selected_subject == '-' 169 | 170 | dpg.configure_item("combo_subject", items=['-'] + self.available_subjects, default_value=self.selected_subject) 171 | self.update_folder_tree(level='filetype') 172 | self.need_update = True 173 | dpg.add_combo(['-'] + self.available_sequences, default_value=self.selected_sequence, label="sequence ", height_mode=dpg.mvComboHeight_Large, callback=set_sequence, tag='combo_sequence') 174 | 175 | def prev_sequence(sender, data): 176 | if self.selected_sequence == '-': 177 | self.selected_sequence = self.available_sequences[-1] 178 | else: 179 | idx = self.available_sequences.index(self.selected_sequence) 180 | if idx > 0: 181 | self.selected_sequence = self.available_sequences[idx-1] 182 | else: 183 | self.selected_sequence = self.available_sequences[-1] 184 | dpg.set_value("combo_sequence", value=self.selected_sequence) 185 | set_sequence(None, self.selected_sequence) 186 | dpg.add_button(label="A", callback=prev_sequence, width=19) 187 | 188 | def next_sequence(sender, data): 189 | if self.selected_sequence == '-': 190 | self.selected_sequence = self.available_sequences[0] 191 | else: 192 | idx = self.available_sequences.index(self.selected_sequence) 193 | if idx < len(self.available_sequences)-1: 194 | self.selected_sequence = self.available_sequences[idx+1] 195 | else: 196 | self.selected_sequence = self.available_sequences[0] 197 | dpg.set_value("combo_sequence", value=self.selected_sequence) 198 | set_sequence(None, self.selected_sequence) 199 | dpg.add_button(label="D", callback=next_sequence, width=19) 200 | 201 | # filetype switch 202 | def set_filetype(sender, data): 203 | self.selected_filetype = data 204 | self.update_folder_tree(level='camera') 205 | self.need_update = True 206 | dpg.add_combo([], label="file type", height_mode=dpg.mvComboHeight_Large, callback=set_filetype, tag='combo_filetype') 207 | 208 | 209 | # camera switch 210 | with dpg.group(horizontal=True): 211 | def set_camera(sender, data): 212 | self.selected_camera = data 213 | self.update_folder_tree(level='camera') 214 | self.need_update = True 215 | dpg.add_combo([], label="camera ", height_mode=dpg.mvComboHeight_Large, callback=set_camera, tag='combo_camera') 216 | 217 | def prev_camera(sender, data): 218 | if len(self.cameras) > 0: 219 | idx = self.cameras.index(self.selected_camera) 220 | if idx > 0: 221 | self.selected_camera = self.cameras[idx-1] 222 | else: 223 | self.selected_camera = self.cameras[-1] 224 | dpg.set_value("combo_camera", value=self.selected_camera) 225 | set_camera(None, self.selected_camera) 226 | dpg.add_button(label="", callback=prev_camera, arrow=True, direction=dpg.mvDir_Up) 227 | 228 | def next_camera(sender, data): 229 | if len(self.cameras) > 0: 230 | idx = self.cameras.index(self.selected_camera) 231 | if idx < len(self.cameras)-1: 232 | self.selected_camera = self.cameras[idx+1] 233 | else: 234 | self.selected_camera = self.cameras[0] 235 | dpg.set_value("combo_camera", value=self.selected_camera) 236 | set_camera(None, self.selected_camera) 237 | dpg.add_button(label="Button", callback=next_camera, arrow=True, direction=dpg.mvDir_Down) 238 | 239 | # timestep switch 240 | with dpg.group(horizontal=True): 241 | def set_timestep_slider(sender, data): 242 | self.selected_timestep_idx = data 243 | self.selected_timestep = self.timesteps[self.selected_timestep_idx] 244 | self.update_folder_tree(level='filetype') 245 | self.need_update = True 246 | dpg.add_slider_int(label="time step", max_value=len(self.timesteps), callback=set_timestep_slider, tag='slider_timestep') 247 | 248 | def prev_timestep(sender, data): 249 | if len(self.timesteps) > 1: 250 | if self.selected_timestep_idx > 0: 251 | self.selected_timestep_idx -= 1 252 | self.selected_timestep = self.timesteps[self.selected_timestep_idx] 253 | dpg.set_value("slider_timestep", value=self.selected_timestep_idx) 254 | set_timestep_slider(None, self.selected_timestep_idx) 255 | dpg.add_button(label="Button", callback=prev_timestep, arrow=True, direction=dpg.mvDir_Left) 256 | 257 | def next_timestep(sender, data): 258 | if len(self.timesteps) > 1: 259 | if self.selected_timestep_idx < len(self.timesteps)-1: 260 | self.selected_timestep_idx += 1 261 | self.selected_timestep = self.timesteps[self.selected_timestep_idx] 262 | dpg.set_value("slider_timestep", value=self.selected_timestep_idx) 263 | set_timestep_slider(None, self.selected_timestep_idx) 264 | dpg.add_button(label="Button", callback=next_timestep, arrow=True, direction=dpg.mvDir_Right) 265 | 266 | def set_timestep(sender, data): 267 | if self.selected_subject == '-' or self.selected_sequence == '-': 268 | return 269 | 270 | if sender == 'mvKey_Home': 271 | self.selected_timestep_idx = 0 272 | elif sender == 'mvKey_End': 273 | if len(self.timesteps) > 0: 274 | self.selected_timestep_idx = len(self.timesteps)-1 275 | self.selected_timestep = self.timesteps[self.selected_timestep_idx] 276 | dpg.set_value("slider_timestep", value=self.selected_timestep_idx) 277 | set_timestep_slider(None, self.selected_timestep_idx) 278 | 279 | # annotations 280 | dpg.add_separator() 281 | def set_annotation(sender, data): 282 | self.need_update = True 283 | with dpg.group(horizontal=True): 284 | dpg.add_checkbox(label="landmarks (STAR) ", callback=set_annotation, tag='checkbox_lmk_star', show=False, default_value=False) 285 | dpg.add_checkbox(label="landmarks (PIPnet)", callback=set_annotation, tag='checkbox_lmk_pipnet', show=False, default_value=False) 286 | with dpg.group(horizontal=True): 287 | dpg.add_checkbox(label="landmarks (FA) ", callback=set_annotation, tag='checkbox_lmk_fa', show=False, default_value=False) 288 | with dpg.group(horizontal=True): 289 | dpg.add_checkbox(label="foreground ", callback=set_annotation, tag='checkbox_fg', show=False, default_value=False) 290 | dpg.add_checkbox(label="segmentation ", callback=set_annotation, tag='checkbox_seg', show=False, default_value=False) 291 | 292 | with dpg.collapsing_header(label="Filter regions", default_open=False, show=False, tag='collapsing_filter_regions'): 293 | def set_region(sender, data): 294 | self.need_update = True 295 | 296 | n_cols = 5 297 | n_rows = len(region2seg_class) // n_cols + int((len(region2seg_class) % n_cols) > 0) 298 | 299 | for i in range(n_rows): 300 | dpg.add_group(tag=f'filter_region_group_{i}', horizontal=True) 301 | 302 | for i, region in enumerate(region2seg_class.keys()): 303 | dpg.add_checkbox(label=f'{region:6s}', callback=set_region, tag=f'checkbox_{region}', show=True, default_value=True, parent=f'filter_region_group_{i // n_cols}') 304 | 305 | 306 | # key press handlers 307 | with dpg.handler_registry(): 308 | dpg.add_key_press_handler(dpg.mvKey_W, callback=prev_subject) 309 | dpg.add_key_press_handler(dpg.mvKey_S, callback=next_subject) 310 | dpg.add_key_press_handler(dpg.mvKey_A, callback=prev_sequence) 311 | dpg.add_key_press_handler(dpg.mvKey_D, callback=next_sequence) 312 | dpg.add_key_press_handler(dpg.mvKey_Left, callback=prev_timestep) 313 | dpg.add_key_press_handler(dpg.mvKey_Right, callback=next_timestep) 314 | dpg.add_key_press_handler(dpg.mvKey_Up, callback=prev_camera) 315 | dpg.add_key_press_handler(dpg.mvKey_Down, callback=next_camera) 316 | dpg.add_key_press_handler(dpg.mvKey_Home, callback=set_timestep, tag='mvKey_Home') 317 | dpg.add_key_press_handler(dpg.mvKey_End, callback=set_timestep, tag='mvKey_End') 318 | 319 | # theme 320 | with dpg.theme() as theme_no_padding: 321 | with dpg.theme_component(dpg.mvAll): 322 | # set all padding to 0 to avoid scroll bar 323 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0, 0, category=dpg.mvThemeCat_Core) 324 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 0, 0, category=dpg.mvThemeCat_Core) 325 | dpg.add_theme_style(dpg.mvStyleVar_CellPadding, 0, 0, category=dpg.mvThemeCat_Core) 326 | dpg.bind_item_theme("viewer_tag", theme_no_padding) 327 | 328 | def resize_windows(self): 329 | dpg.configure_item('viewer_tag', width=self.width, height=self.height) 330 | dpg.configure_item('navigator_tag', width=self.width_nav, height=self.height_nav, pos=[self.width-self.width_nav-15, 0]) 331 | 332 | dpg.delete_item('texture_tag') 333 | dpg.delete_item('image_tag') 334 | with dpg.texture_registry(show=False): 335 | dpg.add_raw_texture(width=self.width, height=self.height, default_value=np.zeros([self.height, self.width, 3]), format=dpg.mvFormat_Float_rgb, tag="texture_tag") 336 | dpg.add_image("texture_tag", tag='image_tag', parent='viewer_tag') 337 | self.need_update = True 338 | 339 | def run(self): 340 | self.reset_folder_tree(update_items=False) 341 | self.define_gui() 342 | dpg.create_viewport(title='NersembleData Viewer', width=self.width, height=self.height, resizable=True) 343 | dpg.setup_dearpygui() 344 | dpg.show_viewport() 345 | 346 | while dpg.is_dearpygui_running(): 347 | if self.width != dpg.get_viewport_width() or self.height != dpg.get_viewport_height(): 348 | self.width = dpg.get_viewport_width() 349 | self.height = dpg.get_viewport_height() 350 | self.resize_windows() 351 | 352 | if self.need_update: 353 | self.update_viewer() 354 | self.need_update = False 355 | dpg.render_dearpygui_frame() 356 | dpg.destroy_context() 357 | 358 | def iterdir(self, subject=None, sequence=None, filetype=None): 359 | if filetype is not None: 360 | return [x.name for x in (self.root_folder / subject / 'sequences' / sequence / filetype).iterdir()] 361 | elif sequence is not None: 362 | return [x.name for x in (self.root_folder / subject / 'sequences' / sequence).iterdir() if x.is_dir()] 363 | elif subject is not None: 364 | return [x.name for x in (self.root_folder / subject / 'sequences').iterdir()] 365 | else: 366 | raise ValueError("Invalid arguments") 367 | 368 | def update_annotations(self): 369 | lmk_star_path = self.root_folder / self.selected_subject / 'sequences' / self.selected_sequence / 'landmark2d' / 'STAR' / f"{self.selected_camera.replace('cam_', '')}.npz" 370 | if lmk_star_path.exists(): 371 | dpg.configure_item('checkbox_lmk_star', show=True) 372 | else: 373 | dpg.configure_item('checkbox_lmk_star', show=False) 374 | 375 | lmk_pipnet_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'landmark2d' / 'PIPnet' / f"{self.selected_camera.replace('cam_', '')}.npy" 376 | if lmk_pipnet_path.exists(): 377 | dpg.configure_item('checkbox_lmk_pipnet', show=True) 378 | else: 379 | dpg.configure_item('checkbox_lmk_pipnet', show=False) 380 | 381 | lmk_fa_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'landmark2d' / 'face-alignment' / f"{self.selected_camera.replace('cam_', '')}.npz" 382 | if lmk_fa_path.exists(): 383 | dpg.configure_item('checkbox_lmk_fa', show=True) 384 | else: 385 | dpg.configure_item('checkbox_lmk_fa', show=False) 386 | 387 | fg = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'alpha_maps' / f'{self.selected_camera}_{self.selected_timestep}.jpg' 388 | if fg.exists(): 389 | dpg.configure_item('checkbox_fg', show=True) 390 | else: 391 | dpg.configure_item('checkbox_fg', show=False) 392 | 393 | seg = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'bisenet_segmentation_masks' / f'{self.selected_camera}_{self.selected_timestep}.jpg' 394 | if seg.exists(): 395 | dpg.configure_item('checkbox_seg', show=True) 396 | dpg.configure_item('collapsing_filter_regions', show=True) 397 | else: 398 | dpg.configure_item('checkbox_seg', show=False) 399 | dpg.configure_item('collapsing_filter_regions', show=False) 400 | 401 | def update_folder_tree(self, level=Optional[Literal['sequence', 'filetype', 'camera', 'timestep']]): 402 | if self.selected_sequence == '-' or self.selected_subject == '-': 403 | self.reset_folder_tree() 404 | return 405 | 406 | update_sequence = level in ['sequence'] 407 | update_filetype = level in ['sequence', 'filetype'] 408 | update_camera = level in ['sequence', 'filetype', 'camera'] 409 | update_timestep = level in ['sequence', 'filetype', 'camera', 'timestep'] 410 | 411 | if update_sequence: 412 | self.available_sequences = [ 413 | x for x in self.iterdir(self.selected_subject) \ 414 | if len(list((self.root_folder / self.selected_subject).glob(f'sequences/{x}/images/*.jpg'))) > 0 415 | ] 416 | if self.selected_sequence not in self.available_sequences: 417 | self.selected_sequence = self.available_sequences[0] 418 | dpg.configure_item("combo_sequence", items=self.available_sequences, default_value=self.selected_sequence) 419 | 420 | if update_filetype: 421 | self.filetypes = self.iterdir(self.selected_subject, self.selected_sequence) 422 | self.filetypes_images = sorted([x for x in self.filetypes if 'image' in x], reverse=True) 423 | if self.selected_filetype not in self.filetypes: 424 | self.selected_filetype = self.filetypes_images[0] if len(self.filetypes_images) > 0 else self.filetypes[0] 425 | dpg.configure_item("combo_filetype", items=self.filetypes, default_value=self.selected_filetype) 426 | 427 | if update_camera: 428 | fnames = self.iterdir(self.selected_subject, self.selected_sequence, self.selected_filetype) 429 | self.cameras = sorted(set(['_'.join(f.split('.')[0].split('_')[:-1]) for f in fnames if '.mp4' not in f])) 430 | if self.selected_camera not in self.cameras: 431 | self.selected_camera = self.cameras[0] 432 | dpg.configure_item("combo_camera", items=self.cameras, default_value=self.selected_camera) 433 | 434 | if update_timestep: 435 | fnames = self.iterdir(self.selected_subject, self.selected_sequence, self.selected_filetype) 436 | self.timesteps = sorted(set([f.split('.')[0].split('_')[-1] for f in fnames])) 437 | if self.selected_timestep not in self.timesteps: 438 | self.selected_timestep = self.timesteps[0] 439 | self.selected_timestep_idx = self.timesteps.index(self.selected_timestep) 440 | dpg.configure_item("slider_timestep", max_value=len(self.timesteps)-1, default_value=self.selected_timestep_idx) 441 | 442 | self.update_annotations() 443 | 444 | def update_viewer(self): 445 | if self.selected_sequence != '-' and self.selected_subject != '-': 446 | 447 | path = self.root_folder / self.selected_subject / 'sequences' / self.selected_sequence / self.selected_filetype 448 | path = glob.glob(f'{str(path)}/{self.selected_camera}_{self.selected_timestep}*') 449 | if len(path) == 0: 450 | return 451 | 452 | path = path[0] 453 | if 'jpg' not in path.lower() and 'png' not in path.lower(): 454 | return 455 | 456 | if self.selected_filetype == 'bisenet_segmentation_masks': 457 | # load as uint8 and get float32 after applying colormap 458 | img = self.load_image(path, Image.NEAREST) 459 | cm = plt.get_cmap('tab20c') 460 | img = cm(img[:, :, 0])[:, :, :3].astype(np.float32) 461 | else: 462 | # directly load as float32 463 | img = self.load_image(path) 464 | img = img.astype(np.float32) / 255 465 | 466 | if dpg.get_item_configuration("checkbox_lmk_star")['show'] and dpg.get_value("checkbox_lmk_star"): 467 | npz_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'landmark2d' / 'STAR' / f"{self.selected_camera.replace('cam_', '')}.npz" 468 | npz = np.load(npz_path) 469 | lmk_star = npz['face_landmark_2d'] 470 | bbox_star = npz['bounding_box'] 471 | 472 | color = (0, 255, 0) 473 | for lmk in lmk_star[self.selected_timestep_idx]: 474 | x = int(lmk[0] * img.shape[1]) 475 | y = int(lmk[1] * img.shape[0]) 476 | cv2.circle(img, (x, y), 2, color, -1) 477 | 478 | # x1, y1, x2, y2 = bbox_star[self.selected_timestep_idx][:4] 479 | # x1 = int(x1 * img.shape[1]) 480 | # y1 = int(y1 * img.shape[0]) 481 | # x2 = int(x2 * img.shape[1]) 482 | # y2 = int(y2 * img.shape[0]) 483 | # cv2.rectangle(img, (x1, y1), (x2, y2), color, 1) 484 | 485 | if dpg.get_item_configuration("checkbox_lmk_pipnet")['show'] and dpg.get_value("checkbox_lmk_pipnet"): 486 | npy_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'landmark2d' / 'PIPnet' / f"{self.selected_camera.replace('cam_', '')}.npy" 487 | lmk_pipnet = np.load(npy_path) 488 | 489 | color = (0, 0, 255) 490 | for lmk in lmk_pipnet[self.selected_timestep_idx]: 491 | x = int(lmk[0] * img.shape[1]) 492 | y = int(lmk[1] * img.shape[0]) 493 | cv2.circle(img, (x, y), 2, color, -1) 494 | 495 | if dpg.get_item_configuration("checkbox_lmk_fa")['show'] and dpg.get_value("checkbox_lmk_fa"): 496 | npz_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'landmark2d' / 'face-alignment' / f"{self.selected_camera.replace('cam_', '')}.npz" 497 | npz = np.load(npz_path) 498 | lmk_fa = npz['face_landmark_2d'] 499 | bbox_fa = npz['bounding_box'] 500 | 501 | color = (255, 0, 0) 502 | for lmk in lmk_fa[self.selected_timestep_idx]: 503 | x = int(lmk[0] * img.shape[1]) 504 | y = int(lmk[1] * img.shape[0]) 505 | cv2.circle(img, (x, y), 2, color, -1) 506 | 507 | # x1, y1, x2, y2 = bbox_fa[self.selected_timestep_idx][:4] 508 | # x1 = int(x1 * img.shape[1]) 509 | # y1 = int(y1 * img.shape[0]) 510 | # x2 = int(x2 * img.shape[1]) 511 | # y2 = int(y2 * img.shape[0]) 512 | # cv2.rectangle(img, (x1, y1), (x2, y2), color, 1) 513 | 514 | if dpg.get_item_configuration("checkbox_fg")['show'] and dpg.get_value("checkbox_fg"): 515 | fg_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'alpha_maps' / f'{self.selected_camera}_{self.selected_timestep}.jpg' 516 | fg_alpha = self.load_image(fg_path).astype(np.float32)[..., :3] / 255 517 | img = img * (fg_alpha) + np.ones_like(img) * (1 - fg_alpha) 518 | 519 | if dpg.get_item_configuration("checkbox_seg")['show'] and dpg.get_value("checkbox_seg"): 520 | seg_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'bisenet_segmentation_masks' / f'{self.selected_camera}_{self.selected_timestep}.jpg' 521 | seg = self.load_image(seg_path, Image.NEAREST) 522 | cm = plt.get_cmap('tab20c') 523 | seg = cm(seg[:, :, 0])[:, :, :3].astype(np.float32) 524 | img = img * 0.5 + seg * 0.5 525 | 526 | if dpg.get_item_configuration("collapsing_filter_regions")['show']: 527 | seg_path = self.root_folder / self.selected_subject / 'sequences'/ self.selected_sequence / 'bisenet_segmentation_masks' / f'{self.selected_camera}_{self.selected_timestep}.jpg' 528 | seg = self.load_image(seg_path, Image.NEAREST) 529 | for region, seg_class in region2seg_class.items(): 530 | if not dpg.get_value(f"checkbox_{region}"): 531 | mask = np.ones_like(img) 532 | mask[seg == seg_class] = 0 533 | img = img * mask + np.ones_like(img) * (1 - mask) 534 | 535 | img = np.pad(img, ((0, self.height - img.shape[0]), (0, self.width - img.shape[1]), (0, 0)), mode='constant', constant_values=0) 536 | 537 | dpg.set_value("texture_tag", img) 538 | 539 | def load_image(self, path, resample=Image.BILINEAR): 540 | img = Image.open(path) 541 | scale = min(self.height / img.height, self.width / img.width) 542 | img = img.resize((int(img.width * scale), int(img.height * scale)), resample) 543 | img = np.asarray(img) 544 | if img.ndim == 2: 545 | img = np.repeat(img[:, :, np.newaxis], 3, axis=2) 546 | return img 547 | 548 | 549 | if __name__ == '__main__': 550 | cfg = tyro.cli(NersembleDataViewerConfig) 551 | app = NersembleDataViewer(cfg) 552 | app.run() 553 | --------------------------------------------------------------------------------