Project: s41467-024-47459-2 | Function: calculate_tip (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:** Spherical coordinate system conversion algorithm that transforms 3D Cartesian whisker tip coordinates to visual space elevation and azimuth angles for tacto-visual convergence analysis

**KEY TERMS:** {
  "direction": "3D whisker tip position vector",
  "directionxy": "projection onto xy-plane √(y²+x²)",
  "phi_t": "elevation angle in visual space coordinate system",
  "theta_t": "azimuth angle in visual space coordinate system",
  "get_global": "global Cartesian coordinates of whisker tips"
}

**CONTEXT CODE:** 
    def evaluated_depsgraph_get(self):
        return MockDepsgraph()

class MockOps:
    pass

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

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

import openpyxl
import os
import math

def get_origin(curve_object):    
#This function reads the objects origin    
    depsgraph = bpy.context.evaluated_depsgraph_get()
    evaluated_curve = curve_object.evaluated_get(depsgraph)
    origin = evaluated_curve.location
    return origin


def get_tip (curve_object):
#This function reads the tip position of the whisker relative to its origin
    curve = curve_object.data.splines[0]
    curve_data = curve_object.data
    spline = curve_data.splines[0]
    num_points = len(spline.points)
    p0 = curve.points[0].co
    p1 = curve.points[num_points-1].co
    whisker = (p1 - p0)
    return whisker

def get_global(curve_object):
#This Function combines the previous two to return the whisker tips global carthesian coordinates   
    whisker=get_tip(curve_object)
    origin=get_origin(curve_object)
    global_tip = (whisker[0] + origin[0], whisker[1] + origin[1], whisker[2] + origin[2])
    vektor_global = vector_3d = Vector(global_tip)
    return vektor_global

def calculate_tip(curve_obj):
    # MASKED: Spherical coordinate system conversion algorithm that transforms 3D Cartesian whisker tip coordinates to visual space elevation and azimuth angles for tacto-visual convergence analysis
    raise NotImplementedError("This function is masked for testing purposes.")

def give_tip(curve_obj):
#This function exports the coordinates to excel and the console
#The variables for finding column and row are based on the names of the whisker object
#This version is used for exporting all whiskers of all 5 animals from one position
#They can be adapted as desired      
    
    curve_data = curve_obj.data
    if curve_data.splines.active.type == 'POLY':
        
#define file name and directory
        file_name = f"file_name.xlsx"
        directory = f"/directory/"

# Join the directory path with the file name
        file_path = os.path.join(directory, file_name)
        points = [(point.co.x, point.co.y, point.co.z) for point in curve_data.splines.active.points]
        
#The indentation i can be used if more positions of the same animal are exported
#remove the comment and add the positions to the whisker object name and script
        i = 0
        Run = ""
#        if "Position name 1" in curve_obj.name.lower():
#            Run ="Position name 1"
#            i=0
#        if "Position name 2" in curve_obj.name.lower():
#            Run ="Position name 2"
#            i=1
#expand if needed for more positions and indentations
        
        #Ectract the animal-ID (a)
        if "007" in curve_obj.name.lower():
            animal="Animal 1"
            a=0
        if "008" in curve_obj.name.lower():
            animal="Animal 2"
            a=1
        if "009" in curve_obj.name.lower():
            animal="Animal 3"
            a=2
        if "010" in curve_obj.name.lower():
            animal="Animal 4"
            a=3
        if "011" in curve_obj.name.lower():
            animal="Animal 5"
            a=4
            
        #Extract the names of the whisker (n)
        if "alpha" in curve_obj.name.lower():
              whisker_name="Alpha"
              n=1
        if "a1" in curve_obj.name.lower():
              whisker_name="A1"
              n=2
        if "a2" in curve_obj.name.lower():
              whisker_name="A2"
              n=3
        if "a3" in curve_obj.name.lower():
              whisker_name="A3"
              n=4
        if "a4" in curve_obj.name.lower():
              whisker_name="A4"
              n=5
        if "beta" in curve_obj.name.lower():
              whisker_name="Beta"
              n=6
        if "b1" in curve_obj.name.lower():
              whisker_name="B1"
              n=7
        if "b2" in curve_obj.name.lower():
              whisker_name="B2"
              n=8
        if "b3" in curve_obj.name.lower():
              whisker_name="B3"
              n=9
        if "b4" in curve_obj.name.lower():
              whisker_name="B4"
              n=10
        if "gamma" in curve_obj.name.lower():
              whisker_name="Gamma"
              n=11        
        if "c1" in curve_obj.name.lower():
              whisker_name="C1"
              n=12
        if "c2" in curve_obj.name.lower():
              whisker_name="C2"
              n=13
        if "c3" in curve_obj.name.lower():
              whisker_name="C3"
              n=14
        if "c4" in curve_obj.name.lower():
              whisker_name="C4"
              n=15  
        if "delta" in curve_obj.name.lower():
              whisker_name="Delta"
              n=16 
        if "d1" in curve_obj.name.lower():


Complete the function implementation replacing "raise NotImplementedError".