def execute(self, context: bpy.types.Context) -> set: """Filter the reconstructed point cloud. Returns: set -- {'FINISHED'} """ obj = context.view_layer.objects.active model = ReconstructionsManager.get_model_by_uuid(obj['sfmflow_model_uuid']) model.filter_model(ReconstructionsManager.gt_kdtree, self.filter_distance_threshold) return {'FINISHED'}
def execute(self, context: bpy.types.Context) -> set: """Clear the point cloud filtering, restore the full cloud. Returns: set -- {'FINISHED'} """ obj = context.view_layer.objects.active model = ReconstructionsManager.get_model_by_uuid(obj['sfmflow_model_uuid']) model.filter_model_clear() return {'FINISHED'}
def poll(cls, context: bpy.types.Context) -> bool: """Panel's enabling condition. The operator is enabled only if a filtered 3D reconstruction is selected. Arguments: context {bpy.types.Context} -- poll context Returns: bool -- True to enable, False to disable """ if is_active_object_reconstruction(context): # the selected object is a UI control for a reconstruction model = ReconstructionsManager.get_model_by_uuid(context.view_layer.objects.active['sfmflow_model_uuid']) return model.has_filter_model() return False
def heavy_load(self, model_uuid: str, **kwargs: Dict) -> None: # pylint: disable=arguments-differ """Align 3D reconstruction to the ground truth geometry. This workload is executed in a dedicated thread. Returns: set -- {'FINISHED', ‘CANCELLED’} """ model = ReconstructionsManager.get_model_by_uuid(model_uuid) logger.info("Starting reconstructed model registration...") # if self.alignment_mode == "cloud_align.matrix": # use user's matrix to align self.progress_string = "Aligning model using matrix" align_matrix = Matrix( (self.alignment_matrix_row1, self.alignment_matrix_row2, self.alignment_matrix_row3, self.alignment_matrix_row4)) model.apply_registration_matrix(align_matrix) msg = "Applied registration matrix to model `{}`.".format( model.name) elif self.alignment_mode == "cloud_align.auto": # use ICP alignment self.progress_string = "Aligning model using ICP" if not self.use_custom_params: error = model.register_model(ReconstructionsManager.gt_points, ReconstructionsManager.gt_kdtree) else: error = model.register_model( ReconstructionsManager.gt_points, ReconstructionsManager.gt_kdtree, max_iterations=self.max_iterations, samples=self.samples_percentage, use_filtered_cloud=self.use_filtered_cloud) msg = "Reconstructed model `{}` registered to ground truth (mean error: {:.3f}).".format( model.name, error) else: msg = "Unknown alignment mode: {}".format(self.alignment_mode) self.progress_string = msg self.exit_code = -1 logger.error(msg) return # self.progress_string = msg self.exit_code = 0 logger.info(msg)
def execute(self, context: bpy.types.Context) -> set: """Evaluates the reconstructed point cloud and camera poses. Returns: set -- in {'FINISHED', 'CANCELLED'} """ obj = context.view_layer.objects.active model = ReconstructionsManager.get_model_by_uuid( obj['sfmflow_model_uuid']) result = model.evaluate(context.scene, ReconstructionsManager.gt_kdtree, self.use_filtered_cloud) # # build full evaluation result dictionary out_data = { "unit_system": context.scene.unit_settings.system, "length_unit": context.scene.unit_settings.length_unit, "name": obj.name, "name_internal": model.name, "project_name": bpy.path.basename(bpy.data.filepath), } out_data.update({ f'pc_{k}': ("{:.6f}".format(v) if isinstance(v, float) else v) for k, v in result[0].items() }) out_data.update({ f'cam_{k}': ("{:.6f}".format(v) if isinstance(v, float) else v) for k, v in result[1].items() }) # len_scale = context.scene.unit_settings.scale_length len_unit = SFMFLOW_OT_evaluate_reconstruction.LENGTH_UNIT[ context.scene.unit_settings.length_unit] flags = 'w' if self.overwrite_evaluation_file else 'a' try: # write .txt file filepath = bpy.path.abspath(self.evaluation_filepath) os.makedirs(os.path.dirname(filepath), exist_ok=True) with open(filepath, mode=flags) as f: f.write("Project: {}\n".format(out_data["project_name"])) f.write( "Evaluation of Reconstruction model '{}' (internal name '{}')\n" .format(out_data["name"], out_data["name_internal"])) f.write("Scene measurement system: {}\n".format( out_data["unit_system"])) f.write("Scene length unit: {}\n".format( out_data["length_unit"])) # f.write("Point cloud evaluation:\n") out_pc = result[0] f.write(" used filtered point cloud: {}\n".format( out_pc['used_filtered_cloud'])) f.write(" filter threshold: {:.3f}\n".format( out_pc['filter_threshold'])) f.write(" full cloud size: {}\n".format( out_pc['full_cloud_size'])) f.write(" evaluated cloud size: {} ({:.1f}%)\n".format( out_pc['used_cloud_size'], out_pc['used_cloud_size_percent'] * 100)) f.write(" discarded points count: {}\n".format( out_pc['discarded_points'])) f.write(" distance mean: {:.3f}{}\n".format( out_pc['dist_mean'] * len_scale, len_unit)) f.write(" distance standard deviation: {:.3f}{}\n".format( out_pc['dist_std'] * len_scale, len_unit)) f.write(" distance min: {:.3f}{}\n".format( out_pc['dist_min'] * len_scale, len_unit)) f.write(" distance max: {:.3f}{}\n".format( out_pc['dist_max'] * len_scale, len_unit)) # f.write("Camera poses evaluation:\n") out_cam = result[1] f.write(" cameras count: {}\n".format( out_cam['camera_count'])) f.write(" reconstructed camera count: {} ({:.1f}%)\n".format( out_cam['reconstructed_camera_count'], out_cam['reconstructed_camera_percent'] * 100)) f.write(" distance mean: {:.3f}{}\n".format( out_cam['pos_mean'] * len_scale, len_unit)) f.write(" distance standard deviation: {:.3f}{}\n".format( out_cam['pos_std'] * len_scale, len_unit)) f.write(" distance min: {:.3f}{}\n".format( out_cam['pos_min'] * len_scale, len_unit)) f.write(" distance max: {:.3f}{}\n".format( out_cam['pos_max'] * len_scale, len_unit)) f.write(" rotation difference mean: {:.3f}°\n".format( out_cam['rot_mean'])) f.write(" rotation difference standard deviation: {:.3f}°\n". format(out_cam['rot_std'])) f.write(" rotation difference min: {:.3f}°\n".format( out_cam['rot_min'])) f.write(" rotation difference max: {:.3f}°\n".format( out_cam['rot_max'])) f.write( " look-at direction difference mean: {:.3f}°\n".format( out_cam['lookat_mean'])) f.write( " look-at direction difference standard deviation: {:.3f}°\n" .format(out_cam['lookat_std'])) f.write( " look-at direction difference min: {:.3f}°\n".format( out_cam['lookat_min'])) f.write( " look-at direction difference max: {:.3f}°\n".format( out_cam['lookat_max'])) # f.write("\n\n") # # write .csv file csv_filepath = bpy.path.abspath( self.evaluation_filepath)[:-3] + "csv" with open(csv_filepath, flags, newline='') as csv_f: writer = DictWriter(csv_f, fieldnames=out_data.keys()) if csv_f.tell() == 0: writer.writeheader() writer.writerow(out_data) # msg = "Evaluation written to file: {}|.csv".format( self.evaluation_filepath) logger.info(msg) self.report({'INFO'}, msg) return {'FINISHED'} # except OSError as e: msg = str(e) logger.error(e) self.report({'ERROR'}, msg) return {'CANCELLED'}
def draw(self, context): """Panel's layout""" layout = self.layout scene = context.scene properties = scene.sfmflow obj = context.view_layer.objects.active # # reconstruction workspace folder row = layout.split(factor=0.33, align=True) row.label(text="Reconstruction workspace") row.prop(properties, "reconstruction_path", text="") # # run pipelines layout.row().separator() row = layout.split(factor=0.33, align=True) row.prop(properties, "reconstruction_pipeline", text="") row.operator("sfmflow.run_pipelines", icon='SETTINGS') # # import reconstruction layout.row().separator() row = layout.row(align=True) row.operator("sfmflow.import_reconstruction", icon='IMPORT') row.operator("sfmflow.sample_geometry_gt", text="", icon='GROUP_VERTEX') # # reconstruction display row = layout.row(align=True) if obj: row.prop(obj.sfmflow, "show_recon_cameras", text="Show cameras", toggle=True) row.prop(obj.sfmflow, "show_recon_always", toggle=True) row.enabled = True if is_active_object_reconstruction( context) else False # # reconstruction filtering col = layout.column(align=True) row = col.row(align=True) row.operator("sfmflow.reconstruction_filter", icon='FILTER') row.operator("sfmflow.reconstruction_filter_clear", text="", icon='X') row = col.row(align=True) if context.view_layer.objects.active is not None: enable = False row.prop(context.view_layer.objects.active.sfmflow, "cloud_filtering_display_mode", expand=True) if is_active_object_reconstruction(context): model = ReconstructionsManager.get_model_by_uuid( context.view_layer.objects.active['sfmflow_model_uuid']) if model.has_filter_model(): enable = True row.enabled = enable # # reconstruction fine alignment layout.operator("sfmflow.align_reconstruction", icon='TRACKING_REFINE_FORWARDS') # # reconstruction evaluation layout.row().separator() layout.operator("sfmflow.evaluate_reconstruction", icon='DRIVER_DISTANCE')