Project: s41467-024-47459-2 | Function: calculate_best_fit_plane_normal (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Principal component analysis algorithm for computing the optimal whisking plane normal vector from whisker endpoint coordinates using covariance matrix eigenvalue decomposition

**KEY TERMS:** {
  "all_points": "whisker base and tip coordinates array",
  "centroid": "mean position of all whisker points",
  "covariance_matrix": "covariance matrix of centered coordinates",
  "eigenvalues": "principal component variances",
  "eigenvectors": "principal component directions",
  "normal": "whisking plane normal vector (minimum variance direction)"
}

**CONTEXT CODE:**     def __init__(self, data=None):
        self.data = data

    def transposed(self):
        return self

class MockContext:
    selected_objects = []

class MockOps:
    class object:
        @staticmethod
        def empty_add(location=None, align=None):
            pass

class MockBpy:
    context = MockContext()
    ops = MockOps()

# Use Mock system if bpy is not available
try:
    import bpy
    from mathutils import Vector, Matrix
except ImportError:
    bpy = MockBpy()
    Vector = MockVector
    Matrix = MockMatrix

import numpy as np

def get_selected_curves():
    selected_objects = bpy.context.selected_objects
    selected_curves = [obj for obj in selected_objects if obj.type == 'CURVE']
    print(f"selected curves: {selected_curves}")
    return selected_curves

def calculate_mean_curve_direction(selected_curves):
#calculates the mean curve direction on which the inclination is based
    mean_direction = np.zeros(3)
    for curve in selected_curves:
        control_points = curve.data.splines[0].points
        num_points = len(control_points)
        direction_vector = (control_points[num_points-1].co - control_points[0].co).xyz
        mean_direction += direction_vector

    mean_direction /= len(selected_curves)
    mean_direction /= np.linalg.norm(mean_direction)  # Normalize the mean direction vector
    print(f"the mean curve direction is {mean_direction}")
    return mean_direction

def calculate_best_fit_plane_normal(selected_curves):
    # MASKED: Principal component analysis algorithm for computing the optimal whisking plane normal vector from whisker endpoint coordinates using covariance matrix eigenvalue decomposition
    raise NotImplementedError("This function is masked for testing purposes.")

def create_empty_with_orientation(location, x_axis, z_axis):
#creates an empty object with the desired orientations
    bpy.ops.object.empty_add(location=location, align='WORLD')
    empty_object = bpy.context.active_object
    y_axis = np.cross(z_axis, x_axis)
    rotation_matrix = Matrix([x_axis, y_axis, z_axis]).transposed()
    empty_object.rotation_euler = rotation_matrix.to_euler('XYZ')
    return empty_object

selected_curves = get_selected_curves()
if len(selected_curves) == 4:
    mean_direction = calculate_mean_curve_direction(selected_curves)
    best_fit_plane_normal = calculate_best_fit_plane_normal(selected_curves)
    centroid_location = np.mean([point.co.xyz for curve in selected_curves for point in curve.data.splines[0].points], axis=0)
    empty_object = create_empty_with_orientation(centroid_location, mean_direction, best_fit_plane_normal)
    empty_object.empty_display_type = 'ARROWS'
    empty_object.empty_display_size = 0.1
    bpy.context.view_layer.objects.active = empty_object
else:
    print("Please select exactly 4 curve objects.")


Complete the function implementation replacing "raise NotImplementedError".