6 заметок с тегом

СВГ

Добавляем рисунки к листьям дендрограммы. 1 часть: используем ggtree

Дендрограмма — это результат объединения данных при помощи иерархического кластерного анализа. Обычно на листьях дендрограммы написаны имена переменных — названия объектов. Рассказываю, как вместо названий показать изображения.

В конце заметки — финальный код на R


Эта красочная дендрограмма содержит 38 листьев и разделена на 19 классов. На листьях дендрограммы написаны названия видов плоских паразитических червей из класса моногеней. Собираюсь показать вместо них изображения прикрепительных органов этих паразитов


Для работы понадобятся

Знание языка Эр.

Установленный пакет ggtree.

Создаю изображения объектов

Каждому листу дендрограммы сопоставлю маленький рисунок объекта. Для этого буду использовать два набора изображений: один — для тестирования, другой — для финальной отрисовки.

Тестовые изображения
Тестовые изображения содержат номера, идущие по порядку. Они нужны для того, чтобы было понятно, куда именно разработанный код помещает картинки.

Вот код для генерации 99-ти пнг-файлов размером 20×20 пикселей. Имя файла с изображением совпадает с нарисованым номером.

imgs_path = "img_indexed/"

for (i in 1:99) {
  # Создаю новый пнг-файл img_{i}.png с нулевыми отступами
  png(file = paste0(imgs_path, "img_", i, ".png"), width = 20, height = 20)
  par(mar = c(0, 0, 0, 0))
  
  # Созданию рисунок числа
  plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1), xlab = "", ylab = "", axes = FALSE)
  text(0.5, 0.5, i, cex = 1)
  
  # Сохраняю пнг-файл
  dev.off()
}

Вот сами изображения.

Вод код для их загрузки.

imgs_indexed = c()
imgs_path = "imgs_indexed/"

for(i in 1:75) {
  imgs_indexed = c(imgs_indexed, paste0(imgs_path, "img_", i, ".png"))
}

Изображения объектов

Изображение объекта — это уменьшенный рисунок прикрепительного крючка паразитического червя (моногенеи). Вот они все:

Общая картинка создана с помощью Имедж-меджик: montage -tile 20x2 -geometry +0+0 *.png all-anchors.png

Название файла с крючком совпадает с наванием вида. Эти названия храню списком в текстовом файле labels.txt. Для загрузки изображений объектов использую следующий код.

imgs_path = "img_objects/"
# Загружаем имена меток
img_objects <- readLines("labels.txt")
# и добавляем расширение '.png'
img_objects <- paste0(imgs_path, img_objects, ".png")

Изображения готовы, перейдем к дереву.

Строю дерево с картинками, используя ggtree

В книге «Data integration, manipulation and visualization of phylogenetic trees» описано, как прикрутить картинки к дендрограмме с помощью ggtree.

К сожалению, приведнный в книге код у меня не заработал. Он подразумевает, что мы загружаем филодерево из файла. А я строю дерево при помощи иерархической кластеризации и преобразую ее в объект дендрограммы.

data — это матрица данных: в ее строках — объекты, в столбцах — признаки.

# Выполняю иерархический кластерный анализ
dist <- dist(data)
hc   <- hclust(dist, method = "complete") 

library(dendextend)

# Преобразую результат в дендрограмму
dend <- as.dendrogram(hc)

Из дендрограммы делаю филодерево.

library(ggtree)

phylo <- as.phylo(dend)

Рисую дерево.

ggtree(phylo)

И получаю пустой каркас.

R: ggtree(∙)

Строю дендрограмму с текстовыми листьями

Добавлю к листьям каркаса текстовые подписи при помощи функции geom_tiplab(∙). Чтобы подписи поместились на канве, оставлю пустое место справа при помощи xlim(∙).

ggtree(phylo) + xlim(0, 0.7) + geom_tiplab(geom="label", size=2.2)
R: ggtree(∙) + xlim(∙) + geom_tiplab(label)

Получилось так себе: текст мелкий и бесит рамка вокруг него, но как от нее избавиться — не понял.

Метки мелкие и иногда перекрывают друг на друга. От перекрытия меток избавит пакет ggrepel. Но в результате получается каша.

Рисую картинки на дендрограмме

У функции geom_tiplab(∙) есть параметр geom, задающий тип метки:

one of ’text’, ’label’, ’shadowtext’, ’image’ and ’phylopic’.

При помощи опции ’image’, можно на месте листьев нарисовать картинки.

На нашей дендрограмме 38 листьев. Значит передадим ей массив из 38 тестовых изображений: imgs_indexed[1:38].

Параметр size управляет размером картинок. Пришлось с ним повозиться, пока не подобрал подходящее значение.

ggtree(phylo) + geom_tiplab(geom="image", aes(image=imgs_indexed[1:38]), size=.03)

В результате выполнения кода получил ошибку:

Error in `label_geom()`:
! Problem while computing aesthetics.
i Error occurred in the 3rd layer.
Caused by error in `check_aesthetics()`:
! Aesthetics must be either length 1 or the same as the data (75)
x Fix the following mappings: `image`

После длительного анализа определил, что массив изображений должен состоять из 75 элементов, что написано в сообщении об ошибке. (75 = 38 листьев * 2 − 1.) Но код все равно использует только 38 первых изображений. (Это хорошо.)

ggtree(phylo) + geom_tiplab(geom="image", aes(image=imgs_indexed[1:75]), size=.03)

Вуаля.

R: ggtree(∙) + xlim(∙) + geom_tiplab(image)

Подставлю сюда изображения объектов.

ggtree(phylo) + geom_tiplab(geom="image", aes(image=img_objects[1:75]), size=.03)

Цель достигнута.

Финальный код

library(ggplot2)
library(ggtree)

# Формируем массив с названиями файлов с рисунками объектов.
imgs_path = "img_objects/"
img_objects <- readLines("labels.txt")
img_objects <- paste0(imgs_path, img_objects, ".png")

# Выполняю иерархический кластерный анализ
dist <- dist(data)
hc   <- hclust(dist, method = "complete") 

# Преобразую результат в филодерево
phylo <- as.phylo(dend)

# Рисую дерево с картинками
ggtree(phylo) + geom_tiplab(geom="image", aes(image=img_objects[1:75]), size=.03)

Итог

Результат не нравится.

Дендрограмма вверху заметки — красивая, а эта — средненькая. Поэтому в следующий раз покажу, как сделать красивую дендрограмму с картинками при помощи пакета dendextend.



Если после прочтения этой прекрасной заметки вам вдруг непреодолимо захотелось меня поблагодарить, переведите мне 200 руб. на круасан с чаем. А я пока напишу что-то новенькое.

Двойная дуга в СВГ пути

Это короткая техническая заметка о декодировании СВГ тега path.

Я — ретроград и у меня есть Дельфи-библиотека для загрузки СВГ графики. Она хорошо работала до тех пор, пока в теге path не встретился такой набор команд:

... a25.35,25.35,0,0,0-.32,8.58,32.42,32.42,0,0,0,1.53,6.3 ...

По спецификации a — рисует дугу и содержит семь параметров (семь чисел). А здесь их четырнадцать.

Немного подумав, понял, что вторая семерка чисел определяет следующую дугу. То есть без сокращений будет так:

... a25.35,25.35,0,0,0-.32,8.58 a32.42,32.42,0,0,0,1.53,6.3 ...

Учту это правило в библиотеке.

Храним данные в СВГ

В СВГ можно хранить свои данные. Это могут быть дополнительные количественные или качественные характеристики векторной модели, которые не поддерживает формат СВГ.

Для хранения данных придумываем новый тег newtag.

СВГ основан на XML. Согласно спецификации надо описать каждый новый тег в ДТД файле, выложить его на сервер и сослаться в заголовке.

<svg ... xmlns:newtag="http://antonlyakh.ru/new-svg-tags.dtd">

По факту работает, если указать пространство имен xhtml.

<svg ... xmlns:newtag="http://www.w3.org/1999/xhtml">

И работает даже если не упоминать новый тег в заголовке.

<svg ...>

Вставляем тег в текст.

<svg ...>
  <newtag>My own data.</newtag>
</svg>

Прописываем атрибуты тега.

<newtag scale="1" scaleunit="mkm" scalepx="254" />

Комбинируем атрибуты и содержимое.

<newtag scale="1" scaleunit="mkm" scalepx="254">Very usefull data.</newtag>

Текст на русском кодируем в УТФ-8.

