Python3: feedparser unicode error

Есть расширение universal feed parser и есть у него одна очень неприятная бага: если установлено расширение chardet, то парсинг лент в юникоде ломается.

$ ./rss.py                                                                                                                                                                     Traceback (most recent call last):
File "./rss.py", line 11, in <module>
feed = feedparser.parse(rss_response.text)
File "/usr/lib/python3.3/site-packages/feedparser.py", line 3966, in parse
data, result['encoding'], error = convert_to_utf8(http_headers, data)
File "/usr/lib/python3.3/site-packages/feedparser.py", line 3768, in convert_to_utf8
chardet_encoding = str(chardet.detect(data)['encoding'] or '', 'ascii', 'ignore')
TypeError: decoding str is not supported

Неприятно. Можно удалить chardet, но тогда другие расширения, которые от него зависят будут удалены. В том числе и requests.

Значит надо чинить. Но поскольку баг пофикшен только в транке, а текущая стейбл 5.1.3, то надо обновляться из транка.

$ sudo python3-pip install git+https://code.google.com/p/feedparser/ --upgrade

Или накатить патч руками.

Cookies: с ними нужно быть внимательным

cookiesОднажды на проекте случлось странное с функционалом. Выясняя причину наткнулся на очень много кода, который работает с кукисами. Но вот только работает код неправильно.

Вообще чем кукиса характеризуется:

  • имя
  • значение
  • путь
  • время истечения
  • домен
  • секурность

Здесь и далее нас будет интересовать домен, поскольку именно он является причиной множества странный и часто трудноуловимых багов.

Возьмем пример https://github.com/RussianPenguin/blogSamples/blob/master/cookies.php и запустим его на локальном сервере пхп (примем за аксиому то, что у нас домен localhost.localdomain резолвится на локалхост).

Пример cookie.php на локальном сервереТеперь зайдем в браузер по нашему скрипту

Cookies.php в firebugВидно, что скрипт выставляет две кукисы для одного и того же домена.

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

Казалось бы, мы ставим кукисы на одном и том же домене. Кстати, вот код:

// Конфигурация (домен на котором запускаем)
$domain = 'localhost.localdomain';
$cookieName = 'test';

setcookie($cookieName, 'empty host', 3600+time(), '/');
setcookie($cookieName, 'host: ' . $domain, 3600+time(), '/', $domain);

тут я оставил конфигурацию домена руками так как запуск сервера идет на нестандартном порту.

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

Но нет. Если хидер установки кукиса содержит домен, то кукису будет присвоено имя домена с точкой в начале. А если имени домена не указывать, то кукис будет поставлен на текущий домен без точки в начале.

И когда мы попытаемся считать на стороне клиента (или сервера) наш поставленый кукис, то какой из них вернется — это уже немного рандом (хотя нет, все зависит от того, в какой последовательности печеньки ставились).

И то же самое происходит на клиенте: если мы при установки кукиса указываем домен, то браузер поставит кукис на домен с точкой в начале, а если не укажем, то без точки.

Попробуем прописать в тестовом скрипте какое-то значение для печеньки, но не указывать домен. А потом нажмем кнопку «поставить кукис».

cookies.php после setCookieЧто мы видим? А видим мы то, что браузер считал значение для текущего домена (первое) и перезаписал его новым. Которое мы выбрали. А второй кукис до перезагрузки остался нетронутым. Круто. А значит нажмем «прочитать печеньку» и увидим наше новое значение.

Однако если мы так думаем, то ошибаемся.

cookies.php getCookieДаже FireCookie что-то показывает — это не значит, что значение печенья поменялось.

Огнелись ничего не поставил.

Но как только мы домен укажем при установке

cookie.php - правильный результатТак сразу все работает как надо — устанавливается значение и при нажатии на кнопку «прочитать» мы его получаем.

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

Учтите, что если вы заходите повторить этот эксперимент, то на доменах верхнего уровня подобное не работает. Т.е. если мы будем использовать localhost, то подобное поведение не воспроизведется.

Intel Galileo IDE и Linux

Если мы запускаем первый раз IDE для Intel Galileo, то можно увидать очень интересную картину в консоли

$ arduino-1.5.3-Intel.1.0.4/arduino 
Board arduino:edison:izmir_ec doesn't define a 'build.board' preference. Auto-set to: EDISON_IZMIR_EC
Board arduino:x86:izmir_fd doesn't define a 'build.board' preference. Auto-set to: X86_IZMIR_FD
Board arduino:x86:izmir_fg doesn't define a 'build.board' preference. Auto-set to: X86_IZMIR_FG
Experimental:  JNI_OnLoad called.
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version   = RXTX-2.1-7
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL
check_group_uucp(): error testing lock file creation Error details:Отказано в доступеcheck_lock_status: No permission to create lock file.
please see: How can I use Lock Files with rxtx? in INSTALL

Ага. А решается все очень просто.

  1. Наш пользователь должен быть в группе lock
  2. Для папки /run/lock (/var/lock в некоторых дистрибах) должны стоять права root:lock (776)

Мониторинг обмена данными с serial-портом

Иногда надо сниффать serial-порт на предмет того, что туда пишется/читается софтиной.
Есть сниффер jpnevulator.

jpnevulator --timing-print --tty /dev/ttyACM0

Но он не совсем удобен. Так как он не сниффер на смом деле, а простой ридер-писатель для порта.
Поэтому если запустить его совместно с другой софтиной, то она данных на порту не увидит.

А нам нужен именно сниффер.

Можно создать виртуальный порт с помощью pyserial. И читать/писать данный и реального в виртуальный порт и наоборот (MITM), но стоит ли? :)

Воспользуемся strace!

$ strace -e trace=read,write python terminal.py
# тут я вырезал кусок, который относится к питону
write(3, "P", 1)                        = 1
write(3, "P", 1)                        = 1
read(3, "P", 1)                         = 1
read(3, ";\26\225\320\0k,\r\0", 9)      = 9
write(1, "3B 16 95 D0 0 6B 2C D 0\n", 243B 16 95 D0 0 6B 2C D 0
) = 24
write(3, "S\3\377\0\377", 5)            = 5
read(3, "\377\0\377", 3)                = 3
write(1, "FF 0 FF\n", 8FF 0 FF
)                = 8
write(3, "S\5\240\244\0\0\2", 7)        = 7
read(3, "\244", 1)                      = 1
write(3, "S\2?\0", 4)                   = 4
read(3, "\237#", 2)                     = 2
write(1, "9F 23\n", 69F 23
)                  = 6
write(3, "S\5\240\300\0\0#", 7)         = 7
read(3, "\300\0\0Z\0?\0\1\0\0\0\0\0\26\263\3\7\4\0\203\212\203\212\0\3\0\0Z\0\0\0Z"..., 38) = 38
write(1, "C0 0 0 5A 0 3F 0 1 0 0 0 0 0 16 "..., 89C0 0 0 5A 0 3F 0 1 0 0 0 0 0 16 B3 3 7 4 0 83 8A 83 8A 0 3 0 0 5A 0 0 0 5A 0 2F 6 2 90 0
) = 89
write(3, "H", 1)                        = 1
+++ exited with 0 +++

И вот уже вожделенный протокол обмена на экране. :)

Делаем подсветку синтаксиса для less

Подсветка синтаксиса в lessДефолтный вывод less  в консоли чрезвычайно скучный. И рассматривать километры одинаковых черно-белых листингов утомляет (или зеленых если у вас Ъ-хакерский терминал :)).

Так привнесем же цветность в наши черно-белые терминал.

 $ sudo yum install python-pygments

А затем прописываем в .bashrc

export LESS="-R"
export LESSOPEN="|pygmentize -g -O encoding=utf8 %s"

Кодировку ставим на выбор. Но если чаще всего работаем в юникоде, то и оставляем юникод.

При этом будет подсвечиваться только вывод less, который вызван в дефолтной форме

 $ less filename

Вывод же less, который работает с перенаправленным вводом подсвечиваться на будет. :)

Проект pygments.

Kinect: избавляемся от искажений при помощи потолка

Идеально плоский потолокДавеча я писал про то, как выглядят данные, которые приходят с сенсора. Такое поведение сенсора просто возмутительно и долго это продолжаться не может.

Ок. А что нам потребуется? Нам потребуется потолок. Я внимательно его осмотрел и убедился, что в реальности он плоский. А вот на восстановленом изображении — нифига.

Для начала нам нужно сохранить дамп глубин «идеального» потолка. Можно и стену если у вас есть достаточно плоской поверхности. :)

В файл будет сохранена двумерная матрица из чисел разделенных пробелами. Строки по строкам и т.д. Позже поймем зачем именно так.

Кусочек файла

888 889 890 890 890 890 890 891 890 891 891 892 892 892 892 892 892 892 892 892 891 893 892 892 892 892 892 ... 2047 2047 2047 2047 2047 2047
...

Теперь посмотрим на картинку этой самой плоскости. Для этого запускаем maxima.

(%i1) load(numericalio);
(%i2) m: read_matrix(&quot;&lt;путь к файлу с глубинами&gt;&quot;);
($i3) f(x, y) := float('m [round(x), round(y)]); 
-- тут мы зададим функцию извлечения нужного значения из матрицы. 
-- Так как матрица наши данные дискретны 
-- и существуют только в определенных точках
(%i4) plot3d (f(x, y), [x, 1, 640], [y, 1, 480]);

О! Круто. Мы что-то видим.

Потолок. Необработанная карта глубин

Что-то не то :)

Стоит заметить, что в режиме сырых данных FREENECT_DEPTH_11BIT самые правые 8 столбцов не используются и всегда возвращают значение FREENECT_DEPTH_RAW_MAX_VALUE. Об этом не стоит забывать при последующих действиях.

Поэтому нам нужно посмотреть плоскость без правых столбцов (8 штук).

(%i4) plot3d (f(x, y), [x, 1, 632], [y, 1, 480]);

И тут уже то, что нам надо.

Потолок: карта глубин после доработкиЭто плоскость. Но не очень плоская. И таки да. Надо сделать ее обратно плоской. :)

Мы знаем, что все точки данной поверхности равноудалены от плоскости, где расположен сенсор.

Поэтому можно ввести коэффициент нормировки для каждой из точек (x, y). Который будет равен отношению реального расстояния к полученному.

Зная этот коэффициент можно узнать, реальное расстояние до точки. Просто умножив полученное от кинекта значение на коэффициент нормировки в данной точке (x, y).

Коэффициенты считаем один раз по калибровочному изображению. А затем загружаем в свое приложение и пользуемся.

Минусы решения

  • Я предположил, что коэффициент нормировки зависит только от координат (x, y), но не зависит от глубины. Так это или нет — проверю. (пока датчик говорит, что я прав)
  • Плоскость по которой выполняется калибровка должна находится на расстоянии 2,5-3,5 метров от сенсора. Именно при таком положении коэффициенты нормировки будет точными.
  • Так как сенсор довольно чувствителен, то его положение должно быть параллельно плоскости калибровки (мне помогал обычный строительный уровень)
  • Так же перед калибровкой полученное изображение плоскости нужно сгладить для того, чтобы избавиться от шумов (это умеет делать матлабоский smoothn)

UPD

Код на гитхабе

Kinect: о восстановлении координат и абберациях разного рода

Вы не подумайте ничего - это плоскость.Нет. Вы не подцмайте ничего. это плоскость — вид сбоку. А точнее мой потолок. И не посмотри я на него через сенсоры кинета, то в жизни бы не узнал, насколько он «плоский». :-D На самом деле потолок-то плоский. Но только разного рода нелинейные искажения, которые вносят сенсор и линзы не компенсируются ни встроенными калибровочными константами (которые зашиваются в каждую модель на заводе), ни функцией преобразования глубины кинекта в глубину реальную. Т.е. в метры/миллиметры.

Так что же не так?

Начнем с самого начала. Функция преобразования данных с датчика в глубину нелинейна по своей природе. Т.е. чем ближе объект наблюдения, чем с большей точностью мы можем мерять расстояние до объекта. Чем объект дальше, тем точность меньше. А это значит, что на 1 диницу шага датчика на ближнем расстоянии приходится меньше миллиметров.

На сейчас ребята из openni предлагают новую версию функции преобразования сырых данных в метрические.

0.1236 * tan(rawDisparity / 2842.5 + 1.1863)

Эта формула даст нам значения в метрах. Есть еще одна формула (она предлагалась раньше).

1/(rawDisparity * -0.0030711016 + 3.3309495161)

Теперь говорят, что она морально устарела. :)

Посмотрим на обе формулы.

Функция вычисления реальной глубины изображенияТут мы видим, что обе дают практически одинаковый результат. Даже если их и увеличить, то расхождения будут заметны только на сильно высоких значениях датчика. Стоит заметить, что по иксу — это сырые данные. А по игреку — восстановленное расстояние (в миллиметрах. для удобства обе функции были домножены на 1000).

Ок. Расстояние есть. Дальше нужно получать как-то координаты x и y. Так как с одной глубиной ничего интересного не получиться.

Тут все интереснее.

Мы знаем из спецификации сенсора (я говорю про первую версию если что):

  • угол обзора по горизонтали — 58 градусов
  • угол обзора по вертикали — 45 градусов
  • количество точек с датчика — 640х480

И тут у нас должно появиться подозрение на эти спеки. Почему? Потому что ничего не говорится про относительный шаг между точками по горизонтали и по вертикали.

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

Но что же получается. А получается следующее (изображение уперто отсюда).

Поиск расстояния

tan(45/2)/tan(58/2) = 0,747261047

А это значит, что точка по вертикали будет 0,747261047, а точка по горизонтали — 1.

Отлично. соотношение расстояний между точками по горизонтали и вертикали нашли.

Тперь надо найти точки.

После того, как данные глубины получены с кинекта формируем массив координат.

(x_v, y_v. raw_depth)

Здесь x_v и y_v — это всего лишь индексы строки и столба в двумерном массиве глубин.

x_v = [0, 639]
y_v = [0, 479]

дальше надо лишь превратить эту точку в точку с реальными координатами.

z_w = 123.6 * tan(depth / 2842.5 + 1.1863)

Эм. А x_w и y_w? Тут все просто. По картинке выше мы знаем, что

x_v/f = x_w/z_w

Где f — это расстояние от камеры, то вьюпорта.

f = MAX_X/2 / tan(58/2) = 639/2 / tan(58/2)  = 319.5/tan(58/2)

Или аналогично для оси ординат. Но тут не стоит забывать про коэффициент отношения между расстоянием по горизонтали и вертикали.

f = 0.747261047*239.5/tan(45/2)

Теперь все просто

x_w = (x_v - 640/2) * z_w / f
y_w = (y_v - 480/2) * 0.747261047 * z_w / f

В случае с y_w не забываем про масштабный коэффициент.

Но все эти вфводы позволяют нам лиш частично восстановить изображение. И все потому, что функция глубины — это фукция от трех переменных: x_v, y_v и depth, а не просто от depth. :) Так как влияние разного рода аббераций слишком велико (не то чтобы слишком +-10 самнтиметров по краям на растоянии 2 метра).

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

Потолок (или часть сферы?)

UPD

Код на гитхабе