Как в следующем коде ускорить предсказание модели с помощью многопоточности:
def parse_yolo_annotations(file_path):
    """
    Извлекает аннотации из файла формата YOLO.

    :param file_path: Путь к файлу аннотаций.
    :return: Список аннотаций, где каждая аннотация представлена в виде словаря.
    """
    annotations = []

    with open(file_path, 'r') as file:
        for line in file:
            # Разделяем строку на части
            parts = line.strip().split()
            if len(parts) < 5:
                continue  # Пропускаем некорректные строки

            # Извлекаем данные
            class_id = int(parts[0])
            x_center = float(parts[1])
            y_center = float(parts[2])
            width = float(parts[3])
            height = float(parts[4])

            # Добавляем аннотацию в список
            annotations.append({
                'class_id': class_id,
                'x_center': x_center,
                'y_center': y_center,
                'width': width,
                'height': height
            })

    return annotations

def get_roboflow_predictions(api_key, model_id, image_path, CLIENT):
    """
    Получает предсказания от API Roboflow.

    :param api_key: API ключ Roboflow.
    :param workspace_name: Название рабочей области.
    :param model_name: Название модели.
    :param image_path: Путь к изображению.
    :return: Предсказания модели.
    """
    image = Image.open(image_path)
    predictions = CLIENT.infer(image, model_id=model_id)['predictions']

    return predictions


def calculate_metrics(predictions, annotations, img_width, img_height, num_classes, iou_threshold=0.5):
    """
    Вычисляет метрики Precision, Recall и mAP50-95.

    :param predictions: Предсказания модели.
    :param annotations: Реальные аннотации.
    :param img_width: Ширина изображения.
    :param img_height: Высота изображения.
    :param num_classes: Количество классов.
    :param iou_threshold: Порог IoU для вычисления метрик.
    :return: Метрики Precision, Recall и mAP50-95.
    """
    # Преобразуем предсказания и аннотации в формат, подходящий для ConfusionMatrix
    pred_boxes = []
    pred_classes = []
    pred_scores = []
    true_boxes = []
    true_classes = []

    for pred in predictions['predictions']:
        x_center = pred['x'] * img_width
        y_center = pred['y'] * img_height
        width = pred['width'] * img_width
        height = pred['height'] * img_height
        x_min = x_center - width / 2
        y_min = y_center - height / 2
        x_max = x_center + width / 2
        y_max = y_center + height / 2

        pred_boxes.append([x_min, y_min, x_max, y_max])
        pred_classes.append(pred['class'])
        pred_scores.append(pred['confidence'])

    for ann in annotations:
        x_center = ann['x_center'] * img_width
        y_center = ann['y_center'] * img_height
        width = ann['width'] * img_width
        height = ann['height'] * img_height
        x_min = x_center - width / 2
        y_min = y_center - height / 2
        x_max = x_center + width / 2
        y_max = y_center + height / 2

        true_boxes.append([x_min, y_min, x_max, y_max])
        true_classes.append(ann['class_id'])

    # Создаем объект ConfusionMatrix
    conf_matrix = ConfusionMatrix(nc=num_classes, conf=iou_threshold)

    # Обновляем матрицу ошибок
    for pred_box, pred_class, pred_score in zip(pred_boxes, pred_classes, pred_scores):
        conf_matrix.process_batch(np.array([pred_box]), np.array([pred_class]), np.array([pred_score]))

    for true_box, true_class in zip(true_boxes, true_classes):
        conf_matrix.process_batch(np.array([true_box]), np.array([true_class]), np.array([1.0]))

    # Вычисляем метрики
    precision, recall = conf_matrix.matrix.mean(axis=0), conf_matrix.matrix.mean(axis=1)
    mAP50_95 = conf_matrix.ap50[0]  # Используем AP50 как приближение для mAP50-95

    return precision, recall, mAP50_95

def process_dataset(dataset_path, subset, api_key, model_id, num_classes):
    """
    Обрабатывает изображения из указанной папки и вычисляет метрики на всем наборе данных.

    :param dataset_path: Путь к датасету.
    :param subset: Подмножество данных (train, test, valid).
    :param api_key: API ключ Roboflow.
    :param workspace_name: Название рабочей области.
    :param model_name: Название модели.
    :param num_classes: Количество классов.
    """
    image_dir = os.path.join(dataset_path, subset, 'images')
    label_dir = os.path.join(dataset_path, subset, 'labels')

    # Проверяем существование директорий
    if not os.path.exists(image_dir) or not os.path.exists(label_dir):
        print(f"Директории для {subset} не найдены.")
        return

    # Получаем список файлов изображений
    image_files = [f for f in os.listdir(image_dir) if f.endswith('.jpg') or f.endswith('.png')]

    predictions_list = []
    annotations_list = []
    img_widths = []
    img_heights = []
    counter = 0
    for image_file in image_files:
        # Формируем путь к файлу изображения и аннотаций
        image_path = os.path.join(image_dir, image_file)
        label_path = os.path.join(label_dir, os.path.splitext(image_file)[0] + '.txt')

        # Проверяем существование файла аннотаций
        if not os.path.exists(label_path):
            print(f"Файл аннотаций для {image_file} не найден.")
            continue


        # Получаем аннотации
        annotations = parse_yolo_annotations(label_path)
        annotations_list.append(annotations)

        # Получаем предсказания от API Roboflow

        CLIENT = InferenceHTTPClient(
            api_url="https://detect.roboflow.com",
            api_key="eDrAo98HJiUSpfaeBDBW"
        )


        predictions = get_roboflow_predictions(api_key, model_id, image_path, CLIENT)
        predictions_list.append(predictions)

        # Загружаем изображение для получения размеров
        image = cv2.imread(image_path)
        if image is None:
            print(f"Не удалось загрузить изображение {image_file}.")
            continue

        img_height, img_width = image.shape[:2]
        img_heights.append(img_height)
        img_widths.append(img_width)
        counter += 1
        print(f'image {counter} processed')

    # Вычисляем метрики на всем наборе данных
    precision, recall, mAP50_95 = calculate_metrics(predictions_list, annotations_list, img_widths, img_heights, num_classes)

    # Выводим метрики
    print(f"Precision: {precision}")
    print(f"Recall: {recall}")
    print(f"mAP50-95: {mAP50_95}")

# Пример использования
dataset_path = 'datasets/shd'
api_key = 'eDrAo98HJiUSpfaeBDBW'
model_id = 'shd-b9maz/1'
num_classes = 4  # Укажите количество классов в вашем датасете

process_dataset(dataset_path, 'test', api_key, model_id, num_classes)