{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f570d10d",
   "metadata": {},
   "source": [
    "# Spin test 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "e0505468",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "from pathlib import Path\n",
    "import numpy as np\n",
    "from matplotlib import pyplot as plt\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aaf2fc9",
   "metadata": {},
   "source": [
    "Import video"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "172b0b21",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opened: True, FPS: 59.94018018162597, Total Frames: 332.0\n"
     ]
    }
   ],
   "source": [
    "video_number = \"7\"\n",
    "# Define the relative path to the video file\n",
    "notebook_dir = Path().resolve()\n",
    "project_root = notebook_dir.parent.parent\n",
    "video_path = (\n",
    "    project_root\n",
    "    / \"data\"\n",
    "    / f\"recording_{video_number}\"\n",
    "    / f\"Recording_{video_number}.mp4\"\n",
    ")\n",
    "video_path = str(video_path)\n",
    "\n",
    "# Load the video\n",
    "cap = cv2.VideoCapture(video_path)\n",
    "\n",
    "# Check\n",
    "print(\n",
    "    f\"Opened: {cap.isOpened()}, FPS: {cap.get(cv2.CAP_PROP_FPS)}, Total Frames: {cap.get(cv2.CAP_PROP_FRAME_COUNT)}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8174c8a",
   "metadata": {},
   "source": [
    "Import data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "debacf95",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame        208\n",
      "x         1408.0\n",
      "y          198.0\n",
      "radius      18.0\n",
      "Name: 208, dtype: object\n"
     ]
    }
   ],
   "source": [
    "# Define the path to the CSV file\n",
    "# input_data_path = project_root / \"notebook\" / \"ball_detection\" / \"intermediate_data\" / f\"Circle_positions_cleaned_{video_number}.csv\"\n",
    "input_data_path = (\n",
    "    project_root\n",
    "    / \"data\"\n",
    "    / \"auxiliary_data\"\n",
    "    / \"circle_positions\"\n",
    "    / f\"Adjusted_positions_{video_number}.csv\"\n",
    ")\n",
    "# Load the CSV file into a DataFrame\n",
    "ball_df = pd.read_csv(input_data_path)\n",
    "\n",
    "\n",
    "new_df = pd.DataFrame(columns=[\"frame\", \"x\", \"y\", \"radius\"])\n",
    "new_df[\"frame\"] = range(0, ball_df.iloc[len(ball_df) - 1][\"frame\"])\n",
    "# Assign values from ball_df to new_df where the 'frame' matches\n",
    "new_df.update(ball_df.set_index(\"frame\"))\n",
    "ball_df = new_df.copy()\n",
    "print(ball_df.iloc[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0778fd78",
   "metadata": {},
   "source": [
    "Select a Frame"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "007357c2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Frame 83 successfully read.\n"
     ]
    }
   ],
   "source": [
    "# Set the video to the 30th frame (frame index starts from 0)\n",
    "frame_number = 83\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)\n",
    "\n",
    "# Read the frame\n",
    "ret, frame = cap.read()\n",
    "\n",
    "# Check if the frame was successfully read\n",
    "if ret:\n",
    "    print(f\"Frame {frame_number} successfully read.\")\n",
    "else:\n",
    "    print(f\"Failed to read frame {frame_number}.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0094e6f",
   "metadata": {},
   "source": [
    "Optical flow"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "7c6b4301",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "83 83\n",
      "78 78\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAakAAAGzCAYAAACVYeimAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAgqlJREFUeJztnQe4FNX5xs/MltvooBQFRHoVQUXQqFEiKvaSaIzBHgs27Ek00agYUzSWaDSJJrFF/dsLFuyKoChIL4JYKYLU23Z35v+cIbvznW/2HGaXvTD33vf3PBdm9sycOTvt7LzfN++xXNd1BQAAABBB7O3dAAAAAEAHOikAAACRBZ0UAACAyIJOCgAAQGRBJwUAACCyoJMCAAAQWdBJAQAAiCzopAAAAEQWdFIAAAAiCzopEEkeeOABYVmW+Pzzzxuk/t/+9rde/aVAtlHWJdu8PSnld2pITj31VLHLLrson8l2y/YDwEEnBUIxZ84c8bOf/UzstNNOoqysTHTp0kWcfPLJ3udbw0033SSefvppEeUbqryB5vubNGmSaMpkO1/616pVKzF06FBx5513ikwms13bY9u2aNeunTj00EPFlClTtOu999574phjjhEdO3b0zl3ZQf7iF78QX3zxhbaj/+677xr424CwxEMvCZotTz75pDjppJO8G8IZZ5whevTo4d0w/vGPf4gnnnhCPProo95NoNhO6vjjjxdHH3208vkpp5wiTjzxRO+msr2Rbfj73/8e+Hy33XYTUeLXv/61uOqqq0perzz2hx12mDe9bt068eKLL4oLLrhALFu2TPzhD38o+fbCtkd2kgsXLhR//etfxQ9/+EPx4YcfisGDByvL3nHHHeKiiy4Su+66q9fmzp07i3nz5nnH87///a/3XUaNGrXNvwMIDzopYOSzzz7zOgx5kb/99ttihx12yJXJi/8HP/iBV/7pp596y5SKWCzm/UWBeDzuPUVGHdlO+Vdqhg0bpnz/8847T4wYMUI8/PDD26WT4u2R56B8mrr77ru9Dos+QV188cVi33339Z56Kysrc2Xnnnuu2GeffbwfSFINaNu27Tb/HiAckPuAEXkTqq6uFvfee6/SQUk6dOgg/va3v4lNmzaJW265JSCZzJ8/X/z4xz/2JKL27dt7nVptbW1uObmMXPdf//pXTsKR8popJvXSSy+J/fffX7Rs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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Extract ball center and radius from ball_df\n",
    "\n",
    "ball_center = (\n",
    "    int(ball_df.iloc[frame_number][\"x\"]),\n",
    "    int(ball_df.iloc[frame_number][\"y\"]),\n",
    ")\n",
    "ball_radius = int(ball_df.iloc[frame_number][\"radius\"])\n",
    "\n",
    "offset = 2\n",
    "\n",
    "# Define the region of interest (ROI) around the ball\n",
    "x_min = max(ball_center[0] - ball_radius, 0) - offset\n",
    "x_max = min(ball_center[0] + ball_radius, frame.shape[1]) + offset\n",
    "y_min = max(ball_center[1] - ball_radius, 0) - offset\n",
    "y_max = min(ball_center[1] + ball_radius, frame.shape[0]) + offset\n",
    "roi = frame[y_min:y_max, x_min:x_max]\n",
    "copy_roi = roi.copy()\n",
    "ball_center_roi = (ball_center[0] - x_min + offset, ball_center[1] - y_min + offset)\n",
    "# Convert ROI to grayscale\n",
    "roi_gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "# Parameters for optical flow\n",
    "lk_params = dict(\n",
    "    winSize=(15, 15),\n",
    "    maxLevel=2,\n",
    "    criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03),\n",
    ")\n",
    "\n",
    "# selezione 100 punti manuali distribuiti su 10 cerchi concentrici\n",
    "manual_points = []\n",
    "num_rings = 10  # numero di cerchi concentrici\n",
    "points_per_ring = 10  # punti per ogni cerchio (10 × 10 = 100)\n",
    "max_radius = ball_radius * 0.99  # lascia un margine vicino al bordo\n",
    "\n",
    "for i in range(1, num_rings + 1):\n",
    "    r = (i / num_rings) * max_radius\n",
    "    for j in range(points_per_ring):\n",
    "        if j < 20 or j > 9:\n",
    "            theta = 2 * np.pi * j / points_per_ring\n",
    "            x = int(ball_center_roi[0] + r * np.cos(theta))\n",
    "            y = int(ball_center_roi[1] + r * np.sin(theta))\n",
    "            manual_points.append((x, y))\n",
    "\n",
    "# Converti in formato richiesto da calcOpticalFlowPyrLK\n",
    "p0 = np.array(manual_points, dtype=np.float32).reshape(-1, 1, 2)\n",
    "\n",
    "# Read the next frame for optical flow calculation\n",
    "ret, next_frame = cap.read()\n",
    "if not ret:\n",
    "    raise ValueError(f\"Failed to read the next frame after frame {frame_number}.\")\n",
    "\n",
    "# calculate the next ball center\n",
    "ball_center_next = (\n",
    "    int(ball_df.iloc[frame_number + 1][\"x\"]),\n",
    "    int(ball_df.iloc[frame_number + 1][\"y\"]),\n",
    ")\n",
    "# as radius I need to use the previous in ordet to have roi of the same size\n",
    "\n",
    "# Define the next ROI around the ball in the next frame\n",
    "x_min_next = max(ball_center_next[0] - ball_radius, 0) - offset\n",
    "x_max_next = min(ball_center_next[0] + ball_radius, next_frame.shape[1]) + offset\n",
    "y_min_next = max(ball_center_next[1] - ball_radius, 0) - offset\n",
    "y_max_next = min(ball_center_next[1] + ball_radius, next_frame.shape[0]) + offset\n",
    "next_roi = next_frame[y_min_next:y_max_next, x_min_next:x_max_next]\n",
    "ball_center_next_roi = (\n",
    "    ball_center_next[0] - x_min_next,\n",
    "    ball_center_next[1] - y_min_next,\n",
    ")\n",
    "\n",
    "next_roi_gray = cv2.cvtColor(next_roi, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "# Calculate optical flow\n",
    "p1, st, err = cv2.calcOpticalFlowPyrLK(roi_gray, next_roi_gray, p0, None, **lk_params)\n",
    "\n",
    "# store the flow vectors\n",
    "flow_vectors = []\n",
    "\n",
    "# Filter good points\n",
    "if p1 is not None and st is not None:\n",
    "    # # get the indeces where st==1 and err<10 -> more filtering\n",
    "    # good_indices = np.where((st.flatten() == 1) & (err < 20))[0]\n",
    "    # good_new = p1[good_indices].reshape(-1, 2)\n",
    "    # good_old = p0[good_indices].reshape(-1, 2)\n",
    "    # err_good = err[good_indices]\n",
    "    # print(good_indices)\n",
    "\n",
    "    good_new = p1[st == 1].reshape(-1, 2)\n",
    "    good_old = p0[st == 1].reshape(-1, 2)\n",
    "    print(len(good_new), len(good_old))\n",
    "\n",
    "    # Filter only the point in the ball\n",
    "    # Compute distances to the ball center\n",
    "    dist_old = np.linalg.norm(good_old[:, :] - ball_center_roi, axis=1)\n",
    "    dist_new = np.linalg.norm(good_new[:, :] - ball_center_next_roi, axis=1)\n",
    "\n",
    "    # Create a mask of points that are within the ball in both frames\n",
    "    within_radius_mask = (dist_old <= ball_radius) & (dist_new <= ball_radius)\n",
    "\n",
    "    # Apply the mask\n",
    "    good_old = good_old[within_radius_mask].reshape(-1, 2)\n",
    "    good_new = good_new[within_radius_mask].reshape(-1, 2)\n",
    "\n",
    "    print(len(good_new), len(good_old))\n",
    "\n",
    "    # Visualize the optical flow\n",
    "    for i, (new, old) in enumerate(zip(good_new, good_old)):\n",
    "        a, b = new.ravel()\n",
    "        c, d = old.ravel()\n",
    "        cv2.line(roi, (int(a), int(b)), (int(c), int(d)), (0, 255, 0), 2)\n",
    "        cv2.circle(roi, (int(a), int(b)), 2, (0, 0, 255), -1)\n",
    "        flow_vectors.append(\n",
    "            {\n",
    "                \"frame\": frame_number,\n",
    "                \"point_id\": i,\n",
    "                \"x_old\": c,\n",
    "                \"y_old\": d,\n",
    "                \"x_new\": a,\n",
    "                \"y_new\": b,\n",
    "                \"dx\": a - c,\n",
    "                \"dy\": b - d,\n",
    "            }\n",
    "        )\n",
    "        # print(f\"Point {i}: Old: ({c}, {d}), New: ({a}, {b}), dx: {a - c}, dy: {b - d}\")\n",
    "\n",
    "    # Display the ROI with optical flow\n",
    "    plt.imshow(cv2.cvtColor(roi, cv2.COLOR_BGR2RGB))\n",
    "    plt.title(\"Optical Flow in Ball ROI\")\n",
    "    plt.show()\n",
    "else:\n",
    "    print(\"No optical flow points detected.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "507bf044",
   "metadata": {},
   "source": [
    "Compute rotation and axis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "1b4cfc39",
   "metadata": {},
   "outputs": [],
   "source": [
    "distances = np.linalg.norm(good_new - good_old, axis=1)\n",
    "velocities = distances / (\n",
    "    1 / cap.get(cv2.CAP_PROP_FPS)\n",
    ")  # Convert to velocity in pixels per second\n",
    "z_3d_old = np.sqrt(\n",
    "    ball_radius**2\n",
    "    - (good_old[:, 0] - ball_center_roi[0]) ** 2\n",
    "    - (good_old[:, 1] - ball_center_roi[1]) ** 2\n",
    ")\n",
    "points_3d_old = np.array([good_old[:, 0], good_old[:, 1], z_3d_old]).T\n",
    "\n",
    "z_3d_new = np.sqrt(\n",
    "    ball_radius**2\n",
    "    - (good_new[:, 0] - ball_center_next_roi[0]) ** 2\n",
    "    - (good_new[:, 1] - ball_center_next_roi[1]) ** 2\n",
    ")\n",
    "points_3d_new = np.array([good_new[:, 0], good_new[:, 1], z_3d_new]).T\n",
    "\n",
    "# Calculate the 3D velocity vectors\n",
    "velocity_vectors_3d = points_3d_new - points_3d_old"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "2db3621d",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"for each point in points_3d_new, calculate its projection on the plane tangen to the ball surface \n",
    "    in the corrspondent poin in points_3d_old\n",
    "the projection is done by calculating the normal vector to the ball surface in the point of interest and \n",
    "    then projecting the point on this plane\n",
    "the normal vector is given by the vector from the center of the ball to the point of interest\n",
    "\"\"\"\n",
    "# normal vectors to the ball surface in the point of interest\n",
    "normal_vectors = points_3d_old / np.linalg.norm(points_3d_old, axis=1)[:, np.newaxis]\n",
    "\n",
    "# projection of the points on the plane tangent to the ball surface in the point of interest (they are the difference, not the points)\n",
    "projection_vectors = (\n",
    "    points_3d_new\n",
    "    - np.sum(points_3d_new * normal_vectors, axis=1)[:, np.newaxis] * normal_vectors\n",
    ")\n",
    "# print(projection_vectors)\n",
    "\n",
    "projected_points_new = points_3d_old + projection_vectors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "d76f8a62",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Rotation matrix:\n",
      " [[ 0.9827588  -0.00395973  0.1848498 ]\n",
      " [-0.01200492  0.9962944   0.08516646]\n",
      " [-0.18450205 -0.0859172   0.9790696 ]]\n",
      "Rotation angles (degrees): [-5.015083  10.632102  -0.6998633]\n",
      "Angular velocity (rad/s): [-5.2465467 11.122811  -0.7321645]\n",
      "Angular velocity magnitude (rad/s): 12.319872\n",
      "Axis of rotation (3D): [-181.27942  291.00708 -141.45093]\n",
      "Intersection point on the ball surface (3D): [ 25.416311 -40.800697  19.832151]\n",
      "Intersection point on the ball surface (2D): [81.41631126 15.19930267]\n"
     ]
    }
   ],
   "source": [
    "def estimate_rotation_matrix(P, Q):\n",
    "    \"\"\"\n",
    "    Estimate rotation matrix that aligns P to Q (P and Q are Nx3)\n",
    "    \"\"\"\n",
    "    assert P.shape == Q.shape\n",
    "    centroid_P = np.mean(P, axis=0)\n",
    "    centroid_Q = np.mean(Q, axis=0)\n",
    "\n",
    "    # Center the points\n",
    "    P_centered = P - centroid_P\n",
    "    Q_centered = Q - centroid_Q\n",
    "\n",
    "    # Compute covariance matrix\n",
    "    H = P_centered.T @ Q_centered\n",
    "\n",
    "    # SVD\n",
    "    U, S, Vt = np.linalg.svd(H)\n",
    "    R = Vt.T @ U.T\n",
    "\n",
    "    # Handle reflection (if determinant is negative)\n",
    "    if np.linalg.det(R) < 0:\n",
    "        Vt[2, :] *= -1\n",
    "        R = Vt.T @ U.T\n",
    "\n",
    "    return R\n",
    "\n",
    "\n",
    "R = estimate_rotation_matrix(points_3d_old, projected_points_new)\n",
    "print(\"Rotation matrix:\\n\", R)\n",
    "# Calculate the rotation angles around each axis (in radians)\n",
    "angles = (\n",
    "    np.arctan2(R[2, 1], R[2, 2]),\n",
    "    np.arctan2(-R[2, 0], np.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2)),\n",
    "    np.arctan2(R[1, 0], R[0, 0]),\n",
    ")\n",
    "angles_deg = np.degrees(angles)  # convert to degrees\n",
    "print(\"Rotation angles (degrees):\", angles_deg)\n",
    "\n",
    "# Compute the angular velocity vector (in radiant per second)\n",
    "angular_velocity = np.array([angles[0], angles[1], angles[2]]) / (\n",
    "    1 / cap.get(cv2.CAP_PROP_FPS)\n",
    ")  # Convert to angular velocity in degrees per second\n",
    "print(\"Angular velocity (rad/s):\", angular_velocity)\n",
    "\n",
    "# compute the vector magnitude\n",
    "angular_velocity_magnitude = np.linalg.norm(angular_velocity)\n",
    "print(\"Angular velocity magnitude (rad/s):\", angular_velocity_magnitude)\n",
    "\n",
    "# Compute the axis of rotation (in 3D space)\n",
    "axis_of_rotation = np.cross(\n",
    "    points_3d_old.mean(axis=0), projected_points_new.mean(axis=0)\n",
    ")\n",
    "print(\"Axis of rotation (3D):\", axis_of_rotation)\n",
    "\n",
    "# Compute the intersection of the axis of rotation with the ball surface\n",
    "# Normalize the axis of rotation\n",
    "axis_of_rotation_normalized = axis_of_rotation / np.linalg.norm(axis_of_rotation)\n",
    "# Compute the intersection point on the ball surface\n",
    "intersection_point_1 = ball_radius * axis_of_rotation_normalized\n",
    "intersection_point_2 = -ball_radius * axis_of_rotation_normalized\n",
    "\n",
    "# I want to chose the intersection point that has positive z coordinate\n",
    "intersection_point = (\n",
    "    intersection_point_1 if intersection_point_1[2] > 0 else intersection_point_2\n",
    ")\n",
    "print(\"Intersection point on the ball surface (3D):\", intersection_point)\n",
    "\n",
    "# Get the intersection point in 2D:\n",
    "intersection_point_2d = intersection_point[:2] + ball_center_roi\n",
    "print(\"Intersection point on the ball surface (2D):\", intersection_point_2d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b550cc36",
   "metadata": {},
   "source": [
    "Generate video"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "f64f7681",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 70 Intersection point: [nan nan nan]\n",
      "frame: 71 Intersection point: [nan nan nan]\n",
      "frame: 72 Intersection point: [nan nan nan]\n",
      "frame: 73 Intersection point: [nan nan nan]\n",
      "frame: 74 Intersection point: [nan nan nan]\n",
      "frame: 75 Intersection point: [nan nan nan]\n",
      "frame: 76 Intersection point: [nan nan nan]\n",
      "frame: 77 Intersection point: [29.27069746 33.68399125 35.46286796]\n",
      "frame: 78 Intersection point: [-10.69425438  20.29206777  51.08683694]\n",
      "frame: 79 Intersection point: [-53.25322769 -13.29473088   3.51338464]\n",
      "frame: 80 Intersection point: [42.92951898 21.84604771 24.40914991]\n",
      "frame: 81 Intersection point: [25.46026634 42.1456503  19.60915599]\n",
      "frame: 82 Intersection point: [10.30459206 50.8678218   3.20625756]\n",
      "frame: 83 Intersection point: [-31.42098025  41.38175519   2.06696331]\n",
      "frame: 84 Intersection point: [-49.03232269 -12.69168849   5.97932899]\n",
      "frame: 85 Intersection point: [-35.16951505 -34.67518502   7.79337892]\n",
      "frame: 86 Intersection point: [-36.30299151 -28.81800621  15.89387699]\n",
      "frame: 87 Intersection point: [-40.29908675 -25.98911942  10.07716621]\n",
      "frame: 88 Intersection point: [-39.01997123 -23.05614313  15.80683742]\n",
      "frame: 89 Intersection point: [-25.26875367   1.45128359  39.60282646]\n",
      "frame: 90 Intersection point: [nan nan nan]\n",
      "frame: 91 Intersection point: [nan nan nan]\n",
      "frame: 92 Intersection point: [nan nan nan]\n",
      "frame: 93 Intersection point: [nan nan nan]\n",
      "frame: 94 Intersection point: [nan nan nan]\n",
      "frame: 95 Intersection point: [nan nan nan]\n",
      "frame: 96 Intersection point: [nan nan nan]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 97 Intersection point: [nan nan nan]\n",
      "frame: 98 Intersection point: [nan nan nan]\n",
      "frame: 99 Intersection point: [nan nan nan]\n",
      "frame: 100 Intersection point: [nan nan nan]\n",
      "frame: 101 Intersection point: [nan nan nan]\n",
      "frame: 102 Intersection point: [nan nan nan]\n",
      "frame: 103 Intersection point: [nan nan nan]\n",
      "frame: 104 Intersection point: [nan nan nan]\n",
      "frame: 105 Intersection point: [nan nan nan]\n",
      "frame: 106 Intersection point: [nan nan nan]\n",
      "frame: 107 Intersection point: [nan nan nan]\n",
      "frame: 108 Intersection point: [nan nan nan]\n",
      "frame: 109 Intersection point: [nan nan nan]\n",
      "frame: 110 Intersection point: [nan nan nan]\n",
      "frame: 111 Intersection point: [nan nan nan]\n",
      "frame: 112 Intersection point: [nan nan nan]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 113 Intersection point: [nan nan nan]\n",
      "frame: 114 Intersection point: [nan nan nan]\n",
      "frame: 115 Intersection point: [nan nan nan]\n",
      "frame: 116 Intersection point: [nan nan nan]\n",
      "frame: 117 Intersection point: [nan nan nan]\n",
      "frame: 118 Intersection point: [nan nan nan]\n",
      "frame: 119 Intersection point: [nan nan nan]\n",
      "frame: 120 Intersection point: [nan nan nan]\n",
      "frame: 121 Intersection point: [nan nan nan]\n",
      "frame: 122 Intersection point: [nan nan nan]\n",
      "frame: 123 Intersection point: [nan nan nan]\n",
      "frame: 124 Intersection point: [nan nan nan]\n",
      "frame: 125 Intersection point: [nan nan nan]\n",
      "frame: 126 Intersection point: [nan nan nan]\n",
      "frame: 127 Intersection point: [nan nan nan]\n",
      "frame: 128 Intersection point: [nan nan nan]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 129 Intersection point: [nan nan nan]\n",
      "frame: 130 Intersection point: [nan nan nan]\n",
      "frame: 131 Intersection point: [nan nan nan]\n",
      "frame: 132 Intersection point: [nan nan nan]\n",
      "frame: 133 Intersection point: [nan nan nan]\n",
      "frame: 134 Intersection point: [nan nan nan]\n",
      "frame: 135 Intersection point: [-17.56713864  16.63343778  11.98851065]\n",
      "frame: 136 Intersection point: [ 12.59009656 -17.63709137  16.1065973 ]\n",
      "frame: 137 Intersection point: [ 7.21370367 -3.23148286 25.81704859]\n",
      "frame: 138 Intersection point: [nan nan nan]\n",
      "frame: 139 Intersection point: [nan nan nan]\n",
      "frame: 140 Intersection point: [10.20983079 23.28387356  5.44247987]\n",
      "frame: 141 Intersection point: [24.45265093  8.65868121  1.75929018]\n",
      "frame: 142 Intersection point: [21.55960294  0.28852352 14.52929026]\n",
      "frame: 143 Intersection point: [nan nan nan]\n",
      "frame: 144 Intersection point: [nan nan nan]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 145 Intersection point: [nan nan nan]\n",
      "frame: 146 Intersection point: [nan nan nan]\n",
      "frame: 147 Intersection point: [nan nan nan]\n",
      "frame: 148 Intersection point: [12.38554771 16.1303662  14.53992759]\n",
      "frame: 149 Intersection point: [-11.70109641  19.87049515   6.65189938]\n",
      "frame: 150 Intersection point: [-10.62089395   9.04915521  19.52714525]\n",
      "frame: 151 Intersection point: [21.60352649 -8.96073435  5.38450398]\n",
      "frame: 152 Intersection point: [-23.47541804   4.67504567   1.74605145]\n",
      "frame: 153 Intersection point: [-19.93215149  -4.77275338  12.48719992]\n",
      "frame: 154 Intersection point: [-23.00910589  -1.38956481   6.68207721]\n",
      "frame: 155 Intersection point: [21.80182979  6.65094867  3.0732881 ]\n",
      "frame: 156 Intersection point: [-22.51836933  -4.58726226   0.93811929]\n",
      "frame: 157 Intersection point: [21.62322242  5.96876333  5.08036579]\n",
      "frame: 158 Intersection point: [-21.61560949   5.19758815   5.8949558 ]\n",
      "frame: 159 Intersection point: [-22.79180234   3.0098715    0.68878113]\n",
      "frame: 160 Intersection point: [-22.77087415   2.09643095   2.46825198]\n",
      "frame: 161 Intersection point: [-21.51892936   1.87237265   4.17491314]\n",
      "frame: 162 Intersection point: [-20.18335935  -0.93593363   8.7037942 ]\n",
      "frame: 163 Intersection point: [-17.92908718   4.06491174  12.08405252]\n",
      "frame: 164 Intersection point: [21.31530417 -3.07257427  4.49634247]\n",
      "frame: 165 Intersection point: [21.06235981  2.32660989  5.91302679]\n",
      "frame: 166 Intersection point: [ 16.62069597 -11.66939791   8.46035565]\n",
      "frame: 167 Intersection point: [-15.28523603  15.81550419   0.48102666]\n",
      "frame: 168 Intersection point: [-0.77144735 -1.54015366 21.93245986]\n",
      "frame: 169 Intersection point: [nan nan nan]\n",
      "frame: 170 Intersection point: [-13.93429425  13.93640638   7.25341444]\n",
      "frame: 171 Intersection point: [nan nan nan]\n",
      "frame: 172 Intersection point: [nan nan nan]\n",
      "frame: 173 Intersection point: [nan nan nan]\n",
      "frame: 174 Intersection point: [ 1.33571676  7.04707288 19.73713821]\n",
      "frame: 175 Intersection point: [nan nan nan]\n",
      "frame: 176 Intersection point: [19.9084837   5.78615615  3.34255497]\n",
      "frame: 177 Intersection point: [-16.808641     3.66498095  12.04315167]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\fromnumeric.py:3860: RuntimeWarning: Mean of empty slice.\n",
      "  return _methods._mean(a, axis=axis, dtype=dtype,\n",
      "C:\\Users\\miche\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python311\\site-packages\\numpy\\_core\\_methods.py:137: RuntimeWarning: invalid value encountered in divide\n",
      "  ret = um.true_divide(\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n",
      "C:\\Users\\miche\\AppData\\Local\\Temp\\ipykernel_13928\\1302648906.py:126: RuntimeWarning: invalid value encountered in divide\n",
      "  axis_of_rotation_normalized = rot.as_rotvec()/np.linalg.norm(rot.as_rotvec())\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frame: 178 Intersection point: [-16.18605064  10.19226622   5.84204364]\n",
      "frame: 179 Intersection point: [14.11010171 10.88759813  9.07552955]\n",
      "frame: 180 Intersection point: [-17.32768939   1.31083178   9.90115652]\n",
      "frame: 181 Intersection point: [-19.29456899   4.7967639    2.17040634]\n",
      "frame: 182 Intersection point: [-14.467923    10.32998364   9.16354964]\n",
      "frame: 183 Intersection point: [ 15.03035371 -12.84642489   3.0096237 ]\n",
      "frame: 184 Intersection point: [ 14.55943314 -11.48227628   7.49534775]\n",
      "frame: 185 Intersection point: [17.8579002  -9.00474537  0.09980558]\n",
      "frame: 186 Intersection point: [-18.44478808   7.34997961   2.40158126]\n",
      "frame: 187 Intersection point: [-19.37966509   3.20654309   3.76120494]\n",
      "frame: 188 Intersection point: [-19.20928818   4.31644821   3.51731751]\n",
      "frame: 189 Intersection point: [19.60988035 -3.9288308   0.12992777]\n",
      "frame: 190 Intersection point: [18.69961305 -3.24946095  0.87491447]\n",
      "frame: 191 Intersection point: [-18.59826972   3.64321018   1.35328599]\n",
      "frame: 192 Intersection point: [-18.43313642   2.57853509   3.8171506 ]\n",
      "frame: 193 Intersection point: [-18.12811372   0.59974201   5.65789734]\n",
      "frame: 194 Intersection point: [18.23243756 -0.89792577  5.26990986]\n",
      "frame: 195 Intersection point: [13.20425667 -8.76328629 10.48105049]\n",
      "frame: 196 Intersection point: [18.55441833 -3.83792255  1.41559566]\n",
      "frame: 197 Intersection point: [-18.70408464   3.24407715   0.79572691]\n",
      "frame: 198 Intersection point: [18.46036346  3.17323651  3.18520816]\n",
      "frame: 199 Intersection point: [18.97665675  0.5704551   0.74905246]\n",
      "frame: 200 Intersection point: [-18.89409026   1.83099909   0.81289343]\n",
      "frame: 201 Intersection point: [-18.72591606   2.8373142    1.51318074]\n",
      "frame: 202 Intersection point: [-18.92910957   1.27473598   1.03143538]\n",
      "frame: 203 Intersection point: [-18.97486426   0.46009562   0.86188063]\n",
      "frame: 204 Intersection point: [18.98719106 -0.68683111  0.12181383]\n",
      "frame: 205 Intersection point: [17.35172793 -2.85771538  3.84070314]\n",
      "frame: 206 Intersection point: [-17.8071996    2.62686017   0.05699067]\n",
      "frame: 207 Intersection point: [ 3.34873834 -4.83484099 17.01206231]\n",
      "Video annotato salvato in: C:\\Users\\miche\\OneDrive\\Documenti\\GitHub\\bowling-analysis\\data\\recording_7\\test_video\\spin_video_modified_7.mp4\n"
     ]
    }
   ],
   "source": [
    "from scipy.spatial.transform import Rotation\n",
    "\n",
    "\n",
    "output_video_path = (\n",
    "    project_root\n",
    "    / \"data\"\n",
    "    / f\"recording_{video_number}\"\n",
    "    / \"test_video\"\n",
    "    / f\"spin_video_modified_{video_number}.mp4\"\n",
    ")\n",
    "\n",
    "# Ottieni le proprietà del video originale\n",
    "frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
    "frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n",
    "fps = int(cap.get(cv2.CAP_PROP_FPS))\n",
    "fourcc = cv2.VideoWriter_fourcc(*\"mp4v\")\n",
    "\n",
    "# Inizializza il VideoWriter\n",
    "out = cv2.VideoWriter(str(output_video_path), fourcc, fps, (frame_width, frame_height))\n",
    "\n",
    "# Riposiziona il video all'inizio\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, 0)\n",
    "\n",
    "# Itera su tutti i frame del video\n",
    "frame_idx = 0\n",
    "while True:\n",
    "    # for i in range(151):\n",
    "    ret, frame = cap.read()\n",
    "    if not ret:\n",
    "        break\n",
    "\n",
    "    # Controlla se il dato radius è presente per il frame corrente\n",
    "    if frame_idx < ball_df[\"frame\"].iloc[-1]:\n",
    "        if not pd.isna(ball_df.iloc[frame_idx][\"radius\"]) and not pd.isna(\n",
    "            ball_df.iloc[frame_idx + 1][\"radius\"]\n",
    "        ):\n",
    "            # Extract ball center and radius from ball_df\n",
    "            ball_center = (\n",
    "                int(ball_df.iloc[frame_idx][\"x\"]),\n",
    "                int(ball_df.iloc[frame_idx][\"y\"]),\n",
    "            )\n",
    "            ball_radius = int(ball_df.iloc[frame_idx][\"radius\"])\n",
    "\n",
    "            offset = 2\n",
    "\n",
    "            # Define the region of interest (ROI) around the ball\n",
    "            x_min = max(ball_center[0] - ball_radius, 0) - offset\n",
    "            x_max = min(ball_center[0] + ball_radius, frame.shape[1]) + offset\n",
    "            y_min = max(ball_center[1] - ball_radius, 0) - offset\n",
    "            y_max = min(ball_center[1] + ball_radius, frame.shape[0]) + offset\n",
    "            roi = frame[y_min:y_max, x_min:x_max]\n",
    "            copy_roi = roi.copy()\n",
    "            ball_center_roi = (\n",
    "                ball_center[0] - x_min + offset,\n",
    "                ball_center[1] - y_min + offset,\n",
    "            )\n",
    "            # Convert ROI to grayscale\n",
    "            roi_gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "            # Parameters for optical flow\n",
    "            lk_params = dict(\n",
    "                winSize=(15, 15),\n",
    "                maxLevel=2,\n",
    "                criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03),\n",
    "            )\n",
    "\n",
    "            # selezione 100 punti manuali distribuiti su 10 cerchi concentrici\n",
    "            manual_points = []\n",
    "            num_rings = 10  # numero di cerchi concentrici\n",
    "            points_per_ring = 10  # punti per ogni cerchio (10 × 10 = 100)\n",
    "            max_radius = ball_radius * 0.9  # lascia un margine vicino al bordo\n",
    "\n",
    "            for i in range(1, num_rings + 1):\n",
    "                r = (i / num_rings) * max_radius\n",
    "                for j in range(points_per_ring):\n",
    "                    if j < 20 or j > 9:\n",
    "                        theta = 2 * np.pi * j / points_per_ring\n",
    "                        x = int(ball_center_roi[0] + r * np.cos(theta))\n",
    "                        y = int(ball_center_roi[1] + r * np.sin(theta))\n",
    "                        manual_points.append((x, y))\n",
    "\n",
    "            # Converti in formato richiesto da calcOpticalFlowPyrLK\n",
    "            p0 = np.array(manual_points, dtype=np.float32).reshape(-1, 1, 2)\n",
    "\n",
    "            # Calcola il flusso ottico per il frame corrente\n",
    "            if frame_idx + 1 < len(ball_df):\n",
    "                # print(f\"Processing frame {frame_idx}...\")\n",
    "                ball_center_next = (\n",
    "                    int(ball_df.iloc[frame_idx + 1][\"x\"]),\n",
    "                    int(ball_df.iloc[frame_idx + 1][\"y\"]),\n",
    "                )\n",
    "                x_min_next = max(ball_center_next[0] - ball_radius, 0) - offset\n",
    "                x_max_next = (\n",
    "                    min(ball_center_next[0] + ball_radius, frame.shape[1]) + offset\n",
    "                )\n",
    "                y_min_next = max(ball_center_next[1] - ball_radius, 0) - offset\n",
    "                y_max_next = (\n",
    "                    min(ball_center_next[1] + ball_radius, frame.shape[0]) + offset\n",
    "                )\n",
    "                next_roi = next_frame[y_min_next:y_max_next, x_min_next:x_max_next]\n",
    "                next_roi_gray = cv2.cvtColor(next_roi, cv2.COLOR_BGR2GRAY)\n",
    "                ball_center_next_roi = (\n",
    "                    ball_center_next[0] - x_min_next,\n",
    "                    ball_center_next[1] - y_min_next,\n",
    "                )\n",
    "\n",
    "                # Calcola il flusso ottico\n",
    "                p1, st, err = cv2.calcOpticalFlowPyrLK(\n",
    "                    roi_gray, next_roi_gray, p0, None, **lk_params\n",
    "                )\n",
    "\n",
    "                # Filtra i punti validi\n",
    "                if p1 is not None and st is not None:\n",
    "                    good_new = p1[st == 1].reshape(-1, 2)\n",
    "                    good_old = p0[st == 1].reshape(-1, 2)\n",
    "\n",
    "                    # Filter only the point in the ball\n",
    "                    # Compute distances to the ball center\n",
    "                    dist_old = np.linalg.norm(good_old[:, :] - ball_center_roi, axis=1)\n",
    "                    dist_new = np.linalg.norm(\n",
    "                        good_new[:, :] - ball_center_next_roi, axis=1\n",
    "                    )\n",
    "\n",
    "                    # Create a mask of points that are within the ball in both frames\n",
    "                    within_radius_mask = (dist_old <= ball_radius) & (\n",
    "                        dist_new <= ball_radius\n",
    "                    )\n",
    "\n",
    "                    # Apply the mask\n",
    "                    good_old = good_old[within_radius_mask].reshape(-1, 2)\n",
    "                    good_new = good_new[within_radius_mask].reshape(-1, 2)\n",
    "\n",
    "                    distances = np.linalg.norm(good_new - good_old, axis=1)\n",
    "                    velocities = distances / (\n",
    "                        1 / cap.get(cv2.CAP_PROP_FPS)\n",
    "                    )  # Convert to velocity in pixels per second\n",
    "                    z_3d_old = np.sqrt(\n",
    "                        ball_radius**2\n",
    "                        - (good_old[:, 0] - ball_center_roi[0]) ** 2\n",
    "                        - (good_old[:, 1] - ball_center_roi[1]) ** 2\n",
    "                    )\n",
    "                    points_3d_old = np.array(\n",
    "                        [good_old[:, 0], good_old[:, 1], z_3d_old]\n",
    "                    ).T\n",
    "\n",
    "                    z_3d_new = np.sqrt(\n",
    "                        ball_radius**2\n",
    "                        - (good_new[:, 0] - ball_center_next_roi[0]) ** 2\n",
    "                        - (good_new[:, 1] - ball_center_next_roi[1]) ** 2\n",
    "                    )\n",
    "                    points_3d_new = np.array(\n",
    "                        [good_new[:, 0], good_new[:, 1], z_3d_new]\n",
    "                    ).T\n",
    "\n",
    "                    distances_3d = np.linalg.norm(points_3d_new - points_3d_old, axis=1)\n",
    "\n",
    "                    # normal vectors to the ball surface in the point of interest\n",
    "                    normal_vectors = (\n",
    "                        points_3d_old\n",
    "                        / np.linalg.norm(points_3d_old, axis=1)[:, np.newaxis]\n",
    "                    )\n",
    "\n",
    "                    # projection of the points on the plane tangent to the ball surface in the point of interest (they are the difference, not the points)\n",
    "                    projection_vectors = (\n",
    "                        points_3d_new\n",
    "                        - np.sum(points_3d_new * normal_vectors, axis=1)[:, np.newaxis]\n",
    "                        * normal_vectors\n",
    "                    )\n",
    "                    # print(projection_vectors)\n",
    "                    # normalize the projection vectors\n",
    "                    projection_vectors = (\n",
    "                        projection_vectors\n",
    "                        / np.linalg.norm(projection_vectors, axis=1)[:, np.newaxis]\n",
    "                    )\n",
    "                    # multiply the projection vectors by the distances_3d to make them have the same magnitude\n",
    "                    projection_vectors = (\n",
    "                        projection_vectors * distances_3d[:, np.newaxis]\n",
    "                    )\n",
    "\n",
    "                    projected_points_new = points_3d_old + projection_vectors\n",
    "\n",
    "                    R = estimate_rotation_matrix(\n",
    "                        points_3d_old, points_3d_new\n",
    "                    )  ## CHANEG points_3d_new to projected_points_new\n",
    "\n",
    "                    # DA LIBRERIA\n",
    "                    rot = Rotation.from_matrix(R)\n",
    "                    axis_of_rotation_normalized = rot.as_rotvec() / np.linalg.norm(\n",
    "                        rot.as_rotvec()\n",
    "                    )\n",
    "\n",
    "                    # Compute the intersection point on the ball surface\n",
    "                    intersection_point_1 = ball_radius * axis_of_rotation_normalized\n",
    "                    intersection_point_2 = -ball_radius * axis_of_rotation_normalized\n",
    "\n",
    "                    # I want to chose the intersection point that has positive z coordinate\n",
    "                    intersection_point = (\n",
    "                        intersection_point_1\n",
    "                        if intersection_point_1[2] > 0\n",
    "                        else intersection_point_2\n",
    "                    )\n",
    "                    behind_point = (\n",
    "                        intersection_point_2\n",
    "                        if intersection_point_1[0] == intersection_point[0]\n",
    "                        else intersection_point_1\n",
    "                    )\n",
    "                    print(\n",
    "                        \"frame:\", frame_idx, \"Intersection point:\", intersection_point\n",
    "                    )\n",
    "\n",
    "                    # Get the intersection point in 2D:\n",
    "                    intersection_point_2d = intersection_point[:2] + ball_center\n",
    "                    behind_point = behind_point[:2] + ball_center\n",
    "                    # print(\"frame:\", frame_idx ,\"Intersection point:\", intersection_point_2d)\n",
    "\n",
    "                    # Add the intersection point to ball_df\n",
    "                    ball_df.loc[frame_idx, \"rotation_center_x\"] = intersection_point_2d[\n",
    "                        0\n",
    "                    ]\n",
    "                    ball_df.loc[frame_idx, \"rotation_center_y\"] = intersection_point_2d[\n",
    "                        1\n",
    "                    ]\n",
    "\n",
    "                    # Disegna il centro della palla\n",
    "                    # cv2.circle(frame, ball_center, 5, (255, 0, 0), -1) # Blu\n",
    "\n",
    "                    if not np.isnan(intersection_point_2d[0]) and not np.isnan(\n",
    "                        intersection_point_2d[1]\n",
    "                    ):\n",
    "                        rotation_point_2d = (\n",
    "                            int(intersection_point_2d[0]),\n",
    "                            int(intersection_point_2d[1]),\n",
    "                        )\n",
    "                        back_point = (int(behind_point[0]), int(behind_point[1]))\n",
    "                        # Disegna il centro di rotazione\n",
    "                        cv2.circle(\n",
    "                            frame, rotation_point_2d, 5, (0, 255, 255), -1\n",
    "                        )  # Giallo\n",
    "                        cv2.circle(frame, back_point, 5, (0, 255, 255), -1)\n",
    "                        cv2.line(\n",
    "                            frame, rotation_point_2d, back_point, (144, 238, 144), 1\n",
    "                        )  # Draw a light green thin line\n",
    "                        # print(f\"Frame {frame_idx}: Center of Rotation: {rotation_point_2d}\")\n",
    "\n",
    "    # Scrivi il frame nel video di output\n",
    "    out.write(frame)\n",
    "\n",
    "    # Incrementa l'indice del frame\n",
    "    frame_idx += 1\n",
    "\n",
    "# Rilascia le risorse\n",
    "out.release()\n",
    "\n",
    "print(f\"Video annotato salvato in: {output_video_path}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37d3203d",
   "metadata": {},
   "source": [
    "Save the data in a csv file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "e54905af",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ball data salvato in: C:\\Users\\miche\\OneDrive\\Documenti\\GitHub\\bowling-analysis\\notebook\\spin\\intermediate_data\\Ball_data_7.csv\n"
     ]
    }
   ],
   "source": [
    "output_path = (\n",
    "    project_root\n",
    "    / \"notebook\"\n",
    "    / \"spin\"\n",
    "    / \"intermediate_data\"\n",
    "    / f\"Ball_data_{video_number}.csv\"\n",
    ")\n",
    "ball_df.to_csv(output_path, index=False)\n",
    "print(f\"Ball data salvato in: {output_path}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