<newtag lang="ru">╨Я╨╛╨╝╨╜╨╕, ╤З╤В╨╛ ╤А╤Г╤Б╤Б╨║╨╕╨╣ тАФ ╨╜╨╡ ╤П╨╖╤Л╨║ ╨╝╨╡╨╢╨┤╤Г╨╜╨░╤А╨╛╨┤╨╜╨╛╨│╨╛ ╨╛╨▒╤Й╨╡╨╜╨╕╤П.</newtag>

Загружаем файл в браузер — браузер правильно рисует вектор и не ругается на теги.

Открываем файл в Иллюстраторе или Инкскейпе. Правим. Сохраняем. Смотрим исходник — теги с данными сохранились. Иллюстратор и Инскейп теги не портят.

Мало того, можно окружить новым тегом команду рисования.

<newtag scale="1" scaleunit="mkm"><line ... /></newtag>

Это скроет векторный объект, и он не будет виден ни в браузере, ни в векторных редакторах, но будет доступен парсеру.

 Нет комментариев    33   2018   СВГ

Вырезаем растровую графику из СВГ

Столкнулся с задачей.

Есть фотография большого размера (более 4 тыс. пикселей по ширине), на которой запечатлены части живых организмов.

Большая фотография с прикрепительными структурами плоского паразитического червя

Организмы — это паразиты. Мизерные примитивные червячки из класса Моногеней, живущие на жабрах рыб. К жабрам они крепятся при помощи крючков.

Рисунок головы кефали из работы Световидова (1964); рисунок жабр и сидящего на них паразита из работы Лопеса-Беленгуэра с сотоварищами (2015)

Разные виды червячков внешне очень похожи. Они отличаются только формой прикрепительных крючков. У каждого таксономического рода уникальная форма крючков, но среди видов одного рода различия форм не столь очевидны. Чтобы их обнаружить, надо использовать методы морфометрического анализа.

Метод главных компонент описывает разнообразие форм прикрепительных крючков разных родов моногеней. График из статьи Вигнона (2011)

Для выполнения морфометрического анализа мы оцифровываем форму крючков. Загружаем фотографию в Инскейп, обводим кривыми и сохраняем результат в СВГ.

Проблема в том, что готовый файл слишком много «весит». Исходный джипег размером 800 Кб, после обводки и сохранения в СВГ, толстеет до 12 Мб. В результате мне присылают мегатонные свгешки с внедренной графикой, которые надо разместить на сервере, а он не резиновый.

Я не могу выбросить фото и загрузить на сервер только векторные кривые. Потому что контуры обрисовывают несколько человек, у каждого свои особенности рисования, они ошибаются, и результаты надо перепроверять. А фотография помогает проверить правильность построения контуров.

Я несколько часов пытался уменьшить размер свг-файла с графикой без потери качества. Попробовал уменьшить в Инскейпе, не получилось — размеры фотографии и свг-файла остались огромными. Потом разобрался с форматом и нашел решение.

Формат СВГ поддерживает два способа хранения изображений.

Первый, когда графический файл внедрен в СВГ. Это не оптимальный способ, потому что файл не сжат. Изображение состоит из последовательности символов в позиционной системе счисления с основанием 64 (base64). Отсюда 800 Кб превращаются в 12 Мб.

Фотография, внедренная внутрь свг-файла, занимает очень много места

Второй, когда СВГ хранит ссылку на внешний графический файл. Тогда СВГ содержит только описание кривых, а фотография хранится отдельно. Размер свг-файла становится маленьким.

Отделил «мух» от «котлет»

Второй вариант подошел.

Если бы файлов было мало, я бы все переконвертировал вручную. Но файлов много — несколько сотен.

В результате написал скрипт на ПХП. Он извлекает из СВГ фотографию, сохраняет ее в виде отдельного джипега и вставляет в СВГ ссылку на этот джипег. Джипег хранится локально и называется так же, как и свг-файл.

Скрипт умный. Если в разных файлах используется одна и та же фотография, скрипт сохранит ее всего один раз и во всех файлах будет правильно на нее ссылаться. Это экономит место.

Скрипт для всех.

Эта заметка описывает этап создания виртуальной коллекции паразитов рыб Мирового океана

СВГ Клинер — утилита, которая очищает СВГ от мусора

СВГ Клинер помогает очистить СВГ-файл от мусора и без потерь уменьшить его размер.

Без потерь значит, что утилита не разрушает СВГ. Она сохраняет векторные объекты и:

  • удаляет данные, которые внедряют в файл векторные редакторы, не нужные для отображения свг-файла;
  • оптимизирует структуру СВГ;
  • удаляет невидимые или неиспользуемые элементы.

Проект на Гитхабе. Есть версия с графическим интерфейсом.

 62   2017   полезности   СВГ

Координаты кривых из СВГ файла

Скачать конвертер

Для работы мне необходимы контуры микроорганизмов, которые я использую для сравнения форм. Я получаю контуры из фотографий, очерчивая объект в векторном редакторе. Программа сохраняет результат в СВГ файле, а мне нужны живые координаты точек. Чтобы получить координаты, я анализирую СВГ файл.

СВГ файл состоит из тегов. Я разберу только один — path, — остальные мне не нужны. Он хранит описание криволинейных и замкнутых траекторий. Траектория — это и есть тот самый контур микроорганизма.

Учим синтаксис тега path

Описание траектории вложено в тег svg. Примерно так:

Заголовок подтверждающий, что это СВГ
<svg>
  <path ... />
</svg>

Тег path содержит один атрибут d с алгоритмом рисования траектории.

<path d="алгоритм рисования" />

Алгоритм состоит из букв-команд и цифр-координат, которые разделенны пробелом, запятой или знаком минус. Траекторию рисует перо, буквы-команды управляют, цифры-координаты показывают куда или как сместить перо, а разделители отделяют одно от другого.

Разделители разделяют

Пробел — это стандартный разделитель. Его можно ставить везде между командами и координатами. Спецификация СВГ разрешает не писать пробел, когда из контекста понятно, что здесь две разные сущности. Это сокращает объем СВГ-файла.

Запомним:

  • пробел не нужен между командой и числом;
  • пробел не нужен после запятой;
  • пробел не нужен перед знаком минус.

Запятая тоже разделяет два числа, но вместо нее можно написать пробел. Что именно написать — дело вкуса.

Следующие три записи эквивалентны, но последняя компактнее.

<path d="M 15 17 L 18 -23 Z" />
<path d="M 15, 17 L 18, -23 Z" />
<path d="M15,17L18-23Z" />

Теперь о цифрах.

Цифры хранят координаты

СВГ предназначен для хранения двумерной графики. Поэтому цифры задают координаты точек на плоскости. Для каждой точки используется два числа — первое для x, второе для y.

Координаты бывают абсолютными и относительными. Абсолютные координаты отсчитываются от нуля, относительные — от конечной точки предыдущей команды. Относительные — это шаг, на который надо сдвинуть перо относительно последней позиции.

Тип координат определяет регистр буквы-команды: большая буква обозначает абсолютную позицию, маленькая — относительную.

Заглавная буква-команда — для абсолютных координат, прописная — для относительных

Точка является десятичным разделителем вещественных чисел. Запись 12,75 — это два числа: 12 и 75, тогда как 12.75 — это одно вещественное число.

Точка — десятичный разделитель, запятая отделяет два числа.

Цифры еще описывают дополнительные параметры команды A, которая рисует дуги.

Буквы управляют пером

Буквы — это команды, которые управляют пером. За каждой командой следуют пары координат, которые показывают, куда сместить перо или как закруглить линию. Тег path использует следующие команды:

  • M — начать новую траекторию: переместить перо в заданную точку без рисования.
  • Z — замкнуть траекторию: переместить перо в начальную точку и нарисовать линию.
  • С, S — нарисовать кубическую кривую Безье.
  • Q, T — нарисовать квадратную кривую Безье.
  • L, Н и V — нарисовать произвольную, вертикальную или горизонтальную прямую.
  • A — нарисовать дугу окружности или эллипса.

Команду A я пропущу, но рассмотрю остальные.

M x y

М или m начинает новую траекторию. Команда перемещает перо в заданную точку и ничего не рисует. Большое М — первая обязательная команда тега path.

<path d="M20 50..." />
Z

Z или z замыкает траекторию — проводит прямую линию от последней позиции пера к первой точке траектории. Регистр буквы не важен. Обычно это последняя команда тега. Если нет, тогда после z опять стоит большое M, которое начинает новую траекторию. Если z не написано, траектория незамкнута.

<path d="M20 50 L 10 10, 140 10, 110 50z" />
<path d="M20 50 L 10 10, 140 10, 110 50z" />
<path d="M20 50 L 10 10, 140 10z M110 50, 80 60, 95 90z" />
L x y

L или l рисует прямую линию от последней позиции пера до заданной точки. Несколько следующих друг за другом команд описывают ломаную. В такой записи, для краткости, все L, кроме первой, не пишут. Если L стоит после M, ее тоже не пишут. Вот так:

<path d="M 70,12 L 24,30 L 60,70 L 120,50" />
<path d="M 70,12 L 24,30 60,70 120,50" />
<path d="M 70,12 24,30 60,70 120,50" />
H x

H или h рисует горизонтальную линию от последней позиции пера до указанной точки. Координата у не меняется.

V x

V или v рисует вертикальную линию от последней позиции пера до указанной точки. Координата x не меняется.

C cx1 cy1 cx2 cy2 x2 y2

С или с строит кубическую кривую Безье. Она состоит из четырех точек: первая и последняя задают начало и конец кривой, две промежуточные управляют формой. Так как первая точка совпадает с текущим положением пера, то после команды C указывают только три точки: две управляющие вершины (cx1 cy1), (cx2 cy2) и последнюю точку кривой (x2 y2).

<path d="M40 50 C 60 10, 90 90, 110 50" />

Поликривая Безье — это несколько соприкасающихся кривых Безье, где конец одной кривой становится началом следующей. Последовательные команды C описывают поликривую. Для экономии байтов все команды, кроме первой C, не пишут.

<path d="M30 100C50 50,70 20,100 100,110 130,45 150,65 100" />

Обычно в месте соприкосновения кривых Безье гладкость поликривой нарушается. Чтобы поликривая оставалась гладкой, используют команду S.

S cx2 cy2 x2 y2

S или s строит гладкую кубическую кривую Безье так, что ее первая управляющая вершина является зеркальным отражением второй управляющей вершины предыдущей кривой Безье. Отражение относительно начальной точки данной кривой.

Q cx1 cy1 x2 y2

Q или q строит квадратную кривую Безье. Синтаксис команды аналогичен C, только здесь используется одна управляющая вершина.

Несколько команд Q описывают квадратную поликривую Безье. Для краткости все команды, кроме первой Q, не пишут. Чтобы квадратная поликривая получилась гладкой, используют команду T.

T x2 y2

T или t строит гладкую квадратную кривую Безье так, что ее единственная управляющая вершина является зеркальным отражением управляющей вершины предыдущей кривой Безье.

Со структурой файла разобрались.

Пишем конвертер СВГ в текст

Конвертер готов. Это экзешник. Инсталировать не нужно. Сохраните на диск и запустите.

Скачать конвертер

Для описания любого контура достаточно трех команд: M, C и Z. Конвертер понимает еще и четвертую: L, остальные — нет.

Конвертер выбирает из СВГ-файла теги path, вычисляет координаты точек траектории и сохраняет их в текстовом файле.

Конвертер не проверяет валидность СВГ-файла. Ему все равно, какая информация находится вокруг тега path. На вход конвертера можно подать любой мусор с корректными данными только в теге path.

Конвертер поддерживает пропорциональное и произвольное масштабирование координат: перед сохранением он спрашивает размер области, в которую надо вписать траекторию.

Конвертер позволяет задать позицию начала координат: в одном из углов, на середине стороны или в центре прямоугольника, ограничивающего контур.

Конвертер сохраняет в текстовом файле пары чисел — двумерные координаты точек контура. Десятичный разделитель — точка. Числа разделены пробелом.

12.3 15.6 78.4 24.1 15 123

Детали

Конвертер написан на Дельфи 7.

Пары чисел вырезаю из СВГ-файла с помощью регулярного выражения. Понимает числа разделенные пробелом, запятой или минусом.

/(-?\d+(\.\d+)?)[,|\s]?(-?\d+(\.\d+)?)\s?/

Для построения кубической кривой Безье применяю адаптивное разбиение.

Полученные данные анализирую методами геометрической морфометрии. Но это уже другая история.

С Новым годом!