Перепиши работу этого приложения для работы на GPU:
from flask import Flask, request, render_template, Response, stream_with_context, redirect, url_for, send_from_directory, abort
import numpy as np
from werkzeug.utils import safe_join
import json
import pandas as pd
import cv2
import os
from concurrent.futures import ThreadPoolExecutor
from hand_detector import handDetector
from feature_extracture import feature_extractor
from queue import Queue
from flask_socketio import SocketIO
# import warnings 
# warnings.filterwarnings('ignore')

app = Flask(__name__)
socketio = SocketIO(app, async_mode='threading')
executor = ThreadPoolExecutor()
detector = handDetector()

left_hand_lmlist_queue = Queue()
right_hand_lmlist_queue = Queue()

BASE_DIR = os.path.dirname(os.path.abspath(__name__))
FILE_DIRECTORY = os.path.join(BASE_DIR, "files")
VIDEO_DIRECTORY = os.path.join(BASE_DIR, "static/videos")

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload():
    global videopath
    if 'video' not in request.files:
        return redirect(url_for('index'))
    
    video_file = request.files['video']
    
    if video_file.filename == '':
        return redirect(url_for('index'))

    videopath = safe_join(VIDEO_DIRECTORY, video_file.filename)
    
    return render_template('play.html')

@app.route('/files/<filename>')
def download_file(filename):
    try:
        file_path = safe_join(FILE_DIRECTORY, filename)
        print(file_path)
        return send_from_directory(FILE_DIRECTORY, filename, as_attachment=True)
    except FileNotFoundError:
        abort(404)

def generate():
    camera = cv2.VideoCapture(videopath)
    while True:
        success, frame = camera.read()
        if not success:
            print('Video End')
            file_name = 'df.csv'
            file_path = safe_join(FILE_DIRECTORY, file_name)
            df.to_csv(file_path)
            # Отправляем сообщение клиенту о завершении
            socketio.emit('video_finished', {'data': 'Video processing completed'})
            break
            
        else:
            frame, df = features_extraction(frame)
            ret, buffer = cv2.imencode('.jpg', frame)
            frame = buffer.tobytes()

            # Запуск предсказания в отдельном потоке
            executor.submit(emit, df)

            yield (b'--frame\r\n'
                    b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

def emit(df_window):
    # print(df_window)
    df_window = df_window.astype('float32')
    df_distance = feature_extractor.get_distance_based_features(df_window)
    df_angular = feature_extractor.get_angular_features(df_window)
    df_geometric = feature_extractor.get_geometric_features(df_window)

    df_distance_stat = calculate_stat_features(df_distance)
    # df_distance_wavelet = calculate_wavelet_features(df_distance, 'distance_wavelet_coefficients')

    df_angular_stat = calculate_stat_features(df_angular)
    # df_angular_wavelet = calculate_wavelet_features(df_angular, 'angular_wavelet_coefficients')

    df_geometric_stat = calculate_stat_features(df_geometric)
    # df_geometric_wavelet = calculate_wavelet_features(df_geometric, 'geometric_wavelet_coefficients')

    # distance_stat_features = pd.concat([df_distance_stat, df_distance_wavelet], axis=1)
    # angular_stat_features = pd.concat([df_angular_stat, df_angular_wavelet], axis=1)
    # geometric_stat_features = pd.concat([df_geometric_stat, df_geometric_wavelet], axis=1)

    # Отправка данных для графиков на клиент
    socketio.emit('distance_stat_features', df_distance_stat.to_dict(orient='records')[0])
    socketio.emit('angular_stat_features', df_angular_stat.to_dict(orient='records')[0])
    socketio.emit('geometric_stat_features', df_geometric_stat.to_dict(orient='records')[0])

    # Отправка данных для признаков вейвлет-преобразований
    # socketio.emit('distance_wavelet_features', df_distance_wavelet.to_dict(orient='records')[0])
    # socketio.emit('angular_wavelet_features', df_angular_wavelet.to_dict(orient='records')[0])
    # socketio.emit('geometric_wavelet_features', df_geometric_wavelet.to_dict(orient='records')[0])

# Функция поиска рук на видео
def features_extraction(frame):
    frame = detector.findHands(frame, draw=True)
    left_hand_lmlist, right_hand_lmlist = detector.findPosition(frame, draw=True)
    df_hands = detector.savePosition(left_hand_lmlist, right_hand_lmlist, detector.x_left_columns, detector.y_left_columns, detector.x_right_columns, detector.y_right_columns)
    detector.df_classification = pd.concat([detector.df_classification, df_hands], axis=0)
    left_hand_lmlist_queue.put(left_hand_lmlist)
    right_hand_lmlist_queue.put(right_hand_lmlist)

    return frame, detector.df_classification

@app.route("/video_feed")
def video_feed():
    return Response(generate(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/chart_data')
def chart_data():
    def update_data():
        while True:
            left_hand_lmlist = left_hand_lmlist_queue.get()
            right_hand_lmlist = right_hand_lmlist_queue.get()

            x_left = [point[1] for point in left_hand_lmlist]
            y_left = [point[2] for point in left_hand_lmlist]
            x_right = [point[1] for point in right_hand_lmlist]
            y_right = [point[2] for point in right_hand_lmlist]

            json_data = json.dumps(
                {'x_left': x_left, 'y_left': y_left, 'x_right': x_right, 'y_right': y_right})
            yield f"data:{json_data}\n\n"

    response = Response(stream_with_context(update_data()), mimetype="text/event-stream")
    response.headers["Cache-Control"] = "no-cache"
    response.headers["X-Accel-Buffering"] = "no"
    return response

# Функция расчета статистических признаков
def calculate_stat_features(df):
    video_stats = {}
    stat_features = []
    for col in df.columns:
        values = df[col].values
        n5 = np.nanpercentile(values, 5)
        n25 = np.nanpercentile(values, 25)
        n75 = np.nanpercentile(values, 75)
        n95 = np.nanpercentile(values, 95)
        median = np.nanpercentile(values, 50)
        mean = np.nanmean(values)
        std = np.nanstd(values)
        var = np.nanvar(values)
        rms = np.nanmean(np.sqrt(values**2))

        video_stats[f'{col}_n5'] = n5
        video_stats[f'{col}_n25'] = n25
        video_stats[f'{col}_n75'] = n75
        video_stats[f'{col}_n95'] = n95
        video_stats[f'{col}_median'] = median
        video_stats[f'{col}_mean'] = mean
        video_stats[f'{col}_std'] = std
        video_stats[f'{col}_var'] = var
        video_stats[f'{col}_rms'] = rms

    # Добавляем словарь в список
    stat_features.append(video_stats)
    df_stats = pd.DataFrame(stat_features)
    return df_stats


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=3000, debug=True)