среда, 29 октября 2014 г.

Разбор статей Wikipedia с помощью парсера Sweble.org. Как распарсить википедию.

Извлечение информации из вики-страниц с учётом их структуры представляется непростой задачей. Зачастую язык вики-разметки (WML) не имеет формального описания. Часто WML развивается путем добавления новых фич без стадии перепроектирования с большим грузом обратной совместимости. В итоге обычно описание WML сводится к набору примеров из документации и в "окончательном виде" язык определен в коде парсера вики-движка. При этом внутри вики движка разбор проводится не на основе грамматических правил, как это делается,  для большинства языков программирования и языков разметки, таких, как XML, а на основе поиска по шаблону. Найденные шаблоны превращаются в кусочки HTML-разметки, которые в итоге формируют вики-страницу в браузере пользователя. Это делает структурированный разбор вики-страниц трудной задачей. Тем не менее для разбора вики-страниц существуют готовые решения, и к счастью обычно не требуется прибегать к велосипедостроению. Об эдном из решений - парсере Sweble.org я и хочу рассказать в этом посте.

Некоторое время назад я делал "химичесикй поиск" по википедии, сервис, который позволяет искать статьи о химических веществах как по тексту, так и по структурной формуле. Для этого я скачивал дамп википедии, извлекал статьи про химические вещества и индексировал их с помощью поискового движка Lucene. Первая (и на данный момент единственная) версия поиска не делала какой-то особой обработки страниц, требовалось просто извлечь текст самой статьи, отделив его от разметки. Перед этим название статьи и структурная формула вещества извлекалась с помощью регулярного выражения perl-скриптом. Немного поискав в инетрнете, я нашёл несколько парсеров вики-разметки, при этом как-то случайно само собой получилось, что быстрее всего пошли дела с парсером Sweble.org. Теоретическую вводную в работу парсера Sweble.org можно получить в статье авторов (pdf).

Как и многие другие парсеры Sweble.org строит дерево разбора страницы. Это дерево в терминах Sweble.org называется WOM, или "Wiki Object Model". Парсер написан на Java и работать с ним проще всего из Java. Документация почти отсутствует, но имеются примеры, которые сильно помогают.

Итак, как же работать с разобранным деревом? Разобранная вики-статья представляется в виде дерева, узлы которого -- объекты реализующие интерфейс WtNode. Эти объекты принадлежат унаследованным интерфейсам и классам WtNodeList (интерфейс), WtUnorderedList, WtOrderedList, WtListItem, EngPage, EngProcessedPage, WtText, WtWhitespace, WtBold, WtXmlCharRef, WtXmlEntityRef, WtUrl, WtExternalLink, WtInternalLink, WtSection, WtParagraph, WtHorizontalRule, WtXmlElement и другим.

Обход дерева реализуется в соответствии с шаблоном проектирования посетитель (Visitor).

Для анлиза дерева разбора я написал небольшую утилитку, которая разбирает вики-статью с помощью Sweble.org и выводит дерево в текстовом формате. Например, для статьи Manzanate


{{Orphan|date=April 2011}}

{{chembox
| verifiedrevid = 425210668
|   Name =Ethyl 2-methylpentanoate
|   Reference =
|   ImageFile = Manzanate.png
|   ImageSize = 200px
|   ImageName =
|   IUPACName = Ethyl 2-methylpentanoate
|   OtherNames = Ethyl α-methylvalerate; Melon valerate
| Section1 = {{Chembox Identifiers
|   CASNo_Ref = {{cascite|correct|??}}
| CASNo = 39255-32-8
|   SMILES = CCCC(C)C(=O)OCC
  }}
| Section2 = {{Chembox Properties
|   Formula = C8H16O2
|   MolarMass = 144.21 g/mol
|   Density = 
|   MeltingPt =
|   BoilingPt =
  }}
}}

'''Manzanate''' is a [[flavor]] ingredient which has a fruity apple smell and with aspects of cider and sweet pineapple.[http://www.thegoodscentscompany.com/data/rw1006801.html Melon valerate]

==References==
{{reflist}}

[[Category:Carboxylate esters]]


{{ester-stub}}


будет сгенерирован примерно такой вывод

[WtListItem]

    [WtParagraph]
        [WtTemplate]
            [WtNodeList]
                [WtText] Orphan
            [WtNodeList]
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText] date
                    [WtNodeList]
                        [WtText] April 2011
    [WtText]
    [WtParagraph]
        [WtTemplate]
            [WtNodeList]
                [WtText] chembox
            [WtNodeList]
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText]  verifiedrevid
                    [WtNodeList]
                        [WtText]  425210668
                [WtTemplateArgument]
                    [WtNodeList]
                        [WtText]    Name
                    [WtNodeList]
                        [WtText] Ethyl 2-methylpentanoate
...поскипано
    [WtParagraph]
        [WtBold]
            [WtText] Manzanate
        [WtText]  is a
        [WtInternalLink]
            [WtText] flavor
        [WtText]  ingredient which has a fruity apple smell and with aspects of cider and sweet pineapple.
        [WtTagExtension]
    [WtText]
    [WtSection]
        --heading--
            [WtText] References
        --body--
            [WtText]
            [WtParagraph]
                [WtTemplate]
                    [WtNodeList]
                        [WtText] reflist
                    [WtNodeList]
            [WtText]
            [WtParagraph]
                [WtInternalLink]
            [WtText]
            [WtParagraph]
                [WtText]
                [WtTemplate]
                    [WtNodeList]
                        [WtText] ester-stub
                    [WtNodeList]
            [WtText] 


На первом этапе мне нужно было просто очистить статью от вики-разметки, оставивь только текст. Эта задача решена в примерах, поставляемых вместе с парсером Sweble.

Общий подход таков: сначала строим WOM-дерево как-то так:

import org.sweble.wikitext.engine.EngineException;
import org.sweble.wikitext.engine.PageId;
import org.sweble.wikitext.engine.PageTitle;
import org.sweble.wikitext.engine.WtEngineImpl;
import org.sweble.wikitext.engine.config.WikiConfig;
import org.sweble.wikitext.engine.nodes.EngProcessedPage;

...

//Установим простую конфигурацию по-умолчанию
WikiConfig config = DefaultConfigEnWp.generate();

//Тайтл меня особо не волнует. Сгенерируем что-нибудь на основе пути к данным в файловой системе
PageTitle pageTitle = PageTitle.make(config, pathStr);

PageId pageId = new PageId(pageTitle, -1);

//Скомипилируем (читай, распарсим) страницу.
EngProcessedPage cp = engine.postprocess(pageId, wikitext, null);


Напомню, что класс EngProcessedPage реализует интерфейс WtNode и, как видно из примера, представлят собой корневой узел WOM-дерева. Для того, чтобы обойти дерево произведя какие-либо полезные действия нужно создать класс, унаследованный от de.fau.cs.osr.ptk.common.AstVisitor и переопределить методы before, after и семейство методов visit В случае очистки от разметки можно взять из примеров проекта уже готовый класс org.sweble.wikitext.example.TextConverter.

Эту магию я планирую применить для извлечения и индексирования различных свойств химических веществ. Статьи, которые индексирует мой "химический поисковик" содержат специальный infobox (chembox, drugbox или ionbox, см. пример выше). В настоящий момент из этого инфобокса с помощью регулярного выражение извлекается только лишь структурная формула в формате SMILES. Использование парсера Sweble.org облегчит эту задачу и позволит единообразно извлечь множество других свойств вещества.

воскресенье, 5 октября 2014 г.

Простой термометр на Arduino UNO и температурном датчике DS18B20

Температурные датчики DS18B20 очень популярны. Они стоят очень дёшево, выдают температуру в цифровом виде, их достаточно легко подключить к микроконтроллерам по однопроводной шине 1-Wire таким образом, что одной ножки микроконтроллера достаточно чтобы адресовать несколько датчиков и считывать с них показания.

Я заказал несколько таких датчиков на eBay. Кроме того, у меня в распоряжении имелся LCD-индикатор размером 2 строки по 16 символов, оказавшийся, впрочем, слегка бракованным (некорректно отображаются 12 и 13-й символы в каждой строке), и мне уже давно хотелось попробовать его в действии. К моему удобству к этому ЖК-модулю был уже припаян I2C-адаптер на чипе PCF8574, точь в точь такой, как описан в этой статье. Из этой же статьи я почерпнул ряд полезных ссылок, в частности на библиотеку для работы LCD-экраном через этот I2C-адаптер.

Подружить Arduino UNO с температурным датчиком DS18B20 оказалось очень нетрудно. Датчик подключается через шину 1-Wire, пртокол которой можно программно реализовать в Arduino. Само собой, для этого имеется готовая библиотека. Кроме этого я использовал ещё одну внешнюю библиотеку для самого датчика DS18B20. В итоге я собрал вот такую схему:


... и написал такой нехитрый скетч:


Собранный термометр выглядит так:


На фотографии один датчик лежит просто так и показывает температуру 27.44 С, другой прикреплен кусочком изоленты к кружке с горячим чаем и показывает 55.00 С.

среда, 30 июля 2014 г.

Как я заводил Raspberry Pi

Помимо Arduino у меня уже некоторое время валяется на полке плата Raspberry Pi. Я давно уже хочу с ней поиграться, но всё как-то не доходили руки. Вот, наконец решил попробовать запустить "PC размером с банковскую карту". Собственно "запуск" заключается в заливке на карту памяти SD образа операционной системы (в основном одного из адаптированных дистрибутивов Linux). Некоторое неудобство доставило отсутствие отдельного монитора с HDMI выходом, но и в этом случае процедура установки системы для Raspberry очень стандартная.

Итак, имеем флеш-карту формата SD на 4Гб (согласно документации это минимально возможный объём). Есть два варианта установки системы в Raspberry Pi. Первый способ - залить на флешку NOOBS (New Out Of the Box Software). Это по сути "менеджер установки системы", который позволит установить систему из одного из популярных дистрибутивов по выбору. Второй способ - залить на флешку уже непосредственно образ готовой системы. Второй способ рекомендуется для "опытных пользователей", первый, как утверждают, годится для новичков. Что-ж, пойдём первым путём, надеясь, что он более простой.

Образ NOOBS можно скачать здесь. У меня на ноутбуке установлен Windows, так что инструкции черпаем в соответствующем разделе сайта Rapberry Pi. Так же в соответствии с инструкцией нам понадобится утилита SD formatter. С помощью этой утилиты сначала форматируем карту, куда будет установлена система. Выбираем при форматировании тип файловой системы  Fat32, а так же ставим настройку "FORMAT SIZE ADJUSTMENT" равной "ON". Распаковываем скачанный архив NOOBS. Внутри архива можно обнаружить директорию ./os . В этой директории необходимо стереть все поддиректории кроме Raspbian (там будут директории с именами Arch, Pidora, RISC_OS и.т.д. Все их надо удалить). Затем редактируем файл /os/Raspbian/flavours.json. Удаляем строки, отмеченные курсивом:

{
     "flavours": [
       {
         "name": "Raspbian - Boot to Scratch",
         "description": "A version of Raspbian that boots straight into Scratch"
       },

       {
         "name": "Raspbian",
         "description": "A Debian wheezy port, optimised for the Raspberry Pi"
       }
     ]
   }


Затем в корне распакованного архива стоит подредактировать файлик recovery.cmdline. Нужно добавить волшебное слово silentinstall в конец строки:
Было:

runinstaller quiet vt.cur_default=1 coherent_pool=6M elevator=deadline

Стало:

runinstaller quiet vt.cur_default=1 coherent_pool=6M elevator=deadline silentinstall

После этого копируем содержимое архива на флешку. При загрузке с получившейся карты Raspberry автоматически установит Raspbian. Установка может занять какое-то время и рекомендуется оставить устройство в покое на часик-другой, чтобы быть точно уверенным, что система полностью установлена. Я вставил флешку в Raspberry Pi и подключил её к роутеру Ethernet-кабелем. Включив питание и подождав какое-то время я стал пытаться получить доступ к установленной системе по SSH. Для этого сначала необходим определить IP-адрес Raspberry. Поискав в интернете я нашёл, что MAC-адрес Raspberry Pi должен начинаться с B8:27:EB. Где-то в глубинах веб-интерфейса своего домашнего роутера я нашел список MAC-адресов подключённых устройств вместе с соответствующими IP-адресами, что было весьма кстати, тем более что среди них действительно был MAC-адрес, начинающийся на B8:27:EB. IP-адрес Raspberry оказался равным 192.168.1.12. Теперь остаётся залогиниться туда по SSH, например, с помощью терминала PuTTY. Следует отметить, что мне встречаются сообщения, что SSH-сервер не включен по умолчанию в системе Raspbian установленной из NOOBS 1.3.7, и понадобятся дополнительные действия для того, чтобы получить доступ. Это противоречит моему опыту (правда с версией NOOBS 1.3.9), в моём случае SSH-сервер оказался включен в "свежей" системе. По умолчанию в системе создаётся пользователь pi с паролем raspberry:

Всё! После этого я увидел, что плата работает и у меня есть контроль над системой, установленной в ней через SSH. Остаётся придумать и сделать с ней что-нибудь полезное.

четверг, 24 июля 2014 г.

Как я подключал модуль Bluetooth к Ардуино

Хочу рассказать короткую историю о том, как я ходил по граблям, пытаясь подружить плату Arduino UNO и модуль Bluetooth HC 06.

Чтобы протестировать взаимодействие между PC/смартфоном и платой Arduino UNO я после недолгих поисков по интернету собрал вот такую нехитрую схему:

В общем-то всё просто: подаём напряжения +5В, +3.3В и "землю" (GND) с Ардуино на модуль Bluetooth, для передачи же данных модуль соединяется с аппаратным последовательным портом на Arduino. Выход (Tx) на Ардуино подключается к входу (Rx) модуля (по этой линии данные передаются из Ардуино в модуль Bluetooth) и наоборот, для передачи из модуля в Ардуино подключаем (Tx) на плате HC 06 к (Rx) Ардуино. Сигнал с Ардуино подаётся на модуль через делитель напряжения, так как Bluetooth-модуль ожидает уровень сигнала 3.3В, а Ардуино выдаёт 5В. R1 я взял равным примерно 5 кОм, R2, соответственно, 10 кОм (на самом деле 5.1 кОм и 11 кОм).

От прошивки в Ардуино в этом опыте требуется только лишь возможность проверить, что данные шлются в обе стороны. Я реализовал следующую логику: если Arduino получает через Bluetooth символ '1', то в ответ высылается строчка "Hello". Eсли Arduino получает через Bluetooth символ '2', то просто мигаем встроенным светодиодом, который подключен к пину 13 на плате Arduino UNO. В ответ на любой другой символ присылается знак вопроса.

Эта схема никак не хотела работать! Было похоже, что данные не пересылаются в Ардуино. Решил попробовать, шлются ли данные в обратном направлении. Переписал скетч таким образом, чтобы Ардуино просто постоянно слал тестовую строчку с данными в модуль, и смог без проблем прочитать их на своём ноутбуке (о том, как подключаться с PC под управлением Windows пойдёт речь ниже).

Потратив 2+ часа на проверки и поиски в интернете, я нашёл, что нельзя пользоваться Rx и Tx пинами на Ардуино когда он подключен к USB-порту компьютера (я использую USB кабель для питания всей схемы). Дело в том, что при подключении USB-кабеля к ардуино, выходы контроллера Rx и Tx используются для связи с компьютером через USB (правда, сигнал Tx продолжил работать и в этом случае, так как послать данные из Ардуино в PC всё-таки получилось). При связи с другими устройствами через аппаратный последовательный порт необходимо подавать питание через разъём внешнего питания (7-12 Вольт). Я полагаю, все это очевидные вещи для мало-мальски опытного пользователя Arduino (коим я пока не являюсь). Надеюсь этот пост поможет кому-то из начинающих сэкономить время и избежать похожих проблем в будущем.

С программной реализацией последовательного обмена разработчик волен выбирать любой пин цифрового ввода/вывода платы Ардуино. Я выбрал десятый пин для приёма информации через Bluetooth, и 11-й, соответственно, для отправки данных. Схема теперь выглядит так (на схеме ошибка, 10-й и 11-й выводы платы поменяны местами):


В реальном мире это выглядит так:



Код скетча почти не изменился, так как программная реализация последовательного обмена имеет очень похожее API:

Не буду останавливаться слишком подробно на на процедуре сопряжения PC с нашим новым "гаджетом". В случае Windows всё как обычно: подключается USB-донгл, осуществляется поиск Bluetooth устройств. В моём случае устройство называлось HC-06, но имя можно поменять с помощью соответствующей AT-команды. С точки зрения программ наше устройство видится как ещё один COM-порт в системе. В свойствах устройства можно увидеть, какой номер присвоен "виртуальному COM-порту" сопряженного устройства. В моём случае получился порт COM-28. Для того чтобы слать и принимать данные в COM-порт для Windows существует множество программ. Я использовал привычный мне терминал PuTTY. Последовательность действий такова: запускаем PuTTY, выбираем Connection type: Serial и идем в категорию "Serial"

'
Здесь выставляем вот такие параметры:

Номер COM-порта может отличаться, его необходимо смотреть в свойствах устройства. Нажимаем кнопку "Open" чтобы сразу соедениться с устройством, либо идём в категорию Session, там придумываем название сессии (у меня, например, ardu-bt) и печатаем его в поле Saved sessions, затем нажимаем кнопку "Save". Это сохранит настройки сессии и позволит не конфигурировать ком-порт при каждой последующей попытке приконнектится к устройству. Достаточно будет просто выбрать сохранённую сессию в списке. После того, как программа-терминал соеденится с устройством, светодиод на модуле Bluetooth должен перестать мигать и начать светить непрерывно. В окне с сессией можно слать команды устройству, нажимая на клавиши. При нажатии на клавишу 1 устройство должно вернуть строку "Hello", при нажатии ина колавишу 2 -- пять раз помигать светодиодом на Arduino. При нажатии на другие клавиши устройство отвечает знаком вопроса. Типичный пример сессии:


Итак, удалось научится сопрягать Ардуино с ПК через Bluetooth! Теперь остаётся придумать какой-нибудь более интересный проект.


вторник, 28 января 2014 г.

ChWiSe.Net: Проект химического поиска по Википедии

О чём проект

Хочу рассказать о chwise.net - созданном мной недавно вебсайте, предназначенном для поиска статей о химических веществах в википедии. Особенностью данного сервиса является то, что поддерживается как полнотекстовый поиск по ключевым словам, так и поиск по структурной похожести молекул. В настоящее время проиндексированы только лишь статьи на английском языке. Если проект будет пользоваться интересом, то он получит дальнейшее развитие в сторону поиска статей и на других языках.

Итак, что представляет собой описываемый сервис? На главной и единственной странице можно увидеть два поля ввода: одно отвечает за текстовую часть запроса,
другое - за химическую структуру-образец, или молекулярная часть запроса. Можно ввести структуру-образец прямо с клавиатуры в формате SMILES или создать её в редакторе химических формул (кнопка "Start structure editor").




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

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

У проекта есть твиттер, так что если интересно следить за новшествами - можно подписываться.

Что "под капотом"

ChWiSe.Net - opensource проект, опубликованный под лицензией GPL. Он базируется на моём плагине для Lucene (MolecularLucene), который позволяет искать похожие химические структуры. Исходный код доступен на GitHub под гораздо более свободной лицензией Apache. При разработке ChWiSe.Net я активно использовал opensource наработки "от третьих лиц": помимо упомянутого движка Lucene жизненно важные компоненты ChWiSe.Net - редактор химических формул из ChemDoodle Web Components (двойная лицензия: GPL/коммерческая) и Chemistry Development Kit (CDK). Ещё стоит упомянуть Twitter Bootstrap, jQuery, клиентский шаблонный движок EJS, Bootsrap Paginator и парсер вики-разметки Sweble.

В настоящее время ChWiSe.Net хостится в Google Appengine (GAE). Поисковый индекс в GAE хранится в статических данных веб-приложения, что является наиболее простым решением если обращение к индексу происходит только для чтения. Для того, чтобы библиотека Lucene версии 4.3.1 могла работать со статическим индексом в среде GAE, тербуется слегка пропатчить исходный файл RamUsageEstimator.java в исходниках Lucene и пересобрать Lucene.

Ссылки

http://www.youtube.com/watch?v=V0ddipGhPc0 - Короткая видоперзентация на английском