Exemple #1
0
    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'}
Exemple #2
0
    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'}
Exemple #3
0
    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')