Проверь код и адаптируй его производительность для наибольшей скорости вычислений на GPU:

import pandas as pd
import numpy as np
from collections import Counter
import scipy
import torch
import pywt
import matplotlib.pyplot as plt

# Определяем доступное устройство для работы (cuda > mps > cpu)
device = torch.device('cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu')


class FeatureExtractor:
    '''
    Класс для извлечения признаков из данных

    В классе рассчитываются три группы признаков: Distance, Angular и Geometric
    Distance - расстояния между точками руки
    Angular - углы между точками руки
    Geometric - геометрические характеристики
    '''

    @staticmethod
    def calc_distance(x1, x0, y1, y0):
        x1, x0, y1, y0 = map(lambda arr: torch.tensor(arr).to(device), (x1, x0, y1, y0))
        return torch.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)

    @staticmethod
    def get_distance_based_features(hands_raw_data):
        # Convert input data to GPU tensors
        # hands_raw_data = {k: torch.tensor(v).to(device) for k, v in hands_raw_data.items()}
        hands_raw_data = {k: torch.tensor(v.values, dtype=torch.float32).to(device) for k, v in hands_raw_data.items()}

        # Compute distances
        d_l_1 = FeatureExtractor.calc_distance(hands_raw_data['x_left_4'], hands_raw_data['x_left_1'],
                                               hands_raw_data['y_left_4'], hands_raw_data['y_left_1'])
        d_l_2 = FeatureExtractor.calc_distance(hands_raw_data['x_left_8'], hands_raw_data['x_left_5'],
                                               hands_raw_data['y_left_8'], hands_raw_data['y_left_5'])
        d_l_3 = FeatureExtractor.calc_distance(hands_raw_data['x_left_12'], hands_raw_data['x_left_9'],
                                               hands_raw_data['y_left_12'], hands_raw_data['y_left_9'])
        d_l_4 = FeatureExtractor.calc_distance(hands_raw_data['x_left_16'], hands_raw_data['x_left_13'],
                                               hands_raw_data['y_left_16'], hands_raw_data['y_left_13'])
        d_l_5 = FeatureExtractor.calc_distance(hands_raw_data['x_left_20'], hands_raw_data['x_left_17'],
                                               hands_raw_data['y_left_20'], hands_raw_data['y_left_17'])
        d_r_1 = FeatureExtractor.calc_distance(hands_raw_data['x_right_4'], hands_raw_data['x_right_1'],
                                               hands_raw_data['y_right_4'], hands_raw_data['y_right_1'])
        d_r_2 = FeatureExtractor.calc_distance(hands_raw_data['x_right_8'], hands_raw_data['x_right_5'],
                                               hands_raw_data['y_right_8'], hands_raw_data['y_right_5'])
        d_r_3 = FeatureExtractor.calc_distance(hands_raw_data['x_right_12'], hands_raw_data['x_right_9'],
                                               hands_raw_data['y_right_12'], hands_raw_data['y_right_9'])
        d_r_4 = FeatureExtractor.calc_distance(hands_raw_data['x_right_16'], hands_raw_data['x_right_13'],
                                               hands_raw_data['y_right_16'], hands_raw_data['y_right_13'])
        d_r_5 = FeatureExtractor.calc_distance(hands_raw_data['x_right_20'], hands_raw_data['x_right_17'],
                                               hands_raw_data['y_right_20'], hands_raw_data['y_right_17'])

        # Collect results and convert to CPU for DataFrame creation
        array_of_dist = [d_l_1.cpu().numpy(), d_l_2.cpu().numpy(), d_l_3.cpu().numpy(), d_l_4.cpu().numpy(), d_l_5.cpu().numpy(),
                         d_r_1.cpu().numpy(), d_r_2.cpu().numpy(), d_r_3.cpu().numpy(), d_r_4.cpu().numpy(), d_r_5.cpu().numpy()]
        df_distance_features = pd.DataFrame(np.stack(array_of_dist, axis=1), columns=['d_l_1', 'd_l_2', 'd_l_3', 'd_l_4', 'd_l_5',
                                                                                      'd_r_1', 'd_r_2', 'd_r_3', 'd_r_4', 'd_r_5'])
        return df_distance_features

    @staticmethod
    def angles_calculation(a, b, c):
        a, b, c = map(lambda val: torch.tensor(val).to(device), (a, b, c))
        alpha = torch.rad2deg(torch.acos((b**2 + c**2 - a**2) / (2 * b * c)))
        beta = torch.rad2deg(torch.acos((a**2 + c**2 - b**2) / (2 * a * c)))
        gamma = torch.rad2deg(torch.acos((a**2 + b**2 - c**2) / (2 * a * b)))
        return alpha, beta, gamma

    @staticmethod
    def get_angular_features(hands_raw_data):
        # hands_raw_data = {k: torch.tensor(v).to(device) for k, v in hands_raw_data.items()}
        hands_raw_data = {k: torch.tensor(v.values, dtype=torch.float32).to(device) for k, v in hands_raw_data.items()}

        a = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_4'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_4'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_left_4'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_4'], hands_raw_data['y_left_8'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_8'])
        tr_l_1_a, tr_l_1_b, tr_l_1_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_12'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_left_8'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_8'], hands_raw_data['y_left_12'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_8'])
        tr_l_2_a, tr_l_2_b, tr_l_2_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_12'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_left_12'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_12'], hands_raw_data['y_left_16'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_16'])
        tr_l_3_a, tr_l_3_b, tr_l_3_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_20'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_left_16'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_16'], hands_raw_data['y_left_20'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_left_0'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_0'], hands_raw_data['y_left_16'])
        tr_l_4_a, tr_l_4_b, tr_l_4_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_4'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_4'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_right_4'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_4'], hands_raw_data['y_right_8'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_8'])
        tr_r_1_a, tr_r_1_b, tr_r_1_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_12'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_right_8'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_8'], hands_raw_data['y_right_12'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_8'])
        tr_r_2_a, tr_r_2_b, tr_r_2_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_12'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_right_12'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_12'], hands_raw_data['y_right_16'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_16'])
        tr_r_3_a, tr_r_3_b, tr_r_3_y = FeatureExtractor.angles_calculation(a, b, c)

        a = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_20'])
        b = FeatureExtractor.calc_distance(hands_raw_data['x_right_16'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_16'], hands_raw_data['y_right_20'])
        c = FeatureExtractor.calc_distance(hands_raw_data['x_right_0'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_0'], hands_raw_data['y_right_16'])
        tr_r_4_a, tr_r_4_b, tr_r_4_y = FeatureExtractor.angles_calculation(a, b, c)

        array_of_angular_features = [tr_l_1_a.cpu().numpy(), tr_l_1_b.cpu().numpy(), tr_l_1_y.cpu().numpy(),
                                     tr_l_2_a.cpu().numpy(), tr_l_2_b.cpu().numpy(), tr_l_2_y.cpu().numpy(),
                                     tr_l_3_a.cpu().numpy(), tr_l_3_b.cpu().numpy(), tr_l_3_y.cpu().numpy(),
                                     tr_l_4_a.cpu().numpy(), tr_l_4_b.cpu().numpy(), tr_l_4_y.cpu().numpy(),
                                     tr_r_1_a.cpu().numpy(), tr_r_1_b.cpu().numpy(), tr_r_1_y.cpu().numpy(),
                                     tr_r_2_a.cpu().numpy(), tr_r_2_b.cpu().numpy(), tr_r_2_y.cpu().numpy(),
                                     tr_r_3_a.cpu().numpy(), tr_r_3_b.cpu().numpy(), tr_r_3_y.cpu().numpy(),
                                     tr_r_4_a.cpu().numpy(), tr_r_4_b.cpu().numpy(), tr_r_4_y.cpu().numpy()]
        
        df_angular_features = pd.DataFrame([array_of_angular_features], columns=[
            'tr_l_1_a', 'tr_l_1_b', 'tr_l_1_y', 'tr_l_2_a', 'tr_l_2_b', 'tr_l_2_y',
            'tr_l_3_a', 'tr_l_3_b', 'tr_l_3_y', 'tr_l_4_a', 'tr_l_4_b', 'tr_l_4_y',
            'tr_r_1_a', 'tr_r_1_b', 'tr_r_1_y', 'tr_r_2_a', 'tr_r_2_b', 'tr_r_2_y',
            'tr_r_3_a', 'tr_r_3_b', 'tr_r_3_y', 'tr_r_4_a', 'tr_r_4_b', 'tr_r_4_y'
        ])

        return df_angular_features

    @staticmethod
    def get_heron_feature(m, n, o):
        m, n, o = map(lambda val: torch.tensor(val).to(device), (m, n, o))
        t = (m + n + o) / 2
        G = torch.sqrt(t * (t - m) * (t - n) * (t - o))
        return G

    @staticmethod
    def get_geometric_features(hands_raw_data):
        # hands_raw_data = {k: torch.tensor(v).to(device) for k, v in hands_raw_data.items()}
        hands_raw_data = {k: torch.tensor(v.values, dtype=torch.float32).to(device) for k, v in hands_raw_data.items()}

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_1'], hands_raw_data['x_left_4'],
                                           hands_raw_data['y_left_1'], hands_raw_data['y_left_4'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_4'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_4'], hands_raw_data['y_left_8'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_1'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_1'], hands_raw_data['y_left_8'])
        G_l_1 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_1'], hands_raw_data['x_left_8'],
                                           hands_raw_data['y_left_1'], hands_raw_data['y_left_8'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_8'], hands_raw_data['x_left_5'],
                                           hands_raw_data['y_left_8'], hands_raw_data['y_left_5'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_1'], hands_raw_data['x_left_5'],
                                           hands_raw_data['y_left_1'], hands_raw_data['y_left_5'])
        G_l_2 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_8'], hands_raw_data['x_left_5'],
                                           hands_raw_data['y_left_8'], hands_raw_data['y_left_5'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_8'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_8'], hands_raw_data['y_left_12'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_5'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_5'], hands_raw_data['y_left_12'])
        G_l_3 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_5'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_5'], hands_raw_data['y_left_12'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_9'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_9'], hands_raw_data['y_left_12'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_5'], hands_raw_data['x_left_9'],
                                           hands_raw_data['y_left_5'], hands_raw_data['y_left_9'])
        G_l_4 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_9'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_9'], hands_raw_data['y_left_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_12'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_12'], hands_raw_data['y_left_16'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_9'], hands_raw_data['x_left_12'],
                                           hands_raw_data['y_left_9'], hands_raw_data['y_left_12'])
        G_l_5 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_9'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_9'], hands_raw_data['y_left_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_9'], hands_raw_data['x_left_13'],
                                           hands_raw_data['y_left_9'], hands_raw_data['y_left_13'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_13'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_13'], hands_raw_data['y_left_16'])
        G_l_6 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_13'], hands_raw_data['x_left_16'],
                                           hands_raw_data['y_left_13'], hands_raw_data['y_left_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_16'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_16'], hands_raw_data['y_left_20'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_13'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_13'], hands_raw_data['y_left_20'])
        G_l_7 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_left_13'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_13'], hands_raw_data['y_left_20'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_left_17'], hands_raw_data['x_left_20'],
                                           hands_raw_data['y_left_17'], hands_raw_data['y_left_20'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_left_13'], hands_raw_data['x_left_17'],
                                           hands_raw_data['y_left_13'], hands_raw_data['y_left_17'])
        G_l_8 = FeatureExtractor.get_heron_feature(m, n, o)

        G_L = G_l_1+G_l_2+G_l_3+G_l_4+G_l_5+G_l_6+G_l_7+G_l_8

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_1'], hands_raw_data['x_right_4'],
                                           hands_raw_data['y_right_1'], hands_raw_data['y_right_4'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_4'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_4'], hands_raw_data['y_right_8'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_1'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_1'], hands_raw_data['y_right_8'])
        G_r_1 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_1'], hands_raw_data['x_right_8'],
                                           hands_raw_data['y_right_1'], hands_raw_data['y_right_8'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_8'], hands_raw_data['x_right_5'],
                                           hands_raw_data['y_right_8'], hands_raw_data['y_right_5'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_1'], hands_raw_data['x_right_5'],
                                           hands_raw_data['y_right_1'], hands_raw_data['y_right_5'])
        G_r_2 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_8'], hands_raw_data['x_right_5'],
                                           hands_raw_data['y_right_8'], hands_raw_data['y_right_5'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_8'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_8'], hands_raw_data['y_right_12'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_5'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_5'], hands_raw_data['y_right_12'])
        G_r_3 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_5'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_5'], hands_raw_data['y_right_12'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_9'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_9'], hands_raw_data['y_right_12'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_5'], hands_raw_data['x_right_9'],
                                           hands_raw_data['y_right_5'], hands_raw_data['y_right_9'])
        G_r_4 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_9'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_9'], hands_raw_data['y_right_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_12'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_12'], hands_raw_data['y_right_16'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_9'], hands_raw_data['x_right_12'],
                                           hands_raw_data['y_right_9'], hands_raw_data['y_right_12'])
        G_r_5 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_9'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_9'], hands_raw_data['y_right_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_9'], hands_raw_data['x_right_13'],
                                           hands_raw_data['y_right_9'], hands_raw_data['y_right_13'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_13'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_13'], hands_raw_data['y_right_16'])
        G_r_6 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_13'], hands_raw_data['x_right_16'],
                                           hands_raw_data['y_right_13'], hands_raw_data['y_right_16'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_16'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_16'], hands_raw_data['y_right_20'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_13'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_13'], hands_raw_data['y_right_20'])
        G_r_7 = FeatureExtractor.get_heron_feature(m, n, o)

        m = FeatureExtractor.calc_distance(hands_raw_data['x_right_13'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_13'], hands_raw_data['y_right_20'])
        n = FeatureExtractor.calc_distance(hands_raw_data['x_right_17'], hands_raw_data['x_right_20'],
                                           hands_raw_data['y_right_17'], hands_raw_data['y_right_20'])
        o = FeatureExtractor.calc_distance(hands_raw_data['x_right_13'], hands_raw_data['x_right_17'],
                                           hands_raw_data['y_right_13'], hands_raw_data['y_right_17'])
        G_r_8 = FeatureExtractor.get_heron_feature(m, n, o)

        G_R = G_r_1+G_r_2+G_r_3+G_r_4+G_r_5+G_r_6+G_r_7+G_r_8

        array_of_geometric_features = [G_L.cpu().numpy(), G_R.cpu().numpy()]
        df_geometric_features = pd.DataFrame([array_of_geometric_features], columns=['G_L', 'G_R'])

        return df_geometric_features

    @staticmethod
    def calculate_entropy(list_values):
        counter_values = Counter(list_values).most_common()
        probabilities = [elem[1]/len(list_values) for elem in counter_values]
        entropy = scipy.stats.entropy(probabilities)
        return entropy

    @staticmethod
    def calculate_statistics(list_values):
        n5 = np.nanpercentile(list_values, 5)
        n25 = np.nanpercentile(list_values, 25)
        n75 = np.nanpercentile(list_values, 75)
        n95 = np.nanpercentile(list_values, 95)
        median = np.nanpercentile(list_values, 50)
        mean = np.nanmean(list_values)
        std = np.nanstd(list_values)
        var = np.nanvar(list_values)
        rms = np.nanmean(np.sqrt(list_values**2))
        return [n5, n25, n75, n95, median, mean, std, var, rms]

    @staticmethod
    def calculate_crossings(list_values):
        zero_crossing_indices = np.nonzero(np.diff(np.array(list_values) > 0))[0]
        mean_crossing_indices = np.nonzero(np.diff(np.array(list_values) > np.nanmean(list_values)))[0]
        no_zero_crossings = len(zero_crossing_indices)
        no_mean_crossings = len(mean_crossing_indices)
        return [no_zero_crossings, no_mean_crossings]

    @staticmethod
    def calculate_energy(list_values):
        energy = np.sum(np.square(list_values))
        return energy

    @staticmethod
    def get_features(list_values):
        entropy = FeatureExtractor.calculate_entropy(list_values)
        crossings = FeatureExtractor.calculate_crossings(list_values)
        statistics = FeatureExtractor.calculate_statistics(list_values)
        energy = FeatureExtractor.calculate_energy(list_values)
        return [entropy] + crossings + statistics + [energy]

    @staticmethod
    def extract_features(hands_raw_data):
        df_distance_features = FeatureExtractor.get_distance_based_features(hands_raw_data)
        df_angular_features = FeatureExtractor.get_angular_features(hands_raw_data)
        df_geometric_features = FeatureExtractor.get_geometric_features(hands_raw_data)
        return df_distance_features, df_angular_features, df_geometric_features

    @staticmethod
    def get_cwt_features(idx, classname, sequence, df_cwt_feature, WAVELETNAME, WIDTH, SAMPLING_PERIOD):
        comp_data = {}    
        cwt_distance_features = []
        for signal_comp in range(0, sequence.shape[1]):
            signal = sequence[:, signal_comp]
            list_coeff, freqs = pywt.cwt(data=signal, scales=WIDTH, wavelet=WAVELETNAME, sampling_period=SAMPLING_PERIOD)
            
            for coeff in list_coeff:
                cwt_distance_features += FeatureExtractor.get_features(coeff)
                
        comp_data.update({'video_id': idx, 'class': classname, df_cwt_feature.columns[2]: cwt_distance_features})
        df_cwt_feature.loc[len(df_cwt_feature)] = comp_data
        
        return df_cwt_feature

    @staticmethod
    def plot_cwt(time, freqs, cwtmatr):
        fig, axs = plt.subplots(2, 1)
        pcm = axs[0].pcolormesh(time, freqs, cwtmatr)
        axs[0].set_xlabel("Time (s)")
        axs[0].set_ylabel("Frequency (Hz)")
        axs[0].set_title("Continuous Wavelet Transform (Scaleogram)")
        fig.colorbar(pcm, ax=axs[0])
        plt.show()