From b7d0f4b82c636b2c53accab486ec9b47584e4fd3 Mon Sep 17 00:00:00 2001 From: JellyDreams Date: Fri, 31 Jan 2025 12:08:18 +0100 Subject: [PATCH] v0.1.11 - Features: added live settings update, mirror option, and improved threading for smoother plugin closure --- CONTRIBUTING.md | 2 +- README.md | 74 ++--- app.py | 293 ++++++------------- info.py | 2 +- plugin/{nizima.py => connector.py} | 99 ++++++- plugin/errors_ui.py | 35 +++ plugin/pose_detection.py | 96 +++++++ plugin/ui.py | 437 +++++++++++++++-------------- plugin/vtube_studio.py | 32 --- 9 files changed, 566 insertions(+), 504 deletions(-) rename plugin/{nizima.py => connector.py} (51%) create mode 100644 plugin/errors_ui.py create mode 100644 plugin/pose_detection.py delete mode 100644 plugin/vtube_studio.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf8584c..14ef577 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ python app.py ### Build executable ```shell - pyinstaller ./app.py -n VTS_Fullbody_Tracking-0.1.10 --add-data='models/*:models' --add-data="venv/Lib/site-packages/mediapipe/modules/pose_landmark/*:mediapipe/modules/pose_landmark" --add-data="venv/Lib/site-packages/mediapipe/modules/pose_detection/*:mediapipe/modules/pose_detection" --add-data='*.png:.' -F -w + pyinstaller ./app.py -n VTS_Fullbody_Tracking-0.1.11 --add-data='models/*:models' --add-data="venv/Lib/site-packages/mediapipe/modules/pose_landmark/*:mediapipe/modules/pose_landmark" --add-data="venv/Lib/site-packages/mediapipe/modules/pose_detection/*:mediapipe/modules/pose_detection" --add-data='*.png:.' -F -w ``` diff --git a/README.md b/README.md index 1ae62ac..9773c00 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,57 @@ # VTS FULLBODY TRACKING -🔗 website: [vts-fullbody-tracking.gitlab.io](https://vts-fullbody-tracking.gitlab.io/) - [discord-link]: https://discord.gg/9K9gejWQ3s - [![Last Release][github-release-badge]](https://github.com/jellydreams/VTS-Fullbody-Tracking/releases) [![VTS FULLBODY TRACKING Discord][discord-badge]](https://discord.gg/9K9gejWQ3s) [![Twitter Follow][twitter-badge]](https://twitter.com/_JellyDreams_) +🔗 website: [vts-fullbody-tracking.gitlab.io](https://vts-fullbody-tracking.gitlab.io/) + +![Demo Tracking Arms](readme_img/Demo_Tracking_Arms.png) + [discord-badge]: https://img.shields.io/badge/Join_Discord-indigo?logo=discord&logoColor=white&color=7289da [twitter-badge]: https://img.shields.io/twitter/follow/_JellyDreams_.svg?style=social [github-release-badge]: https://img.shields.io/github/v/release/jellydreams/VTS-Fullbody-Tracking?label=ALPHA%20release -This plugin integrates full body tracking functionality using Mediapipe. -It allows users to use tracked body parameters as inputs to control Live2D model in [VTube Studio](https://denchisoft.com/) or [Nizima LIVE](https://nizimalive.com/en/). +Full-body tracking for Live2D VTubers, using a camera. +- **Arms**: Shoulders, Elbow, Wrists +- **Torso**: Clavicles, Hips +- **Legs**: Knees, Ankles +- **Feet**: Heels, Feet Index +- **Hands**: Pinkies Knuckles, Index Knuckles, Thumb Knuckles -![Demo Tracking Arms](readme_img/Demo_Tracking_Arms.png) +Plugin can be used with [VTube Studio](https://denchisoft.com/) or [Nizima LIVE](https://nizimalive.com/en/) -## THIS PLUGIN IS UNDER DEVELOPMENT -This plugin may contain bugs and lack certain features.
-Visit the [Wiki](https://github.com/jellydreams/VTS-Fullbody-Tracking/wiki) or [Website](https://vts-fullbody-tracking.gitlab.io/) for more information on how the plugin works.
-Join [Discord Server](https://discord.gg/9K9gejWQ3s) to share tests and feedbacks, ask questions or get help. +## Join the Alpha +This plugin can be fixed and improved with feedback. -### How you can Help -- **Live2D Rigger**: Help understand how to effectively rig models for the body parts feature -- **Live2D Vtuber**: Experiment with usability and performance for movement and configuration in VTube Studio or Nizima LIVE -- **Developper**: Contributions are welcome to improve this plugin +Join [Discord Server](https://discord.gg/9K9gejWQ3s) to get help, ask questions, share tests or give feedbacks.
-### Troubleshooting -- Currently, there might be a latency of a few seconds -- Tracking input may be inaccurate and exhibit occasional jumps +Visit the [Wiki](https://github.com/jellydreams/VTS-Fullbody-Tracking/wiki) or [Website](https://vts-fullbody-tracking.gitlab.io/) for more information on how the plugin works. ## Run the Plugin -**Requirements**: Window, VTube Studio, Camera +**Requirements**: Window, VTube Studio or Nizima LIVE, Camera -1. Download the executable from the [releases page](https://github.com/jellydreams/VTS-Fullbody-Tracking/releases) -2. **Connect a Camera** -4. **Double-click on the executable file** `VTS_Fullbody_Tracking.exe` to launch the plugin. A settings window will appear. -5. **Select your camera** -3. **Open Software (VTube Studio or Nizima LIVE)** -4. **click on the 'Start Tracking' button** -6. **Allow the plugin in your software**. Window displaying a preview of pose tracking will appear. -7. **Configure your model's parameter settings**, using plugin parameters as inputs. you can now choose body parts X, Y, Z coordinates, and visibility as inputs +1. **Download the executable** from the [releases page](https://github.com/jellydreams/VTS-Fullbody-Tracking/releases) +1. **Connect a camera** +1. **Double-click on the executable file** VTS_Fullbody_Tracking.exe to launch the plugin. A settings window will appear. +1. **Select your camera**. The camera should be different from the one used in Vtube Studio or Nizima LIVE. +1. **Open your software (VTube Studio or Nizima LIVE)** ['Start API' setting should be enabled in VTube Studio] +1. **Click on the 'Start Tracking'** button. +1. **Allow the plugin** in your software. A window displaying a preview of pose tracking will appear. +1. **Configure your model's parameter settings**, using plugin parameters as inputs. 📖 Wiki Section - [Run the plugin](https://github.com/jellydreams/VTS-Fullbody-Tracking/wiki/Run-the-plugin) -## Settings - -### Peview Camera -Displays the image captured by the camera - -| default | Preview Camera | -|----------------------------------------------------------|----------------------------------------------------------------------| -| ![exemple_preview.png](readme_img/exemple_preview.png) | ![exemple_camera_preview.png](readme_img/exemple_camera_preview.png) | - -📖Wiki Section - [Settings Window](https://github.com/jellydreams/VTS-Fullbody-Tracking/wiki/Settings-Window) - ## Custom Parameters -This plugin add new controls for various body parts in Vtube Studio. \ -Each body part has parameters for controlling its position and visibility. - -![List Bodyparts MediaPipe](readme_img/list_bodyparts.png)
+Plugin add new parameters to control live2d model, each body part has parameters for controlling its position (X/Y/Z) and visibility. -- **Arms**: Shoulders, Elbow, Wrists -- **Torso**: Clavicles, Hips -- **Legs**: Knees, Ankles -- **Feet**: Heels, Feet Index -- **Hands**: Pinkies Knuckles, Index Knuckles, Thumb Knuckles +![List Bodyparts MediaPipe](readme_img/list_bodyparts.png) 📖Wiki Section - [List of Parameters](https://github.com/jellydreams/VTS-Fullbody-Tracking/wiki/Custom-Parameters) + + diff --git a/app.py b/app.py index 55c1cf7..25c2332 100644 --- a/app.py +++ b/app.py @@ -1,217 +1,98 @@ -import mediapipe as mp -import numpy as np -import pyvts import asyncio +from plugin.connector import Connector +from plugin.pose_detection import PoseDetection +from plugin.mediapipe import get_bodyparts_values +import tkinter as tk +import threading +from plugin.ui import UI import cv2 -import os - from info import VERSION -from plugin.ui import window_tracking_configuration, NIZIMA_LIVE, VTUBE_STUDIO -from plugin.mediapipe import get_bodyparts_values, LIVE_STREAM, IMAGE -from plugin.vtube_studio import connection_vts, create_parameters_vts, send_paramters_vts - -from plugin.nizima import Nizima - -RESULT = None -BG_COLOR = (192, 192, 192) # gray -model_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'models/pose_landmarker_full.task')) -icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'icon.png')) - -plugin_info = { - "plugin_name": "VTS_Fullbody_Tracking", - "developer": "JellyDreams", - "authentication_token_path": "./token.txt", - "plugin_icon": icon_path -} - -vts_api = {"version": "1.0", "name": "VTubeStudioPublicAPI", "port": 8001} - - -async def main(settings): - # ----- MEDIAPIPE: LANDMARKER CONFIGURATION ----------- - mode = settings['tracking_mode'] - mp_drawing = mp.solutions.drawing_utils - mp_drawing_styles = mp.solutions.drawing_styles - mp_pose = mp.solutions.pose - - connection = False - software = settings['software'] - port = settings['port'] - - # ----- NIZIMA ------------- - if software == NIZIMA_LIVE: - plugin_infos = { - "Name": 'Fullbody Tracking', - "Developer": 'Jelly Dreams' - } - nz = Nizima(plugin_infos=plugin_infos, port=port) - await nz.connect_nizima() - - if nz.connection: - connection = True - await nz.create_parameters() - - # ----- VTUBE STUDIO ------------- - elif software == VTUBE_STUDIO: - vts_api['port'] = port - vts = pyvts.vts(plugin_info=plugin_info, vts_api_info=vts_api) - - error = False - try: - await connection_vts(vts) - except ConnectionError: - error_connection_vts() - cv2.waitKey(0) - cv2.destroyAllWindows() - error = True - - if not error: - connection = True - await create_parameters_vts(vts) - - # ---- LIVE TRACKING ---------------- - if connection: - parameters = None - - # -- Camera connection - camera_setting = settings['camera_id'] if not settings['camera_url'] else settings['camera_url'] - cap = cv2.VideoCapture(camera_setting) - if not cap.isOpened(): - error_camera_url(camera_setting) - cv2.waitKey(0) - cv2.destroyAllWindows() - - with mp_pose.Pose(min_detection_confidence=0.5,min_tracking_confidence=0.5) as pose: - #print('========== START LIVE TRACKING =========') - # -- LOOP Through Video - while cap.isOpened(): - success, frame = cap.read() - - if success: - # current frame - input_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame) - - # -- POSE DETECTION ---------------- - # Detect pose landmarks from the current frame - if mode == LIVE_STREAM: - frame.flags.writeable = False - image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - RESULT = pose.process(image) - else: - # Convert the BGR image to RGB before processing. - image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - RESULT = pose.process(image) - - # Display Image for tracking window - image = render_image(input_image, settings['preview_enabled']) - - if RESULT: - # -- DRAW LANDMARKS -------- - if RESULT.pose_world_landmarks: - # Get coordinates - parameters = RESULT - if mode == LIVE_STREAM: - # Draw the pose annotation on the image. - image.flags.writeable = True - - # Flip the image horizontally for a selfie-view display. - #cv2.imshow('MediaPipe Pose', cv2.flip(image, 1)) - - # Draw pose landmarks on the image. - mp_drawing.draw_landmarks( - image, - parameters.pose_landmarks, - mp_pose.POSE_CONNECTIONS, - landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style()) - else: - error_pose_estimation(image) + +class Plugin: + WINDOW_SIZE = "340x590" + + def __init__(self): + self.settings = {} + self.connected = False + self.connector = None + self.ui = None + self.pose_detection = None + + async def run(self): + # connection to software + self.connector = Connector(self.settings['software'], self.settings['port']) + if not self.connected: + self.connected = await self.connector.connect() + await self.connector.create_parameters() + + if self.connected and self.pose_detection: + self.pose_detection.camera_connection() + with self.pose_detection.mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: + # print('========== START LIVE TRACKING =========') + while self.pose_detection.cap.isOpened(): + parameters = self.pose_detection.detect(pose) # - WINDOW : CAMERA TRACKING - cv2.imshow(f'VTS FullBody Tracking {VERSION}', image) + cv2.imshow(f'VTS FullBody Tracking {VERSION}', self.pose_detection.image) + if cv2.waitKey(1) & 0xFF == ord('q'): + break - # SEND DATA if parameters: data = get_bodyparts_values(parameters) - if software == NIZIMA_LIVE: - data = [{"Id": key, "Value": value*10} for key, value in data.items()] - await nz.set_live_parameter_values(data) - elif software == VTUBE_STUDIO: - # Prepare values to send - names, values = zip(*data.items()) - await send_paramters_vts(vts, values, names) - else: - if settings['camera_url']: - error_camera_url(camera_setting) - else: - error_camera() - - # print('-----------------------------------------') - # Closing Plugin : Esc or Q - if cv2.waitKey(1) & 0xFF in [ord('q'), 27]: - cv2.destroyAllWindows() - break - else: - print('connection error') - error_connection_vts() - if cv2.waitKey(1) & 0xFF in [ord('q'), 27]: - cv2.destroyAllWindows() - - -def error_connection_vts(): - """Display error message if there is a camera issue""" - image = np.zeros((180, 600, 3), dtype=np.uint8) - cv2.putText(image, text="Error: Unable to connect to VTube Studio", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.6, color=(255, 255, 255), thickness=1) - cv2.putText(image, text="- Ensure VTube Studio is running", org=(50, 75), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) - cv2.putText(image, text='- Enable "Start API (Allow Plugin)" in VTube Studio settings', org=(50, 100), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) - cv2.putText(image, text="- Verify that the API Port matches in the plugin and VTube Studio", org=(50, 125), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) - cv2.imshow(f'VTS FullBody Tracking {VERSION}', image) - - -def error_pose_estimation(image): - """Display information when no tracking is available""" - cv2.putText(image, text="No Human Detected", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(255, 255, 255), thickness=1) - - -def error_camera(): - # Display error message if there is a camera issue - image = np.zeros((100, 500, 3), dtype=np.uint8) - cv2.putText(image, text="Problem with Camera", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.6, color=(255, 255, 255), thickness=1) - cv2.imshow(f'VTS FullBody Tracking {VERSION}', image) - - -def error_camera_url(camera_setting): - input_image = np.zeros((200, 800, 3), dtype=np.uint8) - cv2.putText(input_image, text=f"Failed to connect to the camera at URL: {camera_setting}", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(255, 255, 255), thickness=1) - cv2.imshow(f'VTS FullBody Tracking {VERSION}', input_image) - - -def render_image(image, preview_enabled=False): - # -- Display the original input or an empty image as a base - image = image.numpy_view() - if preview_enabled: - # Use the input image as a base if preview configuration is enabled - image = np.copy(image) - else: - # Create an empty image with the same dimensions as the input image - height, width, _ = image.shape - image = np.zeros((height, width, 3), dtype=np.uint8) - - return image + await self.connector.update_parameters(data) + + def update_tracking_window(self): + if self.pose_detection.image != None: + cv2.imshow(f'VTS FullBody Tracking {VERSION}', self.pose_detection.image) + + def start_tracking(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.create_task(self.run()) + loop.run_forever() + + def setup_ui(self): + root = tk.Tk() + root.title(f"VTS Fullbody Tracking {VERSION} - Settings") + root.geometry(self.WINDOW_SIZE) + root.configure(bg='#333333') + + ui = UI(root) + ui.set_icons() + ui.set_header() + ui.set_available_cameras() + ui.set_external_camera() + ui.set_camera_view() + ui.set_camera_mirror() + ui.set_connection_software() + ui.set_tracking_mode() + + def on_start(): + self.settings = ui.get_configuration() + self.pose_detection = PoseDetection(self.settings) + thread = threading.Thread(target=self.start_tracking) + thread.daemon = True + thread.start() + # replace button 'start tracking' by button 'update' + start_tracking_button.destroy() + update_button = tk.Button(root, text="Update", command=on_update, font=('Arial', 14, 'bold'), bg='#07121d', fg='white', activebackground='#3c9fbb', activeforeground='white', bd=0) + update_button.pack(pady=(5, 20), padx=20, fill=tk.X) + + # -- Start Tracking Button + start_tracking_button = tk.Button(root, text="Start Tracking", command=on_start, font=('Arial', 14, 'bold'), bg='#07121d', fg='white', activebackground='#3c9fbb', activeforeground='white', bd=0) + start_tracking_button.pack(pady=(5, 20), padx=20, fill=tk.X) + + def on_update(): + self.settings = ui.get_configuration() + self.pose_detection.settings = self.settings + + root.mainloop() + + +def main(): + plugin = Plugin() + plugin.setup_ui() if __name__ == '__main__': - - #print('=== VTS FULLBODY TRACKING ===') - - # --- OPEN USER WINDOW : CONFIGURATION TRACKING - root, settings = window_tracking_configuration() - - # ========= START TRACKING ========== - - asyncio.run(main(settings)) - - # ========= STOP PLUGIN ========== - - root.destroy() - #print('=== VTS FULLBODY TRACKING STOPPED ===') - + main() diff --git a/info.py b/info.py index c515e1c..a7c1e47 100644 --- a/info.py +++ b/info.py @@ -1,2 +1,2 @@ -VERSION = '0.1.10' +VERSION = '0.1.11' ICON_PATH = "icon.png" \ No newline at end of file diff --git a/plugin/nizima.py b/plugin/connector.py similarity index 51% rename from plugin/nizima.py rename to plugin/connector.py index 1f8c319..aa1b0bd 100644 --- a/plugin/nizima.py +++ b/plugin/connector.py @@ -1,10 +1,104 @@ +import pyvts +import cv2 import os -from plugin.mediapipe import get_parameters_names from pynizima import NizimaRequest from pynizima.errors import InvalidToken from info import VERSION, ICON_PATH +from .mediapipe import get_parameters_names +from plugin.errors_ui import error_connection_vts + +NIZIMA_LIVE = 'NizimaLIVE' +VTUBE_STUDIO = 'VTube Studio' + + +icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../icon.png')) + +plugin_info = { + "plugin_name": "VTS_Fullbody_Tracking", + "developer": "JellyDreams", + "authentication_token_path": "./token.txt", + "plugin_icon": icon_path +} + +vts_api = {"version": "1.0", "name": "VTubeStudioPublicAPI", "port": 8001} + + +class Connector: + + def __init__(self, software, port=8001): + self.software = software + self.connector = None + self.port = port + self.connection = False + + async def connect(self): + if self.software == NIZIMA_LIVE: + plugin_infos = { + "Name": 'Fullbody Tracking', + "Developer": 'Jelly Dreams' + } + self.connector = Nizima(plugin_infos=plugin_infos, port=self.port) + await self.connector.connect_nizima() + self.connection = True + + # ----- VTUBE STUDIO ------------- + elif self.software == VTUBE_STUDIO: + vts_api['port'] = self.port + self.connector = pyvts.vts(plugin_info=plugin_info, vts_api_info=vts_api) + + try: + await connection_vts(self.connector) + self.connection = True + except ConnectionError: + error_connection_vts() + cv2.waitKey(0) + cv2.destroyAllWindows() + return self.connection + + async def create_parameters(self): + if self.software == NIZIMA_LIVE: + if self.connector.connection: + await self.connector.create_parameters() + elif self.software == VTUBE_STUDIO: + await create_parameters_vts(self.connector) + + async def update_parameters(self, data): + if self.software == NIZIMA_LIVE: + data = [{"Id": key, "Value": value * 10} for key, value in data.items()] + await self.connector.set_live_parameter_values(data) + elif self.software == VTUBE_STUDIO: + # Prepare values to send + names, values = zip(*data.items()) + await send_paramters_vts(self.connector, values, names) + + +async def connection_vts(vts): + # Initialize VTube Studio connection + await vts.connect() + # Authenticate with VTube Studio API + # print('authentification VtubeStudio..') + await vts.request_authenticate_token() # get token + await vts.request_authenticate() # use token + + +async def create_parameters_vts(vts): + # print('Create parameters in VTube Studio') + # Prepare parameter names for each body part + parameter_names = get_parameters_names() + for parameter_name in parameter_names: + # Add custom parameters in VTube Studio + send_request_new_parameter = vts.vts_request.requestCustomParameter(parameter_name, min=-10, max=10) + await vts.request(send_request_new_parameter) + + +async def send_paramters_vts(vts, values, names): + """parameters in VTube Studio """ + send_request_parameter = vts.vts_request.requestSetMultiParameterValue(names, values, weight=1, face_found=True, mode='set') + await vts.request(send_request_parameter) + + class Nizima(NizimaRequest): def __init__(self, plugin_infos, token_path='token-fullbodytracking.txt', **kwargs): @@ -16,7 +110,6 @@ def __init__(self, plugin_infos, token_path='token-fullbodytracking.txt', **kwar NizimaRequest.__init__(self, **kwargs) async def connect_nizima(self): - self.token = self.load_token() # no token : register plugin and save token if not self.token: @@ -95,5 +188,3 @@ def group_name(self, input_name): group.remove(group[-1]) # remove axis info group_name = ' '.join([text.capitalize() for text in group]) return group_name - - diff --git a/plugin/errors_ui.py b/plugin/errors_ui.py new file mode 100644 index 0000000..7e36738 --- /dev/null +++ b/plugin/errors_ui.py @@ -0,0 +1,35 @@ +import numpy as np +import cv2 + +from info import VERSION + + +def error_connection_vts(): + """Display error message if there is a camera issue""" + image = np.zeros((180, 600, 3), dtype=np.uint8) + cv2.putText(image, text="Error: Unable to connect to Software", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.6, color=(255, 255, 255), thickness=1) + cv2.putText(image, text="- Ensure VTube Studio is running", org=(50, 75), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) + cv2.putText(image, text='- Enable "Start API (Allow Plugin)" in VTube Studio settings', org=(50, 100), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) + cv2.putText(image, text="- Verify that the API Port matches in the plugin and VTube Studio", org=(50, 125), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.4, color=(255, 255, 255), thickness=1) + cv2.imshow(f'VTS FullBody Tracking {VERSION}', image) + + +def error_pose_estimation(image): + """Display information when no tracking is available""" + cv2.putText(image, text="No Human Detected", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(255, 255, 255), thickness=1) + + +def error_camera(): + # Display error message if there is a camera issue + image = np.zeros((100, 500, 3), dtype=np.uint8) + cv2.putText(image, text="Problem with Camera", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.6, color=(255, 255, 255), thickness=1) + cv2.imshow(f'VTS FullBody Tracking {VERSION}', image) + + +def error_camera_url(camera_setting): + input_image = np.zeros((200, 800, 3), dtype=np.uint8) + cv2.putText(input_image, text=f"Failed to connect to the camera at URL: {camera_setting}", org=(50, 50), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(255, 255, 255), thickness=1) + cv2.imshow(f'VTS FullBody Tracking {VERSION}', input_image) + + + diff --git a/plugin/pose_detection.py b/plugin/pose_detection.py new file mode 100644 index 0000000..8ca51d0 --- /dev/null +++ b/plugin/pose_detection.py @@ -0,0 +1,96 @@ +import mediapipe as mp +import cv2 +from plugin.mediapipe import LIVE_STREAM +from plugin.errors_ui import error_camera_url, error_pose_estimation, error_camera +import numpy as np + + +class PoseDetection: + + def __init__(self, settings): + self.mode = settings['tracking_mode'] + self.mp_drawing = mp.solutions.drawing_utils + self.mp_drawing_styles = mp.solutions.drawing_styles + self.mp_pose = mp.solutions.pose + self.settings = settings + self.cap = None + self.results = [] + self.parameters = [] + self.camera_setting = self.settings['camera_id'] if not self.settings['camera_url'] else self.settings['camera_url'] + + def camera_connection(self): + # -- Camera to retrieve input for pose detection + self.camera_setting = self.settings['camera_id'] if not self.settings['camera_url'] else self.settings['camera_url'] + self.cap = cv2.VideoCapture(self.camera_setting) + if not self.cap.isOpened(): + error_camera_url(self.camera_setting) + cv2.waitKey(0) + cv2.destroyAllWindows() + + def detect(self, pose): + # -- LOOP Through Video + success, frame = self.cap.read() + + if success: + # current frame + input_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame) + image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Flip the image horizontally for a selfie-view display. + if self.settings['camera_mirror']: + image = cv2.flip(image, 1) + + # -- POSE DETECTION ---------------- + # Detect pose landmarks from the current frame + if self.mode == LIVE_STREAM: + frame.flags.writeable = False + self.results = pose.process(image) + + # Display Image for tracking window + image = self.render_image(input_image) + if self.results: + image = self.tracking_preview(image) + self.image = image + else: + if self.settings['camera_url']: + error_camera_url(self.camera_setting) + else: + error_camera() + return self.parameters + + def tracking_preview(self, image): + # -- DRAW LANDMARKS -------- + if self.results.pose_world_landmarks: + # Get coordinates + self.parameters = self.results + if self.mode == LIVE_STREAM: + # Draw the pose annotation on the image. + image.flags.writeable = True + + # Draw pose landmarks on the image. + self.mp_drawing.draw_landmarks( + image, + self.parameters.pose_landmarks, + self.mp_pose.POSE_CONNECTIONS, + landmark_drawing_spec=self.mp_drawing_styles.get_default_pose_landmarks_style()) + else: + error_pose_estimation(image) + return image + + def render_image(self, image): + # -- Display the original input or an empty image as a base + image = image.numpy_view() + if self.settings['preview_enabled']: + # Use the input image as a base if preview configuration is enabled + image = np.copy(image) + if self.settings['camera_mirror']: + image = cv2.flip(image, 1) + else: + # Create an empty image with the same dimensions as the input image + height, width, _ = image.shape + image = np.zeros((height, width, 3), dtype=np.uint8) + return image + + + + diff --git a/plugin/ui.py b/plugin/ui.py index a85632e..5030dd0 100644 --- a/plugin/ui.py +++ b/plugin/ui.py @@ -1,15 +1,13 @@ import tkinter as tk from tkinter import ttk - import cv2 from PIL import Image, ImageTk - -from info import VERSION, ICON_PATH import os - import platform import subprocess +from info import VERSION, ICON_PATH +from plugin.connector import VTUBE_STUDIO, NIZIMA_LIVE from plugin.mediapipe import LIVE_STREAM, IMAGE BACKGROUND_COLOR = "#333333" @@ -18,237 +16,248 @@ BACKGROUND_VTUBE_STUDIO = '#fb58bb' BACKGROUND_NIZIMA = '#692fc9' -WINDOW_SIZE = "340x540" -NIZIMA_LIVE = 'NizimaLIVE' -VTUBE_STUDIO = 'VTube Studio' +class UI: + def __init__(self, root): + self.root = root -ICON_NIZIMA = 'icon_nizimalive.png' -ICON_VTUBE_STUDIO = 'icon_vtubestudio.png' + self.camera_url_entry = None + self.selected_software = None + self.nizima_port_input = None + self.vtube_port_input = None + self.selected_tracking_mode = None + self.show_camera_view_var = None + self.camera_mirror_var = None + self.camera_selection = None + self.available_cameras = None + self.camera_options = None + self.icon_photo = None + self.nizima_icon = None + self.vtube_studio_icon = None -def window_tracking_configuration(): - """ Window for choosing configuration for tracking """ - - def get_configuration(): + def get_configuration(self): """ Retrieves configuration values from UI form """ - camera_url = camera_url_entry.get() - software = selected_software.get() - api_port = nizima_port_input.get() if software == NIZIMA_LIVE else vtube_port_input.get() + camera_url = self.camera_url_entry.get() + software = self.selected_software.get() + api_port = self.nizima_port_input.get() if software == NIZIMA_LIVE else self.vtube_port_input.get() if not api_port: if software == NIZIMA_LIVE: api_port = 22022 else: api_port = 8001 - tracking_mode = selected_tracking_mode.get() + tracking_mode = self.selected_tracking_mode.get() settings = { 'camera_url': camera_url, - 'preview_enabled': show_camera_view_var.get(), + 'preview_enabled': self.show_camera_view_var.get(), + 'camera_mirror': self.camera_mirror_var.get(), 'port': api_port, 'software': software, 'tracking_mode': tracking_mode } - if available_cameras: - camera_name = camera_selection.get() - camera_index = camera_options.index(camera_name) - settings['camera_id'] = available_cameras[camera_index]['id'] + if self.available_cameras: + camera_name = self.camera_selection.get() + camera_index = self.camera_options.index(camera_name) + settings['camera_id'] = self.available_cameras[camera_index]['id'] return settings - def toggle_inputs(): - """Enable or disable the input fields based on the selected software.""" - if selected_software.get() == VTUBE_STUDIO: - # Enable VTube Studio inputs - vtube_port_input.config(state="normal") - vtube_port_label.config(fg="white") - vtube_name_label.config(fg="white") - - # Disable NizimaLive inputs - nizima_port_input.config(state="disabled") - nizima_port_label.config(fg="gray") - nizima_name_label.config(fg="gray") - elif selected_software.get() == NIZIMA_LIVE: - # Enable NizimaLive inputs - nizima_port_input.config(state="normal") - nizima_port_label.config(fg="white") - nizima_name_label.config(fg="white") - - # Disable VTube Studio inputs - vtube_port_input.config(state="disabled") - vtube_port_label.config(fg="gray") - vtube_name_label.config(fg="gray") - - def toggle_inputs_tracking_mode(): - """Enable or disable the input fields based on the selected software.""" - if selected_tracking_mode.get() == LIVE_STREAM: - mode_live_stream_label.config(fg="white") - mode_image_label.config(fg="gray") - elif selected_tracking_mode.get() == IMAGE: - mode_image_label.config(fg="white") - mode_live_stream_label.config(fg="gray") - - root = tk.Tk() - root.title(f"VTS Fullbody Tracking {VERSION} - Settings") - root.geometry(WINDOW_SIZE) - root.configure(bg='#333333') - - # Set window icon - icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_PATH)) - icon_image = Image.open(icon_path.replace("plugin\\", "")) - icon_resized_for_window = icon_image.resize((32, 32)) - window_icon = ImageTk.PhotoImage(icon_resized_for_window) - root.iconphoto(False, window_icon) - - # -- Header - header_frame = tk.Frame(root, bg='#222222') - header_frame.pack(fill=tk.X) - - # Load and resize icon image for window and header - icon_photo = icon_image.resize((50, 50)) - icon_photo = ImageTk.PhotoImage(icon_photo) - icon_label = tk.Label(header_frame, image=icon_photo, bg='#222222') - icon_label.pack(side=tk.LEFT, padx=10, pady=10) - # Plugin name - title_label = tk.Label(header_frame, text="VTS Fullbody Tracking", bg='#222222', fg='white', font=('Arial', 12, 'bold')) - title_label.pack(padx=10, pady=(15, 2)) - # version - version_label = tk.Label(header_frame, text=f"version {VERSION}", bg='#222222', fg='#bfbfbf', font=('Arial', 8)) - version_label.pack(padx=10) - - available_cameras = get_available_cameras() - - if available_cameras: - # -- Camera Settings - camera_options = [info['label'] for info in available_cameras] - camera_settings_frame = create_section_header(root, "Camera Settings") - camera_settings_frame.pack(fill=tk.X, pady=(0, 5)) - - # Camera Selection - camera_selection = ttk.Combobox(root, values=camera_options, state='readonly', font=('Arial', 10)) - camera_selection.current(0) - camera_selection.pack(pady=(10, 5), padx=20, fill=tk.X) - else: - camera_label = tk.Label(root, text="No camera found\n Connect a camera before running the plugin") - camera_label.pack() - - # Camera external connection - camera_url_frame = tk.Frame(root, bg='#333333') - camera_url_frame.pack(pady=(10, 20), padx=20, fill=tk.X) - camera_url_label = tk.Label(camera_url_frame, text="Camera url", bg='#333333', fg='white', font=('Arial', 10)) - camera_url_label.pack(side=tk.LEFT, padx=5) - camera_url_entry = tk.Entry(camera_url_frame, validate="key", font=('Arial', 10), width=30) - camera_url_entry.pack(side=tk.LEFT, fill=tk.X) - - # Option for showing original input when displaying tracking pose - show_camera_view_var = tk.BooleanVar() - show_camera_view_checkbox = tk.Checkbutton(root, text="Show Camera View", variable=show_camera_view_var, bg='#333333', fg='white', activeforeground='white', activebackground="#333333", selectcolor='black', font=('Arial', 10)) - show_camera_view_checkbox.pack(anchor='w', padx=20, pady=(0, 10)) - - # -- Vtube Studio Settings - vtube_studio_frame = create_section_header(root, "Connection Settings") - vtube_studio_frame.pack(fill=tk.X, pady=(0, 5)) - - # -------------- - - # load vtube studio icon - image = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_VTUBE_STUDIO)) - image = Image.open(image.replace("plugin\\", "")) - image_resized = image.resize((25, 25)) - vtube_icon = ImageTk.PhotoImage(image_resized) - - # load nizima icon - image = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_NIZIMA)) - image = Image.open(image.replace("plugin\\", "")) - image_resized = image.resize((25, 25)) - nizima_icon = ImageTk.PhotoImage(image_resized) # Remplacez avec le chemin correct - - # Variable to track the selected software - selected_software = tk.StringVar(value=VTUBE_STUDIO) - - # Main frame - main_frame = tk.Frame(root, padx=10, pady=10, bg=BACKGROUND_COLOR) - main_frame.pack() - - # Row for VTube Studio - vtube_row = tk.Frame(main_frame, bg=BACKGROUND_COLOR) - vtube_row.pack(anchor="w", pady=5) - - vtube_radio = tk.Radiobutton(vtube_row, variable=selected_software, value=VTUBE_STUDIO, command=toggle_inputs, bg=BACKGROUND_COLOR ) - vtube_radio.pack(side="left", padx=5) - - vtube_icon_label = tk.Label(vtube_row, image=vtube_icon, bg=BACKGROUND_COLOR, width=18) - vtube_icon_label.pack(side="left", padx=5) - - vtube_name_label = tk.Label(vtube_row, text=VTUBE_STUDIO, bg=BACKGROUND_COLOR, fg=TEXT_COLOR, width=12) - vtube_name_label.pack(side="left", padx=5) - - vtube_port_label = tk.Label(vtube_row, text="Port:", bg=BACKGROUND_COLOR, fg=TEXT_COLOR) - vtube_port_label.pack(side="left", padx=5) - - vtube_port_input = tk.Entry(vtube_row, width=10) - vtube_port_input.pack(side="left", padx=5) - vtube_port_input.insert(0, "8001") # Default port - - # Row for NizimaLive - nizima_row = tk.Frame(main_frame, bg=BACKGROUND_COLOR) - nizima_row.pack(anchor="w", pady=5) - - nizima_radio = tk.Radiobutton(nizima_row, variable=selected_software, value=NIZIMA_LIVE, command=toggle_inputs, bg=BACKGROUND_COLOR) - nizima_radio.pack(side="left", padx=5) - - nizima_icon_label = tk.Label(nizima_row, image=nizima_icon, bg=BACKGROUND_COLOR, width=18) - nizima_icon_label.pack(side="left", padx=5) - - nizima_name_label = tk.Label(nizima_row, text=NIZIMA_LIVE, bg=BACKGROUND_COLOR, fg=TEXT_COLOR, width=12) - nizima_name_label.pack(side="left", padx=5) - - nizima_port_label = tk.Label(nizima_row, text="Port:", bg=BACKGROUND_COLOR, fg=TEXT_COLOR) - nizima_port_label.pack(side="left", padx=5) - - nizima_port_input = tk.Entry(nizima_row, width=10) - nizima_port_input.pack(side="left", padx=5) - nizima_port_input.insert(0, "22022") # Default port - - # Init state fields - toggle_inputs() - - # -- Tracking mode - tracking_setting_frame = create_section_header(root, "Tracking Settings") - tracking_setting_frame.pack(fill=tk.X, pady=(0, 5)) - - # Variable to track the selected software - selected_tracking_mode = tk.StringVar(value=LIVE_STREAM) - - tracking_mode_frame = tk.Frame(root, padx=30, bg=BACKGROUND_COLOR) - tracking_mode_frame.pack(anchor="w", pady=5) - - live_stream_row = tk.Frame(tracking_mode_frame, bg=BACKGROUND_COLOR) - live_stream_row.pack(anchor="w", pady=5) - mode_live_stream_radio = tk.Radiobutton(live_stream_row, variable=selected_tracking_mode, value=LIVE_STREAM, command=toggle_inputs_tracking_mode, bg=BACKGROUND_COLOR) - mode_live_stream_radio.pack(side="left", padx=5) - mode_live_stream_label = tk.Label(live_stream_row, text="Smooth (Slower)", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, anchor="w") - mode_live_stream_label.pack(side="left", padx=5) - - image_row = tk.Frame(tracking_mode_frame, bg=BACKGROUND_COLOR) - image_row.pack(anchor="w", pady=5) - mode_image_radio = tk.Radiobutton(image_row, variable=selected_tracking_mode, value=IMAGE, command=toggle_inputs_tracking_mode, bg=BACKGROUND_COLOR) - mode_image_radio.pack(side="left", padx=5) - mode_image_label = tk.Label(image_row, text="Fast (Less Stable)", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, anchor="w") - mode_image_label.pack(side="left", padx=5) - - # -- Start Tracking Button - start_tracking_button = tk.Button(root, text="Start Tracking", command=root.quit, font=('Arial', 14, 'bold'), bg='#07121d', fg='white', activebackground='#3c9fbb', activeforeground='white', bd=0) - start_tracking_button.pack(pady=(5, 20), padx=20, fill=tk.X) - - toggle_inputs_tracking_mode() - - root.mainloop() - - tracking_configuration = get_configuration() - - return root, tracking_configuration + def set_icons(self): + # Set window icon + icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_PATH)) + icon_image = Image.open(icon_path.replace("plugin\\", "")) + icon_resized_for_window = icon_image.resize((32, 32)) + window_icon = ImageTk.PhotoImage(icon_resized_for_window) + self.root.iconphoto(False, window_icon) + + def set_header(self): + header_frame = tk.Frame(self.root, bg='#222222') + header_frame.pack(fill=tk.X) + + # Load and resize icon image for window and header + icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_PATH)) + icon_image = Image.open(icon_path.replace("plugin\\", "")) + icon_photo = icon_image.resize((50, 50)) + self.icon_photo = ImageTk.PhotoImage(icon_photo) + icon_label = tk.Label(header_frame, image=self.icon_photo, bg='#222222') + icon_label.pack(side=tk.LEFT, padx=10, pady=10) + + # Plugin name + title_label = tk.Label(header_frame, text="VTS Fullbody Tracking", bg='#222222', fg='white', font=('Arial', 12, 'bold')) + title_label.pack(padx=10, pady=(15, 2)) + # version + version_label = tk.Label(header_frame, text=f"version {VERSION}", bg='#222222', fg='#bfbfbf', font=('Arial', 8)) + version_label.pack(padx=10) + + def set_available_cameras(self): + self.available_cameras = get_available_cameras() + + if self.available_cameras: + # -- Camera Settings + self.camera_options = [info['label'] for info in self.available_cameras] + camera_settings_frame = create_section_header(self.root, "Camera Settings") + camera_settings_frame.pack(fill=tk.X, pady=(0, 5)) + + # Camera Selection + self.camera_selection = ttk.Combobox(self.root, values=self.camera_options, state='readonly', font=('Arial', 10)) + self.camera_selection.current(0) + self.camera_selection.pack(pady=(10, 5), padx=20, fill=tk.X) + else: + camera_label = tk.Label(self.root, text="No camera found\n Connect a camera before running the plugin") + camera_label.pack() + + def set_external_camera(self): + # Camera external connection + camera_url_frame = tk.Frame(self.root, bg='#333333') + camera_url_frame.pack(pady=(10, 20), padx=20, fill=tk.X) + camera_url_label = tk.Label(camera_url_frame, text="Camera url", bg='#333333', fg='white', font=('Arial', 10)) + camera_url_label.pack(side=tk.LEFT, padx=5) + self.camera_url_entry = tk.Entry(camera_url_frame, validate="key", font=('Arial', 10), width=30) + self.camera_url_entry.pack(side=tk.LEFT, fill=tk.X) + + def set_camera_view(self): + # Option for showing original input when displaying tracking pose + self.show_camera_view_var = tk.BooleanVar() + show_camera_view_checkbox = tk.Checkbutton(self.root, text="Show Camera View", variable=self.show_camera_view_var, bg='#333333', fg='white', activeforeground='white', activebackground="#333333", selectcolor='black', font=('Arial', 10)) + show_camera_view_checkbox.pack(anchor='w', padx=20, pady=(0, 10)) + + def set_camera_mirror(self): + # Option for showing original input when displaying tracking pose + self.camera_mirror_var = tk.BooleanVar() + camera_mirror_checkbox = tk.Checkbutton(self.root, text="Mirror Camera", variable=self.camera_mirror_var, bg='#333333', fg='white', activeforeground='white', activebackground="#333333", selectcolor='black', font=('Arial', 10)) + camera_mirror_checkbox.pack(anchor='w', padx=20, pady=(0, 10)) + + def set_connection_software(self): + ICON_NIZIMA = 'icon_nizimalive.png' + ICON_VTUBE_STUDIO = 'icon_vtubestudio.png' + + # -- Vtube Studio Settings + vtube_studio_frame = create_section_header(self.root, "Connection Settings") + vtube_studio_frame.pack(fill=tk.X, pady=(0, 5)) + + # -------------- + def toggle_inputs(): + """Enable or disable the input fields based on the selected software.""" + if self.selected_software.get() == VTUBE_STUDIO: + # Enable VTube Studio inputs + self.vtube_port_input.config(state="normal") + vtube_port_label.config(fg="white") + vtube_name_label.config(fg="white") + + # Disable NizimaLive inputs + self.nizima_port_input.config(state="disabled") + nizima_port_label.config(fg="gray") + nizima_name_label.config(fg="gray") + elif self.selected_software.get() == NIZIMA_LIVE: + # Enable NizimaLive inputs + self.nizima_port_input.config(state="normal") + nizima_port_label.config(fg="white") + nizima_name_label.config(fg="white") + + # Disable VTube Studio inputs + self.vtube_port_input.config(state="disabled") + vtube_port_label.config(fg="gray") + vtube_name_label.config(fg="gray") + + # load vtube studio icon + image = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_VTUBE_STUDIO)) + image = Image.open(image.replace("plugin\\", "")) + image_resized = image.resize((25, 25)) + self.vtube_studio_icon = ImageTk.PhotoImage(image_resized) + + # load nizima icon + image = os.path.abspath(os.path.join(os.path.dirname(__file__), ICON_NIZIMA)) + image = Image.open(image.replace("plugin\\", "")) + image_resized = image.resize((25, 25)) + self.nizima_icon = ImageTk.PhotoImage(image_resized) + + # Variable to track the selected software + self.selected_software = tk.StringVar(value=VTUBE_STUDIO) + + # Main frame + main_frame = tk.Frame(self.root, padx=10, pady=10, bg=BACKGROUND_COLOR) + main_frame.pack() + + # Row for VTube Studio + vtube_row = tk.Frame(main_frame, bg=BACKGROUND_COLOR) + vtube_row.pack(anchor="w", pady=5) + + vtube_radio = tk.Radiobutton(vtube_row, variable=self.selected_software, value=VTUBE_STUDIO, command=toggle_inputs, + bg=BACKGROUND_COLOR) + vtube_radio.pack(side="left", padx=5) + + vtube_icon_label = tk.Label(vtube_row, image=self.vtube_studio_icon, bg=BACKGROUND_COLOR, width=18) + vtube_icon_label.pack(side="left", padx=5) + + vtube_name_label = tk.Label(vtube_row, text=VTUBE_STUDIO, bg=BACKGROUND_COLOR, fg=TEXT_COLOR, width=12) + vtube_name_label.pack(side="left", padx=5) + + vtube_port_label = tk.Label(vtube_row, text="Port:", bg=BACKGROUND_COLOR, fg=TEXT_COLOR) + vtube_port_label.pack(side="left", padx=5) + + self.vtube_port_input = tk.Entry(vtube_row, width=10) + self.vtube_port_input.pack(side="left", padx=5) + self.vtube_port_input.insert(0, "8001") # Default port + + # Row for NizimaLive + nizima_row = tk.Frame(main_frame, bg=BACKGROUND_COLOR) + nizima_row.pack(anchor="w", pady=5) + + nizima_radio = tk.Radiobutton(nizima_row, variable=self.selected_software, value=NIZIMA_LIVE, command=toggle_inputs, bg=BACKGROUND_COLOR) + nizima_radio.pack(side="left", padx=5) + + nizima_icon_label = tk.Label(nizima_row, image=self.nizima_icon, bg=BACKGROUND_COLOR, width=18) + nizima_icon_label.pack(side="left", padx=5) + + nizima_name_label = tk.Label(nizima_row, text=NIZIMA_LIVE, bg=BACKGROUND_COLOR, fg=TEXT_COLOR, width=12) + nizima_name_label.pack(side="left", padx=5) + + nizima_port_label = tk.Label(nizima_row, text="Port:", bg=BACKGROUND_COLOR, fg=TEXT_COLOR) + nizima_port_label.pack(side="left", padx=5) + + self.nizima_port_input = tk.Entry(nizima_row, width=10) + self.nizima_port_input.pack(side="left", padx=5) + self.nizima_port_input.insert(0, "22022") # Default port + + # Init state fields + toggle_inputs() + + def set_tracking_mode(self): + def toggle_inputs_tracking_mode(): + """Enable or disable the input fields based on the selected software.""" + if self.selected_tracking_mode.get() == LIVE_STREAM: + mode_live_stream_label.config(fg="white") + mode_image_label.config(fg="gray") + elif self.selected_tracking_mode.get() == IMAGE: + mode_image_label.config(fg="white") + mode_live_stream_label.config(fg="gray") + + # -- Tracking mode + tracking_setting_frame = create_section_header(self.root, "Tracking Settings") + tracking_setting_frame.pack(fill=tk.X, pady=(0, 5)) + + # Variable to track the selected software + self.selected_tracking_mode = tk.StringVar(value=LIVE_STREAM) + + tracking_mode_frame = tk.Frame(self.root, padx=30, bg=BACKGROUND_COLOR) + tracking_mode_frame.pack(anchor="w", pady=5) + + live_stream_row = tk.Frame(tracking_mode_frame, bg=BACKGROUND_COLOR) + live_stream_row.pack(anchor="w", pady=5) + mode_live_stream_radio = tk.Radiobutton(live_stream_row, variable=self.selected_tracking_mode, value=LIVE_STREAM, command=toggle_inputs_tracking_mode, bg=BACKGROUND_COLOR) + mode_live_stream_radio.pack(side="left", padx=5) + mode_live_stream_label = tk.Label(live_stream_row, text="Smooth (Slower)", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, anchor="w") + mode_live_stream_label.pack(side="left", padx=5) + + image_row = tk.Frame(tracking_mode_frame, bg=BACKGROUND_COLOR) + image_row.pack(anchor="w", pady=5) + mode_image_radio = tk.Radiobutton(image_row, variable=self.selected_tracking_mode, value=IMAGE, command=toggle_inputs_tracking_mode, bg=BACKGROUND_COLOR) + mode_image_radio.pack(side="left", padx=5) + mode_image_label = tk.Label(image_row, text="Fast (Less Stable)", bg=BACKGROUND_COLOR, fg=TEXT_COLOR, anchor="w") + mode_image_label.pack(side="left", padx=5) + toggle_inputs_tracking_mode() def get_available_cameras_names(): diff --git a/plugin/vtube_studio.py b/plugin/vtube_studio.py deleted file mode 100644 index 70bc7a3..0000000 --- a/plugin/vtube_studio.py +++ /dev/null @@ -1,32 +0,0 @@ -from .mediapipe import get_parameters_names - -async def connection_vts(vts): - # Initialize VTube Studio connection - await vts.connect() - - # Authenticate with VTube Studio API - # print('authentification VtubeStudio..') - await vts.request_authenticate_token() # get token - await vts.request_authenticate() # use token - - # TODO: Error Message if Vtube Studio not open - # TODO: Error Message if Port cant connect - - -async def create_parameters_vts(vts): - # print('Create parameters in VTube Studio') - - # Prepare parameter names for each body part - parameter_names = get_parameters_names() - - # Maximum of 100 parameters allowed to be created in VTube Studio per plugin - for parameter_name in parameter_names: - # Add custom parameters in VTube Studio - send_request_new_parameter = vts.vts_request.requestCustomParameter(parameter_name, min=-10, max=10) - await vts.request(send_request_new_parameter) - -async def send_paramters_vts(vts, values, names): - """ parameters in VTube Studio """ - - send_request_parameter = vts.vts_request.requestSetMultiParameterValue(names, values, weight=1, face_found=True, mode='set') - await vts.request(send_request_parameter) \ No newline at end of file