Потокобезопасность и медленный pyplot.hist

Функция hist в matplotlib.pyplot работает очень медленно, что, по-видимому, связано с выбранной мной структурой. Я создал в Tkinter переднюю панель, которая запускает цикл управления камерой. Чтобы обеспечить оперативность цикла управления, я создал класс ImageProcessor, который собирает, обрабатывает и отображает изображения в cv2. Объект ImageProcessor выполняется в своем собственном потоке. Это работает до тех пор, пока я не пытаюсь построить гистограмму изображения.

Поскольку Tkinter не является потокобезопасным, я использую Agg в качестве бэкэнда и рисую нарисованный холст pyplot.figure с помощью cv2. Расчет гистограммы изображения с помощью pyplot.hist занимает более 20 секунд. Самостоятельный расчет гистограммы занимает всего 0,5 секунды.

Как это проявляется? Нужно ли запускать Matplotlib из основного потока или этого достаточно, если с ним взаимодействует только один поток (как в моем случае)? Или в моем коде есть еще одно недоразумение?

import threading
import time
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from timeit import default_timer as timer
from datetime import timedelta
import queue


class ImageProcessor(threading.Thread):
    def __init__(self):
        matplotlib.use('Agg')
        threading.Thread.__init__(self)

        # initialize plot for histograms
        self.hist_fig = plt.figure()

        self.loop = True
        self.continuous_acquisition_var = False
        self.a = None

    def run(self):

        while self.loop:

            self.a = np.random.uniform(low=0, high=16384, size=12320768).reshape((4096, 3008))

            self.hist_fig.clf()  # clear histogram plot

            start = timer()
            plt.hist(self.a.flatten(), bins=256, range=(0.0, 16384), fc='r', ec='r')
            end = timer()
            print(timedelta(seconds=end - start))

    def stop(self):
        self.loop = False


def ctl_loop(command):
    ctl_loop_var = True

    img_proc = ImageProcessor()
    img_proc.daemon = True
    img_proc.start()

    while ctl_loop_var:  # main loop

        while not command.empty():
            q_element = command.get()
            task = q_element[0]
            data = q_element[1]

            func = getattr(img_proc, task)
            func(data)

            if task == "stop":
                ctl_loop_var = False


if __name__ == '__main__':
    cmd_queue = queue.Queue()

    ctl = threading.Thread(target=ctl_loop, args=(cmd_queue, ))
    ctl.daemon = True
    ctl.start()

    time.sleep(40)

    cmd_queue.put(('stop', ''))

🤔 А знаете ли вы, что...
С Python можно создавать веб-скраперы для извлечения данных из веб-сайтов.


3
107
1

Ответ:

Решено

Решение простое и не имеет ничего общего с plt.hist. Просто добавьте строку time.sleep(0.01) в основной цикл. Причина в том, что многопоточность — это не то же самое, что многопроцессорность. Все потоки используют один и тот же процесс (ЦП), то есть одновременно может выполняться только один поток. В вашем случае основной поток (цикл while ctl_loop_var) как можно быстрее проверяет, истинно ли ctl_loop_var, не позволяя другому потоку что-либо делать. Поэтому убедитесь, что вы не создаете ненужную нагрузку на процессор. Это относится и к многопроцессорной обработке, хотя влияние может быть менее заметным.

def ctl_loop(command):
    ctl_loop_var = True

    img_proc = ImageProcessor()
    img_proc.daemon = True
    img_proc.start()

    while ctl_loop_var:  # main loop

        while not command.empty():
            q_element = command.get()
            task = q_element[0]
            data = q_element[1]

            if task == "stop":
                img_proc.stop()
                ctl_loop_var = False

            else:
                func = getattr(img_proc, task)
                func(data)
    
        time.sleep(.01)  # give the other thread time to process

Кроме того, код также исправляет две ошибки в исходном коде:

  1. ImageProcessor.stop принимает только один аргумент
  2. ImageProcessor.stop не останавливал поток должным образом, когда основной поток перегружен.

Я также заметил, что ваша реализация с plt.hist(..., bins=256, range=(0.0, 16384)) примерно в 7 раз быстрее, чем с plt.hist(..., bins=list_of_bins)! Не меняйте его ;).