diff --git a/README.md b/README.md index 094dc27b0..ce451cb72 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,21 @@ python fy/run_tiancheng.py --save_states True --debug True ``` python fy/run_tiancheng.py --save_states False --debug False +``` + +``` +python fy/run_tiancheng.py --save_states --debug --render_non_violate_video --test_scene_cls 100 + +``` + +``` +python fy/run_tiancheng.py --render_non_violate_video --test_scene_cls 100 +``` + +``` +python fy/run_tiancheng.py --debug --save_states --test_scene_cls 100 +``` +run with watch +``` +python fy/run_watch_tiancheng.py ``` \ No newline at end of file diff --git a/fy/base.py b/fy/base.py index c551231ef..422e659bb 100644 --- a/fy/base.py +++ b/fy/base.py @@ -249,6 +249,8 @@ def _set_camera_path(self, path_config): key_frame_val = path_config["key_frame_val"] camera = bpy.data.objects['camera'] + + bpy.ops.curve.primitive_bezier_circle_add(enter_editmode=False, align='WORLD', location=center, @@ -293,6 +295,7 @@ def _set_camera_path(self, path_config): keyframe.interpolation = 'CUBIC' keyframe.easing='EASE_IN_OUT' + camera.location = [0,0,0] return path_con def _set_camera_focus_point(self, look_at=[0,0,0]): @@ -356,27 +359,77 @@ def prepare_scene(self): self.static_objs = [] self._setup_everything() - if self._check_scene(): + if self._check_scene_with_all_cameras(): self.generate_keyframes() return logging.warning("Current scene is invalid. Regenerating ") - # self.renderer.save_state(f"temp_scene/invalid_{self.i}.blend") + self.renderer.save_state(f"temp_scene/invalid_{self.i}.blend") self.i += 1 if self.is_move_camera: self.camera_path_sample_stats[self.cur_camera_traj_idx] += 1 logging.info(f"Re-sampling... Current stats: {self.camera_path_sample_stats}") + def _check_scene_with_all_cameras(self): + """Check if the scene is valid for all the cameras. Return Flase if the scene is invalid. + TODO: implement (Override) this function + + Returns: + _type_: _description_ + """ + # self.scene.camera.position = self.default_camera_pos + # self.scene.camera.look_at(self.camera_look_at) + print("checking camera view 1") + self._switch_to_view1() + if not (self._check_scene()): + logging.warning("Check scene failed with Camera View 1") + return False + + if not self.flags.render_multiview: + return True + + print("checking camera view 2") + self._switch_to_view2() + if self._check_scene(): + # change back to the front camera + self._switch_to_view1() + return True + else: + logging.warning("Check scene failed with Camera View 2") + return False + + def _switch_to_view1(self): + cam = bpy.data.objects["camera"] + + if self.is_move_camera: + self.scene.camera.position = [0,0,0] + self.scene.camera.rotation_quaternion = [1,0,0,0] + else: + self.scene.camera.position = self.default_camera_pos + self.scene.camera.look_at(self.camera_look_at) + + + for c in cam.constraints: + c.mute = False + + + def _switch_to_view2(self): + cam = bpy.data.objects["camera"] + for c in cam.constraints: + c.mute = True + + self.scene.camera.position = self.alternative_camera_pos + self.scene.camera.look_at(self.alternative_camera_look_at) + def _check_scene(self): - """Check if the scene is valid. Return Flase if the scene is invalid. - TODO: implement (Override) this function + """Check if the scene is valid Return Flase if the scene is invalid. + TODO: implement (Override) this function - Returns: - _type_: _description_ - """ - return True - # return self._check_scene_visible() - + Returns: + _type_: _description_ + """ + + return True def __enter__(self): return self @@ -540,9 +593,11 @@ def _setup_indoor_scene(self, set_name(self.table_name) self.ref_h = table_h self.default_camera_pos[2] += table_h + self.alternative_camera_pos[2] += table_h self.table_id = table_id # print(self.table_id) self.camera_look_at = [0, 0, self.ref_h] + self.alternative_camera_look_at = [0, 0, self.ref_h] self.rng = rng self.output_dir = output_dir @@ -939,8 +994,9 @@ def render_alternative_view(self, save_to_file=False, **kwargs): _type_: _description_ """ self.load_non_violation_scene() # only used for non-violation scene - self.scene.camera.position = self.alternative_camera_pos - self.scene.camera.look_at(self.alternative_camera_look_at) + # self.scene.camera.position = self.alternative_camera_pos + # self.scene.camera.look_at(self.alternative_camera_look_at) + self._switch_to_view2() data_stack = self.renderer.render(return_layers=self.render_data) if save_to_file: kb.write_image_dict(data_stack, self.output_dir, **kwargs) diff --git a/fy/configs/tables.txt b/fy/configs/tables.txt index 14d81b7f2..f07ba6b95 100644 --- a/fy/configs/tables.txt +++ b/fy/configs/tables.txt @@ -139,7 +139,6 @@ 04379243/bbae4abbff206a2a14038d588fd1342f 04379243/c4388c59f863de596edd3f7982f0bf26 04379243/c7358b3aed4160cb21bc3cf138f79e -04379243/6ab7ebf9b94176456f1e07a56c129dfc 04379243/6f019fe6ab60a3e37b11ae648ea92233 04379243/fe0e7198411fc340c057222d6d091c56 04379243/f0cee441d88de6dafebad4f49b26ec52 @@ -162,9 +161,7 @@ 04379243/87ca3e8e37367054dcabaa2ad147fa73 04379243/9d71f9424fc659e17a50afc9c93f8a50 04379243/97bda10740c4a74036b0f2a1430e993a -04379243/b14c4d5783a339609fd4171283f33ca8 04379243/a4dbf0f4fef1c36cf199233c9f2ce2ce -04379243/726164afa497b154b075b4c36d25279a 04379243/a99a74777f6aacf2489e5619471f9f53 04379243/f64617385056e0b1beedb4c8fd29e2d1 04379243/fe99a1127734f7852b70eac6546e93fd @@ -851,7 +848,6 @@ 04379243/8839cf79a5338a568ce66f12ba927a2b 04379243/be5501adc4564d9edf30786b8faddb78 04379243/f80cce35d167ff9b855931d119219022 -04379243/9a71b92445cd3f023a9bc242c86fb7a0 04379243/ae5ac5b2b027fcf9118ddfdb81cc6068 04379243/6f576d151a46bdefd5cb6d178687b980 04379243/8768002c872788b8e513931a191fd77c diff --git a/fy/continuity.py b/fy/continuity.py index 0f2d2e234..014d77943 100755 --- a/fy/continuity.py +++ b/fy/continuity.py @@ -12,9 +12,16 @@ from utils import align_can_objs, spherical_to_cartesian # TODO: test if obj is on the table at most of time; why velocity changes after teletransportation - - -class ContinuityTestScene(PermananceTestScene): +invalid_small_objs = ["02946921/f8fd565d00a7a6f9fef1ca8c5a3d2e08", + "02946921/343287cd508a798d38df439574e01b2", + "02946921/91a524cc9c9be4872999f92861cdea7a", + "02946921/4d4fc73864844dad1ceb7b8cc3792fd", + "02946921/19fa6044dd31aa8e9487fa707cec1558", + "02946921/129880fda38f3f2ba1ab68e159bfb347", + "02946921/7b643c8136a720d9db4a36333be9155", + ] + +class ContinuityTestScene(BaseTestScene): """Test scene for permanance violation. Start: Object is visible Normal result: ... @@ -25,30 +32,42 @@ class ContinuityTestScene(PermananceTestScene): """ def __init__(self, FLAGS) -> None: - super().__init__(FLAGS) + self.is_add_background_static_objects = False self.frame_violation_start = -1 # two cases: # 1. object disappears # 0. object teleports - self.violation_type = np.random.binomial(n=1,p=0.5) + self.violation_type = 1#np.random.binomial(n=1,p=0.5) + self.is_move_camera = np.random.binomial(n=1,p=0.5) self.gravity = (0, 0, -4.9) - if not(self.violation_type): - self.default_camera_pos = spherical_to_cartesian(r_range=[2, 3], theta_range=[75, 85]) - self.camera_look_at = (0, 0, self.ref_h) - self.flags.move_camera = False - else: - self.flags.move_camera = True + # self.back_camera_pos = spherical_to_cartesian(r_range=[2.25, 2.75], theta_range=[70, 80], phi_range=[-45+180, 45+180]) + self.alternative_camera_pos = spherical_to_cartesian(r_range=[1.5, 2.25], theta_range=[70, 80], phi_range=[-45+180, 45+180]) + + + # if not(self.violation_type): + # self.default_camera_pos = spherical_to_cartesian(r_range=[2, 3], theta_range=[75, 85]) + # self.camera_look_at = (0, 0, self.ref_h) + # # self.flags.is_move_camera = False + # else: + # # self.flags.is_move_camera = True + # pass + + + def prepare_scene(self): print("preparing scene ...") super().prepare_scene() + self.alternative_camera_pos[2] += self.ref_h + self.default_camera_pos[2] += self.ref_h + self.camera_look_at = (0, 0, self.ref_h) + self.alternative_camera_look_at = (0, 0, self.ref_h) if not(self.violation_type): # remove block object self.block_obj.position = (0, 0, -10) - def generate_keyframes(self): """Generate keyframes for the test objects, for both violation and non-violation states """ @@ -113,48 +132,48 @@ def add_test_objects(self): _type_: _description_ """ # -- add small object - print("adding the small object") + valid = False + small_obj = None + while not valid: # only select valid small object + print("adding the small object") + if small_obj is not None: + self.scene.remove(small_obj) - if self.violation_type: - # relocate the block obj - self.block_obj.position = (0.15, -0.1, self.block_obj.position[2]) - else: - self.block_obj.position = (0, 0, -10) - # initialize the obj. Note the the initial position should be (0,0,0) - # so that the dist between its CoM and the table surface can be calculated - small_obj_id = [name for name, spec in shapenet_assets._assets.items() - if spec["metadata"]["category"] == "can"] - small_obj_id = self.rng.choice(small_obj_id) - small_obj = self.add_object(asset_id=small_obj_id, - position=(0, 0, 0), - velocity=(0, 0, 0), - quaternion=kb.Quaternion(axis=[0, 0, 1], degrees=0), - is_dynamic=True, - scale=0.15, - name="small_obj") - - self.test_obj_z_orn = align_can_objs(small_obj) - + if self.violation_type: + # relocate the block obj + self.block_obj.position = (0.15, -0.1, self.block_obj.position[2]) + else: + self.block_obj.position = (0, 0, -10) + # initialize the obj. Note the the initial position should be (0,0,0) + # so that the dist between its CoM and the table surface can be calculated + small_obj_id_all = [name for name, spec in shapenet_assets._assets.items() + if spec["metadata"]["category"] == "can"] + small_obj_id = [id for id in small_obj_id_all if id not in invalid_small_objs] + small_obj_id = self.rng.choice(small_obj_id) + small_obj = self.add_object(asset_id=small_obj_id, + position=(0, 0, 0), + velocity=(0, 0, 0), + quaternion=kb.Quaternion(axis=[0, 0, 1], degrees=0), + is_dynamic=True, + scale=0.15, + name="small_obj") + + self.test_obj_z_orn, radius, _, valid = align_can_objs(small_obj) + self.radius = radius + # small_obj.quaternion = kb.Quaternion(axis=[0, 0, 1], degrees=180) * small_obj.quaternion # set the position of the can to avoid potential collision # of the block object table_x_range = self.table.aabbox[0][0] block_y_range = self.block_obj.aabbox[1][1] - vx = self.rng.uniform(0.8, 1.0) # initial velocitry + vx = self.rng.uniform(0.35, 0.8) # initial velocitry px = self.rng.uniform(0, 0.05) + small_obj.aabbox[1][2] + table_x_range # initial position py = self.rng.uniform(0.1, 0.15) + block_y_range - pz = self.rng.uniform(0.0, 0.01) + self.ref_h - small_obj.aabbox[0][2] + pz = self.rng.uniform(0.05, 0.1) + self.ref_h + radius small_obj.position = (px, py, pz) small_obj.velocity = (vx, 0, 0) - small_obj.friction = 0.1 + small_obj.friction = 0 - - # align the can object - - # small_obj.position = (-0.8, 0.15, self.ref_h+0.2) - # for _ in range(10): - # print(small_obj.position, self.ref_h, small_obj.aabbox[0][2], self.ref_h - small_obj.aabbox[0][2]) - # print(small_obj.aabbox) self.test_obj = [small_obj] self._run_simulate() self.save_non_violation_scene() @@ -170,7 +189,8 @@ def _check_scene(self): Args: (bool): """ - + # self.frame_violation_start = 20; return True + less_strict = not(self.flags.render_violate_video) # TODO: farthest point sampling, try reduce num of samples frame_end = self.flags.frame_end @@ -183,10 +203,10 @@ def _check_scene(self): # Check visibility of the test obj at each frame print("Checking scene...") obj = self.test_obj[0] # work for only one test obj - for i, frame in enumerate(tqdm(range(self.flags.frame_start, self.flags.frame_end))): + for i, frame in enumerate((range(self.flags.frame_start, self.flags.frame_end))): bpy.context.scene.frame_set(frame) - vis_obj = getVisibleVertexFraction("small_obj", self.rng) - vis_table = isPointVisible([0, 0, self.ref_h], [self.table_name, self.block_name, "small_obj"]) + vis_obj = getVisibleVertexFraction("small_obj", self.rng, "camera") + vis_table = isPointVisible([0, 0, self.ref_h], [self.table_name, self.block_name, "small_obj"], "camera") visibility_obj[i] = vis_obj visibility_table[i] = vis_table obj_on_table[i] = obj.keyframes["position"][frame][2] > self.ref_h-0.05 @@ -204,22 +224,33 @@ def _check_scene(self): # cond_2 = visibility_obj[0] >= 0.15 \ # and visibility_obj[5] >= 0.15 \ # and visibility_obj[-6] >= 0.15 - cond_2 = is_obj_visible[:6].max() and is_obj_visible[-7:].max() + if less_strict: + cond_2 = is_obj_visible[:10].max() and is_obj_visible[-10:].max() + else: + cond_2 = is_obj_visible[:6].max() and is_obj_visible[-7:].max() + cond = [cond_2, - cond_1, + cond_1 or less_strict, in_view[0], in_view[5], - in_view[-12:].max(), + in_view[-12:].max() or less_strict, is_table_visible[6:-6].min(), - obj_on_table.sum() >= 16] + obj_on_table.sum() >= 16 or less_strict + ] + else: cond = [visibility_obj[0] >= 0.5, in_view[0], - in_view[-5], + in_view[-5] or less_strict, is_table_visible[6:-6].min(), - obj_on_table.sum() >= 20] + obj_on_table.sum() >= 20 or less_strict + ] is_valid = np.min(cond) + logging.error(cond) + # logging.error(np.min(cond)) + # logging.error(is_obj_visible[:10]) + # logging.error( is_obj_visible[-10:]) if is_valid: # set when the test object is set disappeared if self.violation_type: @@ -251,13 +282,23 @@ def _run_simulate(self, save_state=False, frame_start=0): # remove the test object's unexpected rotation obj_pos0 = obj.keyframes["position"][frame_start].copy() + for frame in range(frame_start, self.scene.frame_end+1): + pos = obj.keyframes["position"][frame].copy() + + rot_angle = (obj_pos0[0] - pos[0]) / self.radius + quaternion = kb.Quaternion(axis=[0, 1, 0], degrees=-rot_angle * 180 / np.pi) + # print(self.radius, (obj_pos0[0] - pos[0]), rot_angle * 180 / np.pi) + obj.quaternion = quaternion + obj.keyframe_insert("quaternion", frame) + return ret for frame in range(frame_start, self.scene.frame_end+1): # set xy velocity to 0 q = obj.keyframes["quaternion"][frame].copy() q = np.array(q) q[1] = 0 q[3] = 0 - obj.quaternion = q + q[2] = -q[2] + obj.quaternion = q / np.linalg.norm(q) obj.keyframe_insert("quaternion", frame) pos = obj.keyframes["position"][frame].copy() diff --git a/fy/run_tiancheng.py b/fy/run_tiancheng.py index dac311f4a..e1b1fcba4 100644 --- a/fy/run_tiancheng.py +++ b/fy/run_tiancheng.py @@ -1,45 +1,24 @@ -from fy.utils import get_args import logging +import colorlog +from fy.utils import get_args import kubric as kb -import imageio from fy.utils import write_video + from fy.solidity import SolidityTestScene from fy.collision import CollisionTestScene from fy.permanance import PermananceTestScene from fy.continuity import ContinuityTestScene from fy.support import SupportTestScene -from tqdm import tqdm -import colorlog import os - -from etils import epath -from random import choice -print(flush=True) -## TODO: set frame num from arg -frame_mid = 18 -frame_end = 36 -output_dirname = "render_output" -path_template = [ - {"euler_xyz": [0,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, - {"euler_xyz": [-25,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, # ! - {"euler_xyz": [0,-20,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, - {"euler_xyz": [0,-40,0], "key_frame_val": [-15, 20], "key_frame_num": [0, frame_end]}, - {"euler_xyz": [0,-60,0], "key_frame_val": [-10, 15], "key_frame_num": [0, frame_end]}, # ! - {"euler_xyz": [0,20,0], "key_frame_val": [25, -20], "key_frame_num": [0, frame_end]}, - {"euler_xyz": [0,40,0], "key_frame_val": [15, -20], "key_frame_num": [0, frame_end]}, # ! - {"euler_xyz": [0,60,0], "key_frame_val": [10, -15], "key_frame_num": [0, frame_end]}, # ! - {"euler_xyz": [0,0,0], "key_frame_val": [-20, 5, -20], "key_frame_num": [0, frame_mid, frame_end]}, - {"euler_xyz": [0,0,0], "key_frame_val": [20, -5, 20], "key_frame_num": [0, frame_mid, frame_end]}, - {"euler_xyz": [0,-90,0], "key_frame_val": [20, 5, 20], "key_frame_num": [0, frame_mid, frame_end]}, # ? - # {"euler_xyz": [0,0,0], "key_frame_val": [-10, 20, -10], "key_frame_num": [0, frame_mid, frame_end]}, - # {"euler_xyz": [0,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, -] +import time +from fy.collision_free_fall import CollisionScene def main() -> None: FLAGS = get_args() + logging.basicConfig(level=FLAGS.logging_level) output_dirname = "render_output" if not(FLAGS.save_states) else "output_temp" @@ -49,7 +28,7 @@ def main() -> None: level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ - logging.FileHandler("output/log"), + logging.FileHandler(f"{output_dirname}/log"), # logging.StreamHandler() ] ) @@ -70,69 +49,100 @@ def main() -> None: logger = logging.getLogger() logger.addHandler(handler) - - num_per_cls = 50 - max_trails = 40 if not(FLAGS.debug) else 10 + num_per_cls = 1000 + max_trails = 5000 test_cls_all = { - "solidity": SolidityTestScene, - # "continuity": ContinuityTestScene, - # "Support": SupportTestScene, - # "collision": CollisionTestScene - # "Permanance": PermananceTestScene + # "solidity": SolidityTestScene, + # "collision": CollisionTestScene, + # "collision_free_fall": CollisionScene, + # "Permanance": PermananceTestScene , + "Continuity": ContinuityTestScene, + # "Support": SupportTestScene, + } for test_name, test_cls in test_cls_all.items(): + # check if exist rendered test in the output folder + # if exist, start from the last one + scene_output_dir = f"{output_dirname}/{test_name}/" + # if os.path.exists(scene_output_dir): + # n = len(os.listdir(scene_output_dir)) + # logging.info(f"Found {n} rendered test in the output folder. Start from the next one.") + # else: + # n = 0 n = 0 - for i in tqdm(range(max_trails)): - output_dir = f"{output_dirname}/{test_name}/scene_{i}/" - if os.path.isdir(output_dir): - if any([True for _ in os.listdir(output_dir)]): - logging.warning(f"Directory {output_dir} already exists, skip rendering the current scene") - continue - - print(f"========== Rendering {test_name} test {i} ===========") - video_dir = f"{output_dirname}/{test_name}/videos/" - FLAGS.job_dir = output_dir - # FLAGS.camera_path_config = choice(path_template) - - if test_name in ["Support", "continuity"]: - FLAGS.move_camera = False - generate_test_scene(test_cls, FLAGS, output_dir, video_dir, i) - n += 1 - # if n >= num_per_cls: - # break + for i in range(max_trails): + logging.info(f"========== Rendering {test_name} test {n} ===========") + output_dir = f"{output_dirname}/{test_name}/scene_{n}/" + + while True: + if os.path.isdir(output_dir): + # if any([True for _ in os.listdir(output_dir)]): + if os.path.isfile(os.path.join(output_dir, "metadata.json")): + logging.warning(f"Directory {output_dir} already exists, skip rendering the current scene") + n += 1 + output_dir = f"{output_dirname}/{test_name}/scene_{n}/" + continue + break - -def generate_test_scene(test_class, FLAGS, output_dir, video_dir, i) -> None: + FLAGS.job_dir = output_dir + try: + generate_test_scene(test_cls, FLAGS, output_dir, n) + n += 1 + if n >= num_per_cls: + break + except Exception as e: + logging.error(f"Error rendering {test_name} test {n}: {e}\n Skipping to the next one.") + # if debug is on, raise the exception + if FLAGS.debug or True: + raise + continue + +def generate_test_scene(test_class, FLAGS,output_dir, i) -> None: + video_dir = os.path.join(output_dir.rsplit('/', 2)[0], "videos/") if not(os.path.exists(video_dir)): os.makedirs(video_dir) + # print(video_dir); return + + with test_class(FLAGS) as test_scene: # first prepare the scene - print("Preparing the scene") + logging.info("Preparing the scene") test_scene.prepare_scene() test_scene.write_metadata() # render the violation state - test_scene.change_output_dir( output_dir + "violation" ) - - print("Rendering the violation state") - # igore rendering if debug is on - if True and not FLAGS.debug: + + if FLAGS.render_non_violate_video: + # test_scene.load_non_violation_scene() + test_scene.change_output_dir( output_dir + "non_violation_view_1" ) + logging.info("Rendering the non-violation video") + start_time = time.time() + test_scene.load_non_violation_scene() test_scene.render(save_to_file=True) - write_video(output_dir + "violation/", video_dir + f"violation_{i}.mp4") - - # load the non-violation state and render it - print("Loading the non-violation state") - test_scene.load_non_violation_scene() - test_scene.change_output_dir( output_dir + "non_violation" ) - print("Rendering the non-violation state") - - # igore rendering if debug is on - if True and not FLAGS.debug: + # write_video(output_dir + "non_violation/", output_dir + "non_violation.mp4") + logging.info(f"Rendering the non-violation video took {time.time() - start_time} seconds") + + if FLAGS.render_multiview and test_scene.alternative_camera_pos: + logging.info("Rendering the non-violation video with alternative camera positions") + test_scene.change_output_dir( output_dir + "non_violation_view_2" ) + start_time = time.time() + test_scene.render_alternative_view(save_to_file=True) + # write_video(output_dir + "non_violation_multiview/", output_dir + "non_violation_multiview.mp4") + logging.info(f"Rendering the non-violation video with alternative camera positions took {time.time() - start_time} seconds") + + if FLAGS.render_violate_video: + # render the violation state + test_scene.change_output_dir( output_dir + "violation" ) + test_scene.load_violation_scene() + logging.info("Rendering the violation video") + start_time = time.time() test_scene.render(save_to_file=True) - write_video(output_dir + "non_violation/", video_dir + f"non_violation_{i}.mp4") - + # write_video(output_dir + "violation/", output_dir + "violation.mp4") + logging.info(f"Rendering the violation video took {time.time() - start_time} seconds") + if __name__ == "__main__": main() + \ No newline at end of file diff --git a/fy/run_tiancheng_old.py b/fy/run_tiancheng_old.py new file mode 100644 index 000000000..208e4ad7e --- /dev/null +++ b/fy/run_tiancheng_old.py @@ -0,0 +1,148 @@ + + + +from fy.utils import get_args +import logging +import kubric as kb +import imageio +from fy.utils import write_video +from fy.solidity import SolidityTestScene +from fy.collision import CollisionTestScene +from fy.permanance import PermananceTestScene +from fy.continuity import ContinuityTestScene +from fy.support import SupportTestScene +from tqdm import tqdm +import colorlog +import os + +from etils import epath +from random import choice +print(flush=True) +## TODO: set frame num from arg +frame_mid = 18 +frame_end = 36 +output_dirname = "render_output" +path_template = [ + {"euler_xyz": [0,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, + {"euler_xyz": [-25,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, # ! + {"euler_xyz": [0,-20,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, + {"euler_xyz": [0,-40,0], "key_frame_val": [-15, 20], "key_frame_num": [0, frame_end]}, + {"euler_xyz": [0,-60,0], "key_frame_val": [-10, 15], "key_frame_num": [0, frame_end]}, # ! + {"euler_xyz": [0,20,0], "key_frame_val": [25, -20], "key_frame_num": [0, frame_end]}, + {"euler_xyz": [0,40,0], "key_frame_val": [15, -20], "key_frame_num": [0, frame_end]}, # ! + {"euler_xyz": [0,60,0], "key_frame_val": [10, -15], "key_frame_num": [0, frame_end]}, # ! + {"euler_xyz": [0,0,0], "key_frame_val": [-20, 5, -20], "key_frame_num": [0, frame_mid, frame_end]}, + {"euler_xyz": [0,0,0], "key_frame_val": [20, -5, 20], "key_frame_num": [0, frame_mid, frame_end]}, + {"euler_xyz": [0,-90,0], "key_frame_val": [20, 5, 20], "key_frame_num": [0, frame_mid, frame_end]}, # ? + # {"euler_xyz": [0,0,0], "key_frame_val": [-10, 20, -10], "key_frame_num": [0, frame_mid, frame_end]}, + # {"euler_xyz": [0,0,0], "key_frame_val": [-20, 20], "key_frame_num": [0, frame_end]}, +] + +def main() -> None: + FLAGS = get_args() + logging.basicConfig(level=FLAGS.logging_level) + output_dirname = "render_output" if not(FLAGS.save_states) else "output_temp" + + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.FileHandler("output/log"), + # logging.StreamHandler() + ] + ) + + + handler = colorlog.StreamHandler() + handler.setFormatter(colorlog.ColoredFormatter( + "%(log_color)s %(asctime)s %(levelname)s:%(message)s", + log_colors={ + 'DEBUG': 'white', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'bold_red', + } + )) + + logger = logging.getLogger() + logger.addHandler(handler) + + + num_per_cls = 50 + max_trails = 20 if not(FLAGS.debug) else 10 + test_cls_all = { + # "solidity": SolidityTestScene, + # "continuity": ContinuityTestScene, + # "Support": SupportTestScene, + "collision": CollisionTestScene + # "Permanance": PermananceTestScene + } + for test_name, test_cls in test_cls_all.items(): + n = 0; i = 0 + for i in tqdm(range(max_trails)): + output_dir = f"{output_dirname}/{test_name}/scene_{i}/" + if os.path.isdir(output_dir): + # if any([True for _ in os.listdir(output_dir)]): + if os.path.isfile(os.path.join(output_dir, "metadata.json")): + logging.warning(f"Directory {output_dir} already exists, skip rendering the current scene") + continue + + print(f"========== Rendering {test_name} test {i} ===========") + video_dir = f"{output_dirname}/{test_name}/videos/" + FLAGS.job_dir = output_dir + # FLAGS.camera_path_config = choice(path_template) + + if test_name in ["Support", "continuity"]: + FLAGS.move_camera = False + + while True: + try: + generate_test_scene(test_cls, FLAGS, output_dir, video_dir, i) + i += 1 + break + except Exception as e: + logging.error(f"Error rendering collision test {n}: {e}\n Skipping to the next one.") + # if debug is on, raise the exception + # if FLAGS.debug: + # raise + continue + # if n >= num_per_cls: + # break + + + +def generate_test_scene(test_class, FLAGS, output_dir, video_dir, i) -> None: + if not(os.path.exists(video_dir)): + os.makedirs(video_dir) + with test_class(FLAGS) as test_scene: + # first prepare the scene + print("Preparing the scene") + test_scene.prepare_scene() + test_scene.write_metadata() + + # render the violation state + test_scene.change_output_dir( output_dir + "violation" ) + + print("Rendering the violation state") + # igore rendering if debug is on + if True and not FLAGS.debug: + test_scene.render(save_to_file=True) + write_video(output_dir + "violation/", video_dir + f"violation_{i}.mp4") + + # load the non-violation state and render it + print("Loading the non-violation state") + test_scene.load_non_violation_scene() + test_scene.change_output_dir( output_dir + "non_violation" ) + print("Rendering the non-violation state") + + # igore rendering if debug is on + if True and not FLAGS.debug: + test_scene.render(save_to_file=True) + write_video(output_dir + "non_violation/", video_dir + f"non_violation_{i}.mp4") + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/fy/run_watch_tiancheng.py b/fy/run_watch_tiancheng.py new file mode 100644 index 000000000..6f3f3e6dc --- /dev/null +++ b/fy/run_watch_tiancheng.py @@ -0,0 +1,61 @@ +""" Run the script as subprocess, restart it if it is killed by the system. """ +import os +import subprocess +import argparse +import time +def check_if_job_finished(num_per_cls: int, test_scene_cls) -> bool: + # check if output/{test_scene} has {num_per_cls} folders + for test_scene in test_scene_cls: + scene_output_dir = f"output/{test_scene}/" + if os.path.exists(scene_output_dir): + n = len(os.listdir(scene_output_dir)) + if n >= num_per_cls: + print(f"Found {n} rendered test in the {scene_output_dir} folder. Job finished for {test_scene}.") + continue + print(f"Job not finished. Found {n} rendered test in the {scene_output_dir} folder.") + return False + return True + +def run_watch(): + parser = argparse.ArgumentParser() + parser.add_argument("--num_per_cls", type=int, required=False) + parser.add_argument("--test_scene_cls",nargs='+', required=False) # test scenes + # rest of the arguments as list + + args, other_args = parser.parse_known_args() + print(args) + print(other_args) + + while True: + # check if the job is finished + + # if check_if_job_finished(args.num_per_cls, args.test_scene_cls): + # print("Job finished.") + # break + + all_args = ["/bin/python", "fy/run_tiancheng.py", "--render_non_violate_video", "--render_multiview", "--test_scene_cls", "1000"] + other_args + print("=============================================================") + print("Restarting the job with the following arguments:") + print(" ".join(all_args)) + print("=============================================================") + proc = subprocess.Popen(all_args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT) + + while True: + # first check if the job is killed + if proc.poll() is not None: + print("Job is killed. Restarting the job.") + break + # read all the output since the last read + output = proc.stdout.read(200) + + if output == b'' and proc.poll() is not None: + break + if output: + print(output.strip().decode("utf-8", 'ignore')) + # print("Still runing...") + # wait for 1 second + time.sleep(1) + + +if __name__ == "__main__": + run_watch() \ No newline at end of file diff --git a/fy/utils.py b/fy/utils.py index 6da81eee2..d7323297c 100644 --- a/fy/utils.py +++ b/fy/utils.py @@ -43,6 +43,7 @@ def get_args(): parser.add_argument("--generate_violation", type=bool, default=False) # generate violation results # parser.add_argument("--render_both_results", type=txt2bool, default=True) # render both violation and non-violation results + parser.add_argument("--add_back_camera", type=bool, default=True) parser.add_argument("--move_camera", type=bool, default=True) # move camera parser.add_argument("--scene_type", type=str, default="indoor") # scene type indoor | hdri | both @@ -228,7 +229,7 @@ def set_name(name): obj_bpy.name = name break -def getVisibleVertexFraction(obj_name, rng, sample_num=1000): +def getVisibleVertexFraction(obj_name, rng, camera_name="camera", sample_num=1000): """ Calculates and returns the fraction of vertices of a given object that are visible from a given camera position. @@ -244,7 +245,7 @@ def getVisibleVertexFraction(obj_name, rng, sample_num=1000): Returns: - float: The fraction of vertices of the object that are visible from the camera position. """ - camera = bpy.data.objects['camera'] + camera = bpy.data.objects[camera_name] obj = bpy.data.objects[obj_name] camera_loc = [camera.matrix_world[0][3], camera.matrix_world[1][3], camera.matrix_world[2][3]] @@ -255,6 +256,7 @@ def getVisibleVertexFraction(obj_name, rng, sample_num=1000): num_vert_in_fov = 0 # number of vertices in the camera's field of view vert_list = list(obj.data.vertices) + sampled_verts = sample(vert_list, sample_num) if sample_num < len(vert_list) else vert_list sample_num = len(sampled_verts) for vert in sampled_verts: @@ -269,7 +271,7 @@ def getVisibleVertexFraction(obj_name, rng, sample_num=1000): return num_vert_in_fov / sample_num -def isPointVisible(pt, obj_names): +def isPointVisible(pt, obj_names, camera_name="camera"): """ Calculates and returns the fraction of vertices of a given object that are visible from a given camera position. @@ -285,7 +287,7 @@ def isPointVisible(pt, obj_names): Returns: - float: The fraction of vertices of the object that are visible from the camera position. """ - camera = bpy.data.objects['camera'] + camera = bpy.data.objects[camera_name] camera_loc = [camera.matrix_world[0][3], camera.matrix_world[1][3], camera.matrix_world[2][3]] dist = np.array(pt) - np.array(camera_loc) @@ -299,7 +301,8 @@ def isPointVisible(pt, obj_names): outcome = (target.name in obj_names) if not(outcome): - logging.warning(f"The object {obj_names} is blocked in at least one frame, {target.name} detected instead") + pass + # logging.warning(f"The object {obj_names} is blocked in at least one frame, {target.name} detected instead") return outcome @@ -390,6 +393,9 @@ def align_can_objs(obj): # otherwise set the longest axis as the principal axis axis = np.argmax(np.array([x_size, y_size, z_size])) + radius = [y_size, z_size, x_size][axis] / 2 + height = [x_size, y_size, z_size][axis] + axis_rot_mapping = { 0: kb.Quaternion(axis=[0, 0, 1], degrees=-90), 1: kb.Quaternion(axis=[1, 0, 0], degrees=0), @@ -399,7 +405,11 @@ def align_can_objs(obj): quaternion_tf = axis_rot_mapping[axis] obj.quaternion = quaternion_tf * obj.quaternion - return axis + x_iccentric = obj.aabbox[1][0] + obj.aabbox[0][0] + y_iccentric = obj.aabbox[1][1] + obj.aabbox[0][1] + valid = np.abs(x_iccentric) <= 0.01 and np.abs(x_iccentric) <= 0.01 + + return axis, radius, height, valid @@ -423,9 +433,9 @@ def align_can_objs(obj): def spherical_to_cartesian(r_range=[3, 4], theta_range=[60, 80], phi_range=[-30, 30]): r = np.random.uniform(r_range[0], r_range[1]) theta = np.random.uniform(theta_range[0], theta_range[1]) * np.pi/180 - phi = np.random.uniform(np.random.uniform(phi_range[0], phi_range[1])) * np.pi/180 - phi -= np.pi / 2 + phi = np.random.uniform(phi_range[0], phi_range[1]) * np.pi/180 + phi -= np.pi / 2 x = r * np.sin(theta) * np.cos(phi) y = r * np.sin(theta) * np.sin(phi) z = r * np.cos(theta) diff --git a/run_tiancheng.sh b/run_tiancheng.sh new file mode 100755 index 000000000..cf0485573 --- /dev/null +++ b/run_tiancheng.sh @@ -0,0 +1 @@ +python fy/run_tiancheng.py --render_non_violate_video --render_multiview --test_scene_cls 100