diff --git a/misc/UDFs-demo.ipynb b/misc/UDFs-demo.ipynb new file mode 100644 index 0000000..0791227 --- /dev/null +++ b/misc/UDFs-demo.ipynb @@ -0,0 +1,300 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "be540ffe-d4f2-4bb8-bf49-b15866712f22", + "metadata": {}, + "outputs": [], + "source": [ + "import cv2\n", + "import numpy as np\n", + "\n", + "def add_bottom_overlay(image, percent=20, alpha=0.5):\n", + " height, width, _ = image.shape\n", + " overlay_height = int(height * (percent / 100))\n", + " overlay = image.copy()\n", + " cv2.rectangle(overlay, (0, height - overlay_height), (width, height), (0, 0, 0), -1)\n", + " image[height - overlay_height:height, 0:width] = cv2.addWeighted(\n", + " overlay[height - overlay_height:height, 0:width], alpha, \n", + " image[height - overlay_height:height, 0:width], 1 - alpha, 0)\n", + "\n", + "def draw_text(image, text, position, font=cv2.FONT_HERSHEY_SIMPLEX, font_scale=0.8, color=(255, 255, 255), thickness=2):\n", + " \"\"\"Draw text on an image.\"\"\"\n", + " cv2.putText(image, text, position, font, font_scale, color, thickness, cv2.LINE_AA)\n", + "\n", + "def draw_bar(image, position, width, height, level, label, bar_bg_color=(100, 100, 100), bar_fg_color_start=(255, 255, 255), bar_fg_color_end=(150, 150, 150)):\n", + " \"\"\"Draw a bar with a gradient and rounded corners on an image.\"\"\"\n", + " def draw_rounded_rect(img, pt1, pt2, color, radius):\n", + " x1, y1 = pt1\n", + " x2, y2 = pt2\n", + " cv2.rectangle(img, (x1 + radius, y1), (x2 - radius, y2), color, -1)\n", + " cv2.rectangle(img, (x1, y1 + radius), (x2, y2 - radius), color, -1)\n", + " cv2.ellipse(img, (x1 + radius, y1 + radius), (radius, radius), 180, 0, 90, color, -1)\n", + " cv2.ellipse(img, (x2 - radius, y1 + radius), (radius, radius), 270, 0, 90, color, -1)\n", + " cv2.ellipse(img, (x1 + radius, y2 - radius), (radius, radius), 90, 0, 90, color, -1)\n", + " cv2.ellipse(img, (x2 - radius, y2 - radius), (radius, radius), 0, 0, 90, color, -1)\n", + "\n", + " def draw_gradient_bar(img, pt1, pt2, start_color, end_color):\n", + " x1, y1 = pt1\n", + " x2, y2 = pt2\n", + " bar_width = x2 - x1\n", + " bar_height = y2 - y1\n", + "\n", + " gradient_bar = np.zeros((bar_height, bar_width, 3), dtype=np.uint8)\n", + " for i in range(bar_width):\n", + " alpha = i / bar_width\n", + " color = tuple(int(start_color[j] * (1 - alpha) + end_color[j] * alpha) for j in range(3))\n", + " cv2.line(gradient_bar, (i, 0), (i, bar_height), color, 1)\n", + "\n", + " rounded_bar = np.zeros((bar_height, bar_width, 3), dtype=np.uint8)\n", + " draw_rounded_rect(rounded_bar, (0, 0), (bar_width, bar_height), (255, 255, 255), bar_height // 2)\n", + " \n", + " mask = cv2.cvtColor(rounded_bar, cv2.COLOR_BGR2GRAY)\n", + " mask_inv = cv2.bitwise_not(mask)\n", + "\n", + " img_bg = cv2.bitwise_and(img[y1:y2, x1:x2], img[y1:y2, x1:x2], mask=mask_inv)\n", + " bar_fg = cv2.bitwise_and(gradient_bar, gradient_bar, mask=mask)\n", + "\n", + " img[y1:y2, x1:x2] = cv2.add(img_bg, bar_fg)\n", + "\n", + " x, y = position\n", + " bar_level = int(width * level)\n", + "\n", + " # Bar background and foreground\n", + " draw_rounded_rect(image, (x, y), (x + width, y + height), bar_bg_color, height // 2)\n", + " draw_gradient_bar(image, (x, y), (x + bar_level, y + height), bar_fg_color_start, bar_fg_color_end)\n", + " draw_text(image, label, (x + width + 10, y + 15))\n", + "\n", + "def draw_circ_bool(img, state, pos, rad=12, color=(255, 255, 255)):\n", + " thickness = cv2.FILLED if state else 1\n", + " cv2.circle(img, pos, rad, color, thickness, cv2.LINE_AA)\n", + "\n", + "def draw_battery(image, position, width, height, level, border_color=(255, 255, 255), fill_color=(207, 255, 207), background_color=(20, 20, 20)):\n", + " \"\"\"\n", + " Draw a battery icon with the specified fill level.\n", + " \n", + " Parameters:\n", + " - image: The image on which to draw the battery.\n", + " - position: A tuple (x, y) representing the top-left corner of the battery icon.\n", + " - width: The width of the battery icon.\n", + " - height: The height of the battery icon.\n", + " - level: The fill level of the battery (0 to 1).\n", + " - border_color: The color of the battery border.\n", + " - fill_color: The color of the battery fill.\n", + " - background_color: The color of the battery background.\n", + " \"\"\"\n", + " x, y = position\n", + " # Draw the main battery rectangle\n", + " cv2.rectangle(image, (x, y), (x + width, y + height), border_color, 2)\n", + " \n", + " # Draw the positive terminal\n", + " terminal_width = int(width * 0.08)\n", + " cv2.rectangle(image, (x + width, y + int(height * 0.3)), (x + width + terminal_width, y + int(height * 0.7)), border_color, -1)\n", + " \n", + " # Draw the battery background\n", + " cv2.rectangle(image, (x + 2, y + 2), (x + width - 2, y + height - 2), background_color, -1)\n", + " \n", + " # Draw the filled part of the battery\n", + " fill_width = int((width - 4) * level)\n", + " cv2.rectangle(image, (x + 2, y + 2), (x + 2 + fill_width, y + height - 2), fill_color, -1)\n", + "\n", + "import cv2\n", + "import numpy as np\n", + "\n", + "def draw_line_graph(image, data, position, graph_size, axis_color=(255, 255, 255), line_color=(0, 255, 255), thickness=2):\n", + " \"\"\"\n", + " Draw a line graph on an image.\n", + "\n", + " Parameters:\n", + " - image: The image on which to draw the graph.\n", + " - data: A list of normalized data points (values between -1 and 1).\n", + " - position: A tuple (x, y) representing the top-left corner of the graph.\n", + " - graph_size: A tuple (width, height) representing the size of the graph.\n", + " - axis_color: The color of the graph axes.\n", + " - line_color: The color of the graph line.\n", + " - thickness: The thickness of the graph line.\n", + " \"\"\"\n", + " x, y = position\n", + " width, height = graph_size\n", + "\n", + " # Draw horizontal axis\n", + " cv2.line(image, (x, y + height // 2), (x + width, y + height // 2), axis_color, thickness)\n", + " \n", + " # Draw graph line\n", + " num_points = len(data)\n", + " step = width // (num_points - 1)\n", + " \n", + " for i in range(1, num_points):\n", + " pt1 = (x + (i - 1) * step, y + height // 2 - int(data[i - 1] * (height // 2)))\n", + " pt2 = (x + i * step, y + height // 2 - int(data[i] * (height // 2)))\n", + " cv2.line(image, pt1, pt2, line_color, thickness)\n", + "\n", + " # Draw vertical axis\n", + " cv2.line(image, (x, y), (x, y + height), axis_color, thickness)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4bc3cef-fc6f-42a5-bbe0-44975335580c", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import cv2\n", + "\n", + "def draw_axes(frame, angles, center=(320, 240), length=100):\n", + " \"\"\"\n", + " Draws 3D axes on the given frame.\n", + "\n", + " Parameters:\n", + " frame: The image frame on which to draw the axes.\n", + " angles: A tuple or list of three angles (roll, pitch, yaw) in degrees.\n", + " center: The center point for the axes (default is (320, 240) for a 640x480 frame).\n", + " length: The length of each axis line (default is 100).\n", + " \"\"\"\n", + " roll, pitch, yaw = np.deg2rad(angles)\n", + "\n", + " # Rotation matrices\n", + " Rx = np.array([\n", + " [1, 0, 0],\n", + " [0, np.cos(roll), -np.sin(roll)],\n", + " [0, np.sin(roll), np.cos(roll)]\n", + " ])\n", + "\n", + " Ry = np.array([\n", + " [np.cos(pitch), 0, np.sin(pitch)],\n", + " [0, 1, 0],\n", + " [-np.sin(pitch), 0, np.cos(pitch)]\n", + " ])\n", + "\n", + " Rz = np.array([\n", + " [np.cos(yaw), -np.sin(yaw), 0],\n", + " [np.sin(yaw), np.cos(yaw), 0],\n", + " [0, 0, 1]\n", + " ])\n", + "\n", + " # Combined rotation matrix\n", + " R = Rz @ Ry @ Rx\n", + "\n", + " # Define the axes in 3D space\n", + " axes = np.array([\n", + " [length, 0, 0], # X-axis (red)\n", + " [0, length, 0], # Y-axis (green)\n", + " [0, 0, length] # Z-axis (blue)\n", + " ])\n", + "\n", + " # Project the 3D axes to 2D\n", + " axes_2d = np.dot(axes, R.T).astype(int)\n", + "\n", + " # Define colors for the axes\n", + " colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)]\n", + "\n", + " for axis, color in zip(axes_2d, colors):\n", + " pt = (center[0] + axis[0], center[1] - axis[1])\n", + " cv2.line(frame, center, pt, color, 2, cv2.LINE_AA)\n", + "\n", + " return frame\n", + "\n", + "import pandas as pd\n", + "data = {\n", + " 'roll': np.linspace(0, 360, 100),\n", + " 'pitch': np.sin(np.linspace(0, 2*np.pi, 100)) * 45,\n", + " 'yaw': np.cos(np.linspace(0, 2*np.pi, 100)) * 45\n", + "}\n", + "df = pd.DataFrame(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec101273-0265-4382-8c9b-b16ac67b0b06", + "metadata": {}, + "outputs": [], + "source": [ + "import vidformer\n", + "import cv2\n", + "\n", + "server = vidformer.YrdenServer(bin=\"../target/release/vidformer-cli\")\n", + "# server = vidformer.YrdenServer(domain='localhost', port=5000)\n", + "\n", + "scale = vidformer.Filter(\"Scale\")\n", + "\n", + "class MyFilter(vidformer.UDF): \n", + " def filter(self, frame: vidformer.UDFFrame, i: int):\n", + " f = frame.data().copy()\n", + " height, width = f.shape[:2]\n", + " add_bottom_overlay(f, percent=25)\n", + "\n", + " speed, altitude, lox, ch4 = 3808 + i // 3, 43 + i // 34, max(0.8 - i * .001, 0.1), max(1.0 - i * .003, 0.1)\n", + " draw_text(f, f'SPEED {speed} KM/H', (200, height-150))\n", + " draw_text(f, f'ALTITUDE {altitude} KM', (200, height-110))\n", + "\n", + " # Draw the LOX and CH4 bars\n", + " draw_bar(f, (200, height-40), 200, 20, lox, 'LOX')\n", + " draw_bar(f, (200, height-80), 200, 20, ch4, 'CH4')\n", + "\n", + " draw_text(f, f'T+{i/24:0.2f}', (575, height-75), font_scale=2.0, thickness=4)\n", + "\n", + " draw_text(f, f'ALPHA', (945, height-90))\n", + " draw_circ_bool(f, False, (920, height-100))\n", + "\n", + " draw_text(f, f'GAMMA', (945, height-50))\n", + " draw_circ_bool(f, i % 50 < 25, (920, height-60))\n", + "\n", + " # draw_battery(f, (50, 50), 100, 40, 0.75)\n", + "\n", + " import math\n", + " num_points = 50\n", + " data = [0.6 * math.sin(2 * math.pi * 2 * x / num_points + i / 25) for x in range(num_points)]\n", + " position = (1080, height-150) # (x, y)\n", + " size = (150, 100) # (width, height)\n", + " # Draw the line graph on the image\n", + " draw_line_graph(f, data, position, size)\n", + "\n", + " draw_axes(f, df.iloc[i % len(df)].values, center=(100, height-100), length=80)\n", + "\n", + " return vidformer.UDFFrame(f, frame.frame_type())\n", + " \n", + " def filter_type(self, frame, text): \n", + " return frame\n", + " \n", + "mf_udf = MyFilter(\"MyFilter\") \n", + "mf = mf_udf.into_filter()\n", + "\n", + "tos = vidformer.Source(server, \"tos_720p\", \"tos_720p.mp4\", 0)\n", + "\n", + "domain = tos.ts()\n", + "\n", + "def render(t, i):\n", + " return scale(mf(scale(tos[t], format=\"rgb24\", width=1280, height=720), i), format=\"yuv420p\", width=1280, height=720)\n", + "\n", + "spec = vidformer.Spec(domain, render, tos.fmt())\n", + "# spec.play(server)\n", + "spec.save(server, \"tos-hud.mp4\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}