Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Библиотека matplotlib

Глава 11 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: matplotlib
Автор: John Hunter, Michael Droettboom, перевод: А.Панин

11.2. Обзор архитектуры библиотеки matplotlib

Объект библиотеки, находящийся на верхнем уровне и содержащий и управляющий всеми элементами заданного графика, носит имя Figure. Одной из основных архитектурных задач, которую должна решать библиотека matplotlib, является реализация фреймворка для представления и манипуляции объектом Figure независимо от операции отображения объекта Figure в окне пользовательского интерфейса или файле. Это позволяет нам интегрировать усложненные функции и логические операции в объекты Figure, делая системы поддержки вывода данных или системы поддержки устройств вывода относительно простыми. Библиотека matplotlib реализует не только интерфейсы рисования для предоставления возможности вывода данных на множество устройств, но также базовые функции обработки событий и создания окон для большинства популярных тулкитов, используемых для построения пользовательских интерфейсов. Благодаря этому пользователи могут создавать довольно сложные интерактивные графики и тулкиты, поддерживающие ввод с использованием клавиатуры и мыши, которые могут быть подключены без модификаций к шести тулкитам для создания пользовательских интерфейсов, поддерживаемым нами.

Архитектура для реализации этих возможностей логически разделена на три уровня, которые могут рассматриваться в виде стека. При этом каждый уровень, находящийся над другим уровнем, знает метод обращения к нижележащему уровню, но нижележащий уровень не располагает информацией об уровнях, находящихся выше него. Три уровня снизу вверх: уровень вывода данных, уровень рисования, уровень сценариев.

Уровень вывода данных

Снизу стека расположен уровень вывода данных (backend layer), который предоставляет реализации абстрактных классов интерфейса:
  • FigureCanvas реализует концепцию поверхности для рисования (т.е., является "бумагой").
  • Renderer осуществляет рисование (т.е., является "кистью").
  • Event обрабатывает пользовательский ввод, представленный событиями от таких устройств, как клавиатура и мышь.

Для такого тулкита пользовательского интерфейса, как Qt, класс FigureCanvas содержит реализацию алгоритма, позволяющего произвести встраивание в созданное с помощью Qt окно (QtGui.QMainWindow), передать команды класса Renderer библиотеки matplotlib классу канвы (QtGui.QPainter) и преобразовать события тулкита Qt в события класса Event библиотеки matplotlib, который отправляет сигналы обработчику обратных вызовов функций для генерации событий, чтобы высокоуровневые обработчики смогли использовать их. Базовые абстрактные классы располагаются в модуле matplotlib.backend_bases, а все дочерние классы расположены в таких отдельных модулях, как matplotlib.backends.backend_qt4agg. В случае систем вывода данных в файлы изображений, таких, как PDF, PNG, SVG или PS, реализация класса FigureCanvas может просто инициализировать объект типа файла с описаниями стандартных заголовков, шрифтов и макро-функций наряду с отдельными объектами (линиями, текстом, прямоугольниками, и.т.д.), создаваемыми с помощью класса Renderer.

Задачей класса Renderer является предоставление низкоуровневого интерфейса рисования для изображения фигур на канве. Как было сказано выше, оригинальное приложение на основе библиотеки matplotlib являлось инструментом для визуализации данных ECoG на основе тулкита GTK+ и большая часть оригинальной архитектуры была создана под влиянием API GDK/GTK+, доступного в тот момент. Оригинальный API класса Renderer был создан на основе интерфейса Drawable GDK, который реализует такие примитивные методы, как draw_point, draw_line, draw_rectangle, draw_image, draw_polygon и draw_glyphs. В каждой из разрабатываемых нами систем вывода данных - первыми были системы вывода данных в файлы формата PostScript и с помощью библиотеки GD - был реализован API GDK Drawable, после чего его методы преобразовывались в используемые данной системой команды рисования. Как мы обсудили выше, эта необоснованно запутанная реализация новых систем вывода данных с большим количеством методов, а также этот API впоследствии были значительно упрощены, что привело к упрощению процесса портирования библиотеки matplotlib для использования новых тулкитов пользовательского интерфейса или спецификаций файлов.

Одним из удачно функционирующих архитектурных решений в рамках библиотеки matplotlib является поддержка низкоуровневой библиотеки вывода, использующей библиотеку шаблонов языка C++ с названием Anti-Grain Geometry или "agg" [She06]. Это высокопроизводительная библиотека для вывода 2D-графики со сглаживанием (anti-aliasing), которая позволяет создавать привлекательные изображения. Библиотека matplotlib поддерживает вставку буферов пикселей, выводимых библиотекой agg в качестве элемента пользовательского интерфейса на основе каждого из поддерживаемых нами тулкитов, поэтому становится возможным вывод идентичных с точностью до пикселя графиков в различных пользовательских интерфейсах и операционных системах. Так как при выводе с помощью matplotlib изображений в формате PNG также используется библиотека agg, изображение из файла будет идентичным изображению на экране, поэтому вы увидите график, который не изменится в различных пользовательских интерфейсах, операционных системах и файлах формата PNG.

Фреймворк Event из состава matplotlib связывает такие события уровня пользовательского интерфейса, как key-press-event или mouse-motion-event с классами KeyEvent или MouseEvent из состава matplotlib. Пользователи могут соединить эти события с функциями обратного вызова и осуществлять взаимодействие с их графиками и данными; например, для захвата элемента или множества элементов набора данных (pick) или манипуляции каким-либо аспектом отображения графика или его составных частей. Следующий пример кода иллюстрирует метод переключения отображения всех линий в использующем класс Axes окне при нажатии пользователем клавиши 't'.

Абстракция над фреймворком событий уровня тулкита для создания пользовательского интерфейса позволяет и разработчикам библиотеки matplotlib, и конечным пользователям осуществлять обработку событий пользовательского интерфейса в соответствии с подходом "разработать один раз и использовать везде". Например, функции интерактивной фиксации и масштабирования созданных с использованием библиотеки matplotlib изображений, работающие со всеми тулкитами пользовательского интерфейса, реализуются в рамках фреймворка обработки событий библиотеки matplotlib.

Уровень рисования

Иерархия классов уровня рисования (Artist hierarchy) находится на среднем уровне стека, а также является местом, где производится большая часть сложных операций. Продолжая аналогию, по которой класс FigureCanvas системы вывода данных является бумагой, класс Artist является объектом, который знает, как использовать класс Renderer (кисть) и поместить краску на канву. Все рисунки, принадлежащие классу Figure, которые вы видите, состоят из экземпляров класса Artist; заголовок, линии, метки на осях, изображения и другие элементы соответствуют отдельным экземплярам класса Artist (обратитесь к Рисунку 11.3). Базовым классом является класс matplotlib.artist.Artist, который содержит атрибуты, свойственные каждому классу Artist: данные преобразования, используемые для преобразования координат класса рисования в координаты канвы (этот процесс описан ниже в подробностях), настройки видимости, координаты области, устанавливающие регион, в котором может осуществляться рисование, строку с названием и интерфейс для осуществления взаимодействия с пользователем, например, для "выбора точек"; это взаимодействие осуществляется путем установления факта нажатия кнопки мыши в области рисования.

Готовый рисунок
Рисунок 11.2: Готовый рисунок

Иерархия экземпляров класса уровня рисования, используемая при создании Рисунка 11.2.
Рисунок 11.3: Иерархия экземпляров класса уровня рисования, используемая при создании Рисунка 11.2.

Объединение иерархии классов уровня рисования (Artist hierarchy) и системы вывода данных осуществляется в рамках метода draw. Например, в шаблоне класса, приведенном ниже, где мы создали класс SomeArtist, являющийся подклассом класса Artist, основным методом, который должен был быть реализован в рамках класса SomeArtist, является метод draw, с помощью которого примитив для рисования передается от системы вывода данных. Класс Artist не располагает информацией о том, какая система вывода данных будет использоваться для рисования (PDF, SVG, GTK+ DrawingArea, и.т.д.), но при этом он располагает информацией о том, как работать с API класса Renderer и будет использовать подходящий метод (draw_text или draw_path). Так как класс Renderer содержит указатель на канву и располагает информацией о том, как рисовать на ней, метод draw осуществляет преобразование абстрактного представления иерархии классов Artist в цвета буфера пикселей, координаты направлений в файле SVG или любое другое конкретное представление.

Существует два типа классов Artist в рамках иерархии классов уровня рисования. Примитивные классы (Primitive artists) представляют типы объектов, которые вы видите на графике: Line2D (двумерная линия), Rectangle (прямоугольник), Circle (окружность) и Text (текст). Композитные объекты (Composite artists) являются наборами классов Artist, такими, как классы Axis (ось), Tick (метки), Axes (координатные оси) и Figure (рисунок). Каждый композитный класс может содержать другие композитные классы также, как и примитивные классы. Например, класс Figure содержит один или несколько композитных классов Axes и фон изображения, представленного классом Figure, создан с помощью примитивного класса Rectangle.

Наиболее важным композитным классом является класс Axes, в рамках которого объявлена большая часть методов для создания графиков из состава API библиотеки matplotlib. Класс Axes содержит не только большую часть графических элементов, формирующих фон графика - метки, линии координатные оси, сетку, цвет, используемый для фона графика, но и множество вспомогательных методов для создания примитивных классов и добавления их в экземпляр класса Axes. Например, в Таблице 11.1 показан пример нескольких методов класса Axes, с помощью которых объекты графика создаются и сохраняются в экземпляре класса Axes.

Таблица 11.1: Пример методов класса Axes и экземпляров класса Artist, которые создаются с помощью них
Метод Создает класс Хранится в
Axes.imshow Один или несколько экземпляров matplotlib.image.AxesImage Axes.images
Axes.hist Множество экземпляров matplotlib.patch.Rectangle Axes.patches
Axes.plot Один или несколько экземпляров matplotlib.lines.Line2D Axes.lines

Ниже приведен простой сценарий на языке Python, иллюстрирующий описанные выше архитектурные решения. Он устанавливает систему вывода данных, соединяет с ней экземпляр класса Figure, использует библиотеку для работы с массивами numpy для генерации 10000 нормально распределенных случайных чисел и формирует их гистограмму.

Уровень сценариев (pyplot)

Приведенный выше сценарий, использующий API, работает очень хорошо, особенно в случае его использования разработчиками и примененная программная парадигма подходит для разработки серверных веб-приложений, приложений с пользовательским интерфейсом, или, возможно, для разработки сценариев и обмена ним со сторонними разработчиками. Для каждодневной работы, особенно в случае интерактивной исследовательской работы ставящих опыты ученых, которые не являются профессиональными программистами, синтаксис является в некоторой степени усложненным. Большинство специализированных языков программирования для анализа данных и их визуализации предоставляет простой интерфейс сценариев для упрощения выполнения стандартных задач, при этом библиотека matplotlib предоставляет интерфейс matplotlib.pyplot для выполнения аналогичных действий. Код, представленный выше, будет выглядеть следующим образом в случае использования pyplot:

Гистограмма, созданная с помощью pyplot
Рисунок 11.4: Гистограмма, созданная с помощью pyplot

Интерфейс pyplot является интерфейсом, изменяющим параметры состояния и выполняющим команды создания графиков с осями координат с последующим соединением их с выбранной системой вывода данных, а также поддерживающим внутренние структуры данных уровня модулей, представляющие текущий рисунок и координатные оси, в отношении которых непосредственно могут выполняться команды.

Давайте рассмотрим важные строки сценария для того, чтобы понять принцип управления внутренними данными состояния.
  • import matplotlib.pyplot as plt: В момент загрузки модуля pyplot происходит разбор локального файла конфигурации, в котором помимо различных параметров хранятся пользовательские настройки, содержащие выбранную систему вывода данных. Это может быть система вывода данных в пользовательский интерфейс, такая, как QtAgg и в этом случае приведенный выше сценарий импортирует модуль фреймворка для создания графического пользовательского интерфейса и создаст окно Qt с графиком, а также в качестве системы вывода данных может быть выбрана система вывода данных в файл, такая, как Agg и в этом случае сценарий сгенерирует файл и завершит свою работу.
  • plt.hist(x, 100): Это первая команда создания графика в сценарии. Интерфейс pyplot проверит внутренние структуры данных для установления того, присутствует ли экземпляр класса Figure для данного графика. В случае его наличия, он извлечет текущий экземпляр класса Axes и осуществит непосредственное создание графика путем вызова метода API Axes.hist. В том случае, если этого экземпляра класса не существует, будут созданы экземпляры классов Figure и Axes, которые будут сделаны текущими, после чего будет осуществлено непосредственное создание графика при помощи вызова метода Axes.hist.
  • plt.title(r'Normal distribution with $\mu=0,\sigma=1$'): Как было описано выше, интерфейс pyplot установит, присутствуют ли текущие экземпляры классов Figure и Axes. В случае их обнаружения он не будет создавать новых экземпляров, а вместо этого осуществит прямой вызов метода Axes.set_title существующего экземпляра класса Axes.
  • plt.show(): Этот вызов приведет к выводу изображения с помощью экземпляра класса Figure и в том случае, если пользователь выбрал систему вывода данных, использующую тулкит для создания графического интерфейса в файле конфигурации, будет запущен основной цикл приема событий графического интерфейса и пользователю будут показаны созданные изображения.

Незначительно сокращенная и упрощенная версия часто используемой функции для создания линий matplotlib.pyplot.plot интерфейса pyplot показана ниже для иллюстрации метода реализации функциональности объектно-ориентированного основного интерфейса matplotlib с помощью функции интерфейса pyplot. Другие функции интерфейса сценариев pyplot используют аналогичные архитектурные решения:

Директива языка Python @autogen_docstring(Axes.plot) извлекает строку документации для соответствующего метода API и добавляет ее корректно отформатированную версию в метод pyplot.plot; у нас имеется отдельный модуль matplotlib.docstring для этой магии со строками документации. Аргументы *args и **kwargs в документации используют специальные соглашения языка Python для указания всех аргументов и ключевых слов, относящихся к аргументам, предназначенных для передачи в качестве данных метода. Это позволяет нам перенаправить их соответствующему методу из состава API. Вызов ax = gca() позволяет использовать механизм изменения параметров состояния для получения "текущих экземпляров класса Axes" (каждый интерпретатор языка Python может иметь только один "текущий экземпляр класса Axes"), а также позволяет создать экземпляры классов Figure и Axes в случае необходимости. Вызов ret = ax.plot(*args, **kwargs) перенаправляет аргументы соответствующему методу экземпляра класса Axes и сохраняет возвращаемое значение для последующего возврата. Таким образом, интерфейс pyplot является достаточно тонкой оберткой над API основного класса Artist, при создании которой была предпринята попытка избежать копирования кода настолько, насколько это возможно путем раскрытия функций API, спецификаций вызова и строк документации в рамках интерфейса сценариев с минимальным количеством лишнего кода.


Далее: Рефакторинг системы вывода данных