# common utils import os import argparse import time # pytorch3d from pytorch3d.renderer import TexturesUV # torch import torch from torchvision import transforms # numpy import numpy as np # image from PIL import Image # customized import sys # sys.path.append(".") sys.path.append("./text2tex") from lib.mesh_helper import ( init_mesh, apply_offsets_to_mesh, adjust_uv_map ) from lib.render_helper import render from lib.io_helper import ( save_backproject_obj, save_args, save_viewpoints ) from lib.vis_helper import ( visualize_outputs, visualize_principle_viewpoints, visualize_refinement_viewpoints ) from lib.diffusion_helper import ( get_controlnet_depth, get_inpainting, apply_controlnet_depth, apply_inpainting_postprocess ) from lib.projection_helper import ( backproject_from_image, render_one_view_and_build_masks, select_viewpoint, build_similarity_texture_cache_for_all_views ) from lib.camera_helper import init_viewpoints # Setup if torch.cuda.is_available(): DEVICE = torch.device("cuda:0") torch.cuda.set_device(DEVICE) else: print("no gpu avaiable") exit() """ Use Diffusion Models conditioned on depth input to back-project textures on 3D mesh. The inputs should be constructed as follows: - / |- # name of the input OBJ file The outputs of this script would be stored under `outputs/`, with the configuration parameters as the folder name. Specifically, there should be following files in such folder: - outputs/ |- / # configurations of the run |- generate/ # assets generated in generation stage |- depth/ # depth map |- inpainted/ # images generated by diffusion models |- intermediate/ # renderings of textured mesh after each step |- mask/ # generation mask |- mesh/ # textured mesh |- normal/ # normal map |- rendering/ # input renderings |- similarity/ # simiarity map |- update/ # assets generated in refinement stage |- ... # the structure is the same as generate/ |- args.json # all arguments for the run |- viewpoints.json # all viewpoints |- principle_viewpoints.png # principle viewpoints |- refinement_viewpoints.png # refinement viewpoints """ def init_args(): print("=> initializing input arguments...") parser = argparse.ArgumentParser() parser.add_argument("--input_dir", type=str, default="./inputs",) parser.add_argument("--output_dir", type=str, default="./outputs") parser.add_argument("--obj_name", type=str, default="mesh") parser.add_argument("--obj_file", type=str, default="mesh.obj") parser.add_argument("--prompt", type=str, default="a 3D object") parser.add_argument("--a_prompt", type=str, default="best quality, high quality, extremely detailed, good geometry") parser.add_argument("--n_prompt", type=str, default="deformed, extra digit, fewer digits, cropped, worst quality, low quality, smoke") parser.add_argument("--new_strength", type=float, default=1) parser.add_argument("--update_strength", type=float, default=0.5) parser.add_argument("--ddim_steps", type=int, default=20) parser.add_argument("--guidance_scale", type=float, default=10) parser.add_argument("--output_scale", type=float, default=1) parser.add_argument("--view_threshold", type=float, default=0.1) parser.add_argument("--num_viewpoints", type=int, default=8) parser.add_argument("--viewpoint_mode", type=str, default="predefined", choices=["predefined", "hemisphere"]) parser.add_argument("--update_steps", type=int, default=8) parser.add_argument("--update_mode", type=str, default="heuristic", choices=["sequential", "heuristic", "random"]) parser.add_argument("--blend", type=float, default=0.5) parser.add_argument("--eta", type=float, default=0.0) parser.add_argument("--seed", type=int, default=42) parser.add_argument("--use_patch", action="store_true", help="apply repaint during refinement to patch up the missing regions") parser.add_argument("--use_multiple_objects", action="store_true", help="operate on multiple objects") parser.add_argument("--use_principle", action="store_true", help="poperate on multiple objects") parser.add_argument("--use_shapenet", action="store_true", help="operate on ShapeNet objects") parser.add_argument("--use_objaverse", action="store_true", help="operate on Objaverse objects") parser.add_argument("--use_unnormalized", action="store_true", help="save unnormalized mesh") parser.add_argument("--add_view_to_prompt", action="store_true", help="add view information to the prompt") parser.add_argument("--post_process", action="store_true", help="post processing the texture") parser.add_argument("--smooth_mask", action="store_true", help="smooth the diffusion mask") parser.add_argument("--force", action="store_true", help="forcefully generate more image") # negative options parser.add_argument("--no_repaint", action="store_true", help="do NOT apply repaint") parser.add_argument("--no_update", action="store_true", help="do NOT apply update") # device parameters parser.add_argument("--device", type=str, choices=["a6000", "2080"], default="a6000") # camera parameters NOTE need careful tuning!!! parser.add_argument("--test_camera", action="store_true") parser.add_argument("--dist", type=float, default=1, help="distance to the camera from the object") parser.add_argument("--elev", type=float, default=0, help="the angle between the vector from the object to the camera and the horizontal plane") parser.add_argument("--azim", type=float, default=180, help="the angle between the vector from the object to the camera and the vertical plane") parser.add_argument("--uv_size", type=int, default=1000) parser.add_argument("--image_size", type=int, default=768) args = parser.parse_args() if args.device == "a6000": setattr(args, "render_simple_factor", 12) setattr(args, "fragment_k", 1) setattr(args, "image_size", 768) setattr(args, "uv_size", 3000) else: setattr(args, "render_simple_factor", 4) setattr(args, "fragment_k", 1) setattr(args, "image_size", args.image_size) setattr(args, "uv_size", args.uv_size) return args def text2tex_call(args): tik = time.time() # save output_dir = os.path.join( args.output_dir, "{}-{}-{}-{}-{}-{}-{}-{}-{}".format( str(args.seed), args.viewpoint_mode[0]+str(args.num_viewpoints), args.update_mode[0]+str(args.update_steps), 'd'+str(args.ddim_steps), str(args.new_strength), str(args.update_strength), str(args.view_threshold), 'uv'+str(args.uv_size), 'img'+str(args.image_size), ), ) if args.no_repaint: output_dir += "-norepaint" if args.no_update: output_dir += "-noupdate" os.makedirs(output_dir, exist_ok=True) print("=> OUTPUT_DIR:", output_dir) # init resources # init mesh mesh, _, faces, aux, principle_directions, mesh_center, mesh_scale = init_mesh( os.path.join(args.input_dir, args.obj_file), os.path.join(output_dir, args.obj_file), DEVICE ) # gradient texture init_texture = Image.open("./text2tex/samples/textures/dummy.png").convert("RGB").resize((args.uv_size, args.uv_size)) # HACK adjust UVs for multiple materials if args.use_multiple_objects: new_verts_uvs, init_texture = adjust_uv_map(faces, aux, init_texture, args.uv_size) else: new_verts_uvs = aux.verts_uvs # update the mesh mesh.textures = TexturesUV( maps=transforms.ToTensor()(init_texture)[None, ...].permute(0, 2, 3, 1).to(DEVICE), faces_uvs=faces.textures_idx[None, ...], verts_uvs=new_verts_uvs[None, ...] ) # back-projected faces exist_texture = torch.from_numpy(np.zeros([args.uv_size, args.uv_size]).astype(np.float32)).to(DEVICE) # initialize viewpoints # including: principle viewpoints for generation + refinement viewpoints for updating ( dist_list, elev_list, azim_list, sector_list, view_punishments ) = init_viewpoints(args.viewpoint_mode, args.num_viewpoints, args.dist, args.elev, principle_directions, use_principle=True, use_shapenet=args.use_shapenet, use_objaverse=args.use_objaverse) # save args save_args(args, output_dir) # initialize depth2image model controlnet, ddim_sampler = get_controlnet_depth() # ------------------- OPERATION ZONE BELOW ------------------------ # 1. generate texture with RePaint # NOTE no update / refinement generate_dir = os.path.join(output_dir, "generate") os.makedirs(generate_dir, exist_ok=True) update_dir = os.path.join(output_dir, "update") os.makedirs(update_dir, exist_ok=True) init_image_dir = os.path.join(generate_dir, "rendering") os.makedirs(init_image_dir, exist_ok=True) normal_map_dir = os.path.join(generate_dir, "normal") os.makedirs(normal_map_dir, exist_ok=True) mask_image_dir = os.path.join(generate_dir, "mask") os.makedirs(mask_image_dir, exist_ok=True) depth_map_dir = os.path.join(generate_dir, "depth") os.makedirs(depth_map_dir, exist_ok=True) similarity_map_dir = os.path.join(generate_dir, "similarity") os.makedirs(similarity_map_dir, exist_ok=True) inpainted_image_dir = os.path.join(generate_dir, "inpainted") os.makedirs(inpainted_image_dir, exist_ok=True) mesh_dir = os.path.join(generate_dir, "mesh") os.makedirs(mesh_dir, exist_ok=True) interm_dir = os.path.join(generate_dir, "intermediate") os.makedirs(interm_dir, exist_ok=True) # prepare viewpoints and cache NUM_PRINCIPLE = 10 if args.use_shapenet or args.use_objaverse else 6 pre_dist_list = dist_list[:NUM_PRINCIPLE] pre_elev_list = elev_list[:NUM_PRINCIPLE] pre_azim_list = azim_list[:NUM_PRINCIPLE] pre_sector_list = sector_list[:NUM_PRINCIPLE] pre_view_punishments = view_punishments[:NUM_PRINCIPLE] pre_similarity_texture_cache = build_similarity_texture_cache_for_all_views(mesh, faces, new_verts_uvs, pre_dist_list, pre_elev_list, pre_azim_list, args.image_size, args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, DEVICE ) # start generation print("=> start generating texture...") start_time = time.time() for view_idx in range(NUM_PRINCIPLE): print("=> processing view {}...".format(view_idx)) # sequentially pop the viewpoints dist, elev, azim, sector = pre_dist_list[view_idx], pre_elev_list[view_idx], pre_azim_list[view_idx], pre_sector_list[view_idx] prompt = " the {} view of {}".format(sector, args.prompt) if args.add_view_to_prompt else args.prompt print("=> generating image for prompt: {}...".format(prompt)) # 1.1. render and build masks ( view_score, renderer, cameras, fragments, init_image, normal_map, depth_map, init_images_tensor, normal_maps_tensor, depth_maps_tensor, similarity_tensor, keep_mask_image, update_mask_image, generate_mask_image, keep_mask_tensor, update_mask_tensor, generate_mask_tensor, all_mask_tensor, quad_mask_tensor, ) = render_one_view_and_build_masks(dist, elev, azim, view_idx, view_idx, view_punishments, # => actual view idx and the sequence idx pre_similarity_texture_cache, exist_texture, mesh, faces, new_verts_uvs, args.image_size, args.fragment_k, init_image_dir, mask_image_dir, normal_map_dir, depth_map_dir, similarity_map_dir, DEVICE, save_intermediate=True, smooth_mask=args.smooth_mask, view_threshold=args.view_threshold ) # 1.2. generate missing region # NOTE first view still gets the mask for consistent ablations if args.no_repaint and view_idx != 0: actual_generate_mask_image = Image.fromarray((np.ones_like(np.array(generate_mask_image)) * 255.).astype(np.uint8)) else: actual_generate_mask_image = generate_mask_image print("=> generate for view {}".format(view_idx)) generate_image, generate_image_before, generate_image_after = apply_controlnet_depth(controlnet, ddim_sampler, init_image.convert("RGBA"), prompt, args.new_strength, args.ddim_steps, actual_generate_mask_image, keep_mask_image, depth_maps_tensor.permute(1, 2, 0).repeat(1, 1, 3).cpu().numpy(), args.a_prompt, args.n_prompt, args.guidance_scale, args.seed, args.eta, 1, DEVICE, args.blend) generate_image.save(os.path.join(inpainted_image_dir, "{}.png".format(view_idx))) generate_image_before.save(os.path.join(inpainted_image_dir, "{}_before.png".format(view_idx))) generate_image_after.save(os.path.join(inpainted_image_dir, "{}_after.png".format(view_idx))) # 1.2.2 back-project and create texture # NOTE projection mask = generate mask init_texture, project_mask_image, exist_texture = backproject_from_image( mesh, faces, new_verts_uvs, cameras, generate_image, generate_mask_image, generate_mask_image, init_texture, exist_texture, args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, DEVICE ) project_mask_image.save(os.path.join(mask_image_dir, "{}_project.png".format(view_idx))) # update the mesh mesh.textures = TexturesUV( maps=transforms.ToTensor()(init_texture)[None, ...].permute(0, 2, 3, 1).to(DEVICE), faces_uvs=faces.textures_idx[None, ...], verts_uvs=new_verts_uvs[None, ...] ) # 1.2.3. re: render # NOTE only the rendered image is needed - masks should be re-used ( view_score, renderer, cameras, fragments, init_image, *_, ) = render_one_view_and_build_masks(dist, elev, azim, view_idx, view_idx, view_punishments, # => actual view idx and the sequence idx pre_similarity_texture_cache, exist_texture, mesh, faces, new_verts_uvs, args.image_size, args.fragment_k, init_image_dir, mask_image_dir, normal_map_dir, depth_map_dir, similarity_map_dir, DEVICE, save_intermediate=False, smooth_mask=args.smooth_mask, view_threshold=args.view_threshold ) # 1.3. update blurry region # only when: 1) use update flag; 2) there are contents to update; 3) there are enough contexts. if not args.no_update and update_mask_tensor.sum() > 0 and update_mask_tensor.sum() / (all_mask_tensor.sum()) > 0.05: print("=> update {} pixels for view {}".format(update_mask_tensor.sum().int(), view_idx)) diffused_image, diffused_image_before, diffused_image_after = apply_controlnet_depth(controlnet, ddim_sampler, init_image.convert("RGBA"), prompt, args.update_strength, args.ddim_steps, update_mask_image, keep_mask_image, depth_maps_tensor.permute(1, 2, 0).repeat(1, 1, 3).cpu().numpy(), args.a_prompt, args.n_prompt, args.guidance_scale, args.seed, args.eta, 1, DEVICE, args.blend) diffused_image.save(os.path.join(inpainted_image_dir, "{}_update.png".format(view_idx))) diffused_image_before.save(os.path.join(inpainted_image_dir, "{}_update_before.png".format(view_idx))) diffused_image_after.save(os.path.join(inpainted_image_dir, "{}_update_after.png".format(view_idx))) # 1.3.2. back-project and create texture # NOTE projection mask = generate mask init_texture, project_mask_image, exist_texture = backproject_from_image( mesh, faces, new_verts_uvs, cameras, diffused_image, update_mask_image, update_mask_image, init_texture, exist_texture, args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, DEVICE ) # update the mesh mesh.textures = TexturesUV( maps=transforms.ToTensor()(init_texture)[None, ...].permute(0, 2, 3, 1).to(DEVICE), faces_uvs=faces.textures_idx[None, ...], verts_uvs=new_verts_uvs[None, ...] ) # 1.4. save generated assets # save backprojected OBJ file save_backproject_obj( mesh_dir, "{}.obj".format(view_idx), mesh_scale * mesh.verts_packed() + mesh_center if args.use_unnormalized else mesh.verts_packed(), faces.verts_idx, new_verts_uvs, faces.textures_idx, init_texture, DEVICE ) # save the intermediate view inter_images_tensor, *_ = render(mesh, renderer) inter_image = inter_images_tensor[0].cpu() inter_image = inter_image.permute(2, 0, 1) inter_image = transforms.ToPILImage()(inter_image).convert("RGB") inter_image.save(os.path.join(interm_dir, "{}.png".format(view_idx))) # save texture mask exist_texture_image = exist_texture * 255. exist_texture_image = Image.fromarray(exist_texture_image.cpu().numpy().astype(np.uint8)).convert("L") exist_texture_image.save(os.path.join(mesh_dir, "{}_texture_mask.png".format(view_idx))) print("=> total generate time: {} s".format(time.time() - start_time)) # visualize viewpoints visualize_principle_viewpoints(output_dir, pre_dist_list, pre_elev_list, pre_azim_list) # 2. update texture with RePaint if args.update_steps > 0: update_dir = os.path.join(output_dir, "update") os.makedirs(update_dir, exist_ok=True) init_image_dir = os.path.join(update_dir, "rendering") os.makedirs(init_image_dir, exist_ok=True) normal_map_dir = os.path.join(update_dir, "normal") os.makedirs(normal_map_dir, exist_ok=True) mask_image_dir = os.path.join(update_dir, "mask") os.makedirs(mask_image_dir, exist_ok=True) depth_map_dir = os.path.join(update_dir, "depth") os.makedirs(depth_map_dir, exist_ok=True) similarity_map_dir = os.path.join(update_dir, "similarity") os.makedirs(similarity_map_dir, exist_ok=True) inpainted_image_dir = os.path.join(update_dir, "inpainted") os.makedirs(inpainted_image_dir, exist_ok=True) mesh_dir = os.path.join(update_dir, "mesh") os.makedirs(mesh_dir, exist_ok=True) interm_dir = os.path.join(update_dir, "intermediate") os.makedirs(interm_dir, exist_ok=True) dist_list = dist_list[NUM_PRINCIPLE:] elev_list = elev_list[NUM_PRINCIPLE:] azim_list = azim_list[NUM_PRINCIPLE:] sector_list = sector_list[NUM_PRINCIPLE:] view_punishments = view_punishments[NUM_PRINCIPLE:] similarity_texture_cache = build_similarity_texture_cache_for_all_views(mesh, faces, new_verts_uvs, dist_list, elev_list, azim_list, args.image_size, args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, DEVICE ) selected_view_ids = [] print("=> start updating...") start_time = time.time() for view_idx in range(args.update_steps): print("=> processing view {}...".format(view_idx)) # 2.1. render and build masks # heuristically select the viewpoints dist, elev, azim, sector, selected_view_ids, view_punishments = select_viewpoint( selected_view_ids, view_punishments, args.update_mode, dist_list, elev_list, azim_list, sector_list, view_idx, similarity_texture_cache, exist_texture, mesh, faces, new_verts_uvs, args.image_size, args.fragment_k, init_image_dir, mask_image_dir, normal_map_dir, depth_map_dir, similarity_map_dir, DEVICE, False ) ( view_score, renderer, cameras, fragments, init_image, normal_map, depth_map, init_images_tensor, normal_maps_tensor, depth_maps_tensor, similarity_tensor, old_mask_image, update_mask_image, generate_mask_image, old_mask_tensor, update_mask_tensor, generate_mask_tensor, all_mask_tensor, quad_mask_tensor, ) = render_one_view_and_build_masks(dist, elev, azim, selected_view_ids[-1], view_idx, view_punishments, # => actual view idx and the sequence idx similarity_texture_cache, exist_texture, mesh, faces, new_verts_uvs, args.image_size, args.fragment_k, init_image_dir, mask_image_dir, normal_map_dir, depth_map_dir, similarity_map_dir, DEVICE, save_intermediate=True, smooth_mask=args.smooth_mask, view_threshold=args.view_threshold ) # # -------------------- OPTION ZONE ------------------------ # # still generate for missing regions during refinement # # NOTE this could take significantly more time to complete. # if args.use_patch: # # 2.2.1 generate missing region # prompt = " the {} view of {}".format(sector, args.prompt) if args.add_view_to_prompt else args.prompt # print("=> generating image for prompt: {}...".format(prompt)) # if args.no_repaint: # generate_mask_image = Image.fromarray((np.ones_like(np.array(generate_mask_image)) * 255.).astype(np.uint8)) # print("=> generate {} pixels for view {}".format(generate_mask_tensor.sum().int(), view_idx)) # generate_image, generate_image_before, generate_image_after = apply_controlnet_depth(controlnet, ddim_sampler, # init_image.convert("RGBA"), prompt, args.new_strength, args.ddim_steps, # generate_mask_image, keep_mask_image, depth_maps_tensor.permute(1, 2, 0).repeat(1, 1, 3).cpu().numpy(), # args.a_prompt, args.n_prompt, args.guidance_scale, args.seed, args.eta, 1, DEVICE, args.blend) # generate_image.save(os.path.join(inpainted_image_dir, "{}_new.png".format(view_idx))) # generate_image_before.save(os.path.join(inpainted_image_dir, "{}_new_before.png".format(view_idx))) # generate_image_after.save(os.path.join(inpainted_image_dir, "{}_new_after.png".format(view_idx))) # # 2.2.2. back-project and create texture # # NOTE projection mask = generate mask # init_texture, project_mask_image, exist_texture = backproject_from_image( # mesh, faces, new_verts_uvs, cameras, # generate_image, generate_mask_image, generate_mask_image, init_texture, exist_texture, # args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, # DEVICE # ) # project_mask_image.save(os.path.join(mask_image_dir, "{}_new_project.png".format(view_idx))) # # update the mesh # mesh.textures = TexturesUV( # maps=transforms.ToTensor()(init_texture)[None, ...].permute(0, 2, 3, 1).to(DEVICE), # faces_uvs=faces.textures_idx[None, ...], # verts_uvs=new_verts_uvs[None, ...] # ) # # 2.2.4. save generated assets # # save backprojected OBJ file # save_backproject_obj( # mesh_dir, "{}_new.obj".format(view_idx), # mesh.verts_packed(), faces.verts_idx, new_verts_uvs, faces.textures_idx, init_texture, # DEVICE # ) # # -------------------- OPTION ZONE ------------------------ # 2.2. update existing region prompt = " the {} view of {}".format(sector, args.prompt) if args.add_view_to_prompt else args.prompt print("=> updating image for prompt: {}...".format(prompt)) if not args.no_update and update_mask_tensor.sum() > 0 and update_mask_tensor.sum() / (all_mask_tensor.sum()) > 0.05: print("=> update {} pixels for view {}".format(update_mask_tensor.sum().int(), view_idx)) update_image, update_image_before, update_image_after = apply_controlnet_depth(controlnet, ddim_sampler, init_image.convert("RGBA"), prompt, args.update_strength, args.ddim_steps, update_mask_image, old_mask_image, depth_maps_tensor.permute(1, 2, 0).repeat(1, 1, 3).cpu().numpy(), args.a_prompt, args.n_prompt, args.guidance_scale, args.seed, args.eta, 1, DEVICE, args.blend) update_image.save(os.path.join(inpainted_image_dir, "{}.png".format(view_idx))) update_image_before.save(os.path.join(inpainted_image_dir, "{}_before.png".format(view_idx))) update_image_after.save(os.path.join(inpainted_image_dir, "{}_after.png".format(view_idx))) else: print("=> nothing to update for view {}".format(view_idx)) update_image = init_image old_mask_tensor += update_mask_tensor update_mask_tensor[update_mask_tensor == 1] = 0 # HACK nothing to update old_mask_image = transforms.ToPILImage()(old_mask_tensor) update_mask_image = transforms.ToPILImage()(update_mask_tensor) # 2.3. back-project and create texture # NOTE projection mask = update mask init_texture, project_mask_image, exist_texture = backproject_from_image( mesh, faces, new_verts_uvs, cameras, update_image, update_mask_image, update_mask_image, init_texture, exist_texture, args.image_size * args.render_simple_factor, args.uv_size, args.fragment_k, DEVICE ) project_mask_image.save(os.path.join(mask_image_dir, "{}_project.png".format(view_idx))) # update the mesh mesh.textures = TexturesUV( maps=transforms.ToTensor()(init_texture)[None, ...].permute(0, 2, 3, 1).to(DEVICE), faces_uvs=faces.textures_idx[None, ...], verts_uvs=new_verts_uvs[None, ...] ) # 2.4. save generated assets # save backprojected OBJ file save_backproject_obj( mesh_dir, "{}.obj".format(view_idx), mesh_scale * mesh.verts_packed() + mesh_center if args.use_unnormalized else mesh.verts_packed(), faces.verts_idx, new_verts_uvs, faces.textures_idx, init_texture, DEVICE ) # save the intermediate view inter_images_tensor, *_ = render(mesh, renderer) inter_image = inter_images_tensor[0].cpu() inter_image = inter_image.permute(2, 0, 1) inter_image = transforms.ToPILImage()(inter_image).convert("RGB") inter_image.save(os.path.join(interm_dir, "{}.png".format(view_idx))) # save texture mask exist_texture_image = exist_texture * 255. exist_texture_image = Image.fromarray(exist_texture_image.cpu().numpy().astype(np.uint8)).convert("L") exist_texture_image.save(os.path.join(mesh_dir, "{}_texture_mask.png".format(view_idx))) print("=> total update time: {} s".format(time.time() - start_time)) # post-process if args.post_process: del controlnet del ddim_sampler inpainting = get_inpainting(DEVICE) post_texture = apply_inpainting_postprocess(inpainting, init_texture, 1-exist_texture[None, :, :, None], "", args.uv_size, args.uv_size, DEVICE) save_backproject_obj( mesh_dir, "{}_post.obj".format(view_idx), mesh_scale * mesh.verts_packed() + mesh_center if args.use_unnormalized else mesh.verts_packed(), faces.verts_idx, new_verts_uvs, faces.textures_idx, post_texture, DEVICE ) # save viewpoints save_viewpoints(args, output_dir, dist_list, elev_list, azim_list, selected_view_ids) # visualize viewpoints visualize_refinement_viewpoints(output_dir, selected_view_ids, dist_list, elev_list, azim_list) # output total time used and save to the output directory print("=> total time used: {} s".format(time.time() - tik)) with open(os.path.join(output_dir, "time.txt"), "w") as f: f.write("total time used: {} s".format(time.time() - tik)) return output_dir if __name__ == "__main__": args = init_args() text2tex_call(args)