Яндекс OCR: разметка данных

В ходе использования облака Yandex OCR для распознавания данных, выявилось что при анализе получаемого на выходе JSON, очень не достаёт визуализации полученных данных. Для того чтобы эту ситуацию чуть улучшить, написал скриптик, который на входе получает картинку для распознавания и JSON полученный от Яндекса, а на выходе выдаёт картинку с нанесенными распознанными блоками и таблицами.

Собственно код:

<?php
$res=json_decode(file_get_contents("res.json"));  // прочитаем json
$source = imagecreatefromjpeg ("pdfs/11-1.jpg");
$pink = imagecolorallocate($source, 255, 105, 180);
$white = imagecolorallocate($source, 255, 255, 255);
$green = imagecolorallocate($source, 132, 135, 28);
$blue = imagecolorallocate($source, 0, 0, 255);

// размечаю блоки
$bl=0;
foreach ($res->result->textAnnotation->blocks as $block) {    
    $kof=1.5;
    $points=[];
    $points[]=$block->boundingBox->vertices[0]->x*$kof;
    $points[]=$block->boundingBox->vertices[0]->y*$kof;
    $points[]=$block->boundingBox->vertices[1]->x*$kof;
    $points[]=$block->boundingBox->vertices[1]->y*$kof;
    $points[]=$block->boundingBox->vertices[2]->x*$kof;
    $points[]=$block->boundingBox->vertices[2]->y*$kof;
    $points[]=$block->boundingBox->vertices[3]->x*$kof;
    $points[]=$block->boundingBox->vertices[3]->y*$kof;
    $points[]=$block->boundingBox->vertices[0]->x*$kof;
    $points[]=$block->boundingBox->vertices[0]->y*$kof;    
    imagepolygon($source,$points,$green);
    imagettftext($source,20,0,$points[0],$points[1],$green,"/usr/share/fonts/truetype/r7-office/ext/PTS55F.ttf",$bl);
    $bl++;
};

//размечаю таблицы
$bl=0;
foreach ($res->result->textAnnotation->tables as $table) {    
    $kof=2.1;
    $offset_x=110;
    $offset_y=10;
    $points=[];
    $points[]=$table->boundingBox->vertices[0]->x*$kof+$offset_x;
    $points[]=$table->boundingBox->vertices[0]->y*$kof+$offset_y;
    $points[]=$table->boundingBox->vertices[1]->x*$kof+$offset_x;
    $points[]=$table->boundingBox->vertices[1]->y*$kof+$offset_y;
    $points[]=$table->boundingBox->vertices[2]->x*$kof+$offset_x;
    $points[]=$table->boundingBox->vertices[2]->y*$kof+$offset_y;
    $points[]=$table->boundingBox->vertices[3]->x*$kof+$offset_x;
    $points[]=$table->boundingBox->vertices[3]->y*$kof+$offset_y;
    $points[]=$table->boundingBox->vertices[0]->x*$kof+$offset_x;
    $points[]=$table->boundingBox->vertices[0]->y*$kof+$offset_y;    
    imagepolygon($source,$points,$blue);
    imagettftext($source,20,0,$points[0],$points[1],$blue,"/usr/share/fonts/truetype/r7-office/ext/PTS55F.ttf",$bl);
    $bl++;
    
}
imagejpeg($source, 'out.jpg');

Получаем картинку вида:

разметка на основе Yandex.ocr

PHP и его странная математика

Столкнулся с очень чудесной ошибкой на PHP:

<?php
$a=1.15;
$b=$a*100;
var_dump($b);
echo $b;
echo "\n";
?>

Что вы ожидаете увидеть в выводе? 115? А вот и нет. Если echo выведет 115, то var_dump выведет 114.99999

Что не так с float?

Тип float в языке PHP, как и его “родственник” тип double, вовсе не предназначен для точного представления десятичных дробей. Всё что мы записали в float, хранится в приближенном значении, с некоторой погрешностью.

Решение: в виду того что в PHP это считается фичей, а не багом, то для точной работы с математикой нужно использовать модуль bcmath. Ну или использовать округления до нужной точности

P.S. Не первый раз уже вспотыкаюсь об это

GitItea: does not appear to be a git repository

Как оказалось такая совершенно не очевидная ошибка показывается (помимо варианта что и на самом деле вы напутали с URL репозитария) и когда что-то случилось с вашим открытым SSH ключём. Соответственно достаточно сгенерировать новый:

ssh-keygen -t ed25519 -C "your_email@example.com"

А затем добавить содержимое файла ~/.ssh/id_ed25519.pub в админке GitItea в раздел «Ключи SSH»

Перенос БД с MySQL на Postgresql

Как то уже описывал в одном из постов, что в одной из инсталяций внедрял WordPress на основе БД PostreeSQL. И в принципе спустя год могу сказать что особых проблем пока не возникло. Кроме одного НО! Если требуется какой-то установить плагин, то чаще всего он просто так, без некоторой адаптации не заработает. Вот сейчас адаптирую плагин «WP Voting Contest Lite«, который можно использовать для «выбора лучшего фото» пользователями.

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

  • wp_votes_tbl
  • wp_votes_post_contestant_track
  • wp_votes_custom_registeration_contestant
  • wp_votes_custom_field_contestant

В которых встречается имеющийся в MySQL, но отсутствующий в PostgreSQL тип ENUM. Ну чтож.. создадим эти типы «руками», благо такая возможность в PostgreSQL предусмотрена:

CREATE TYPE public.admin_only_enum AS ENUM ('Y','N');
CREATE TYPE public.grid_only_enum AS ENUM ('Y','N');
CREATE TYPE public.list_only_enum AS ENUM ('Y','N');	
CREATE TYPE public.pretty_view_enum AS ENUM ('Y','N');	
CREATE TYPE public.question_type_enum AS ENUM ('TEXT','TEXTAREA','MULTIPLE','SINGLE','DROPDOWN','FILE','DATE');	
CREATE TYPE public.required_enum AS ENUM ('Y','N');	
CREATE TYPE public.set_limit_enum AS ENUM ('Y','N');	
CREATE TYPE public.show_labels_enum AS ENUM ('Y','N');	

И далее создать таблицы по типу:

CREATE TABLE public.wp_votes_custom_registeration_contestant (
	id bigserial NOT NULL,
	"sequence" int4 NULL,
	question_type public.question_type_enum NULL,
	question text NULL,
	system_name varchar(45) NULL,
	response text NULL,
	required public.required_enum NULL,
	required_text text NULL,
	admin_only public.admin_only_enum NULL,
	delete_time varchar(45) NULL,
	react_val text NULL,
	wp_user int4 NULL,
	CONSTRAINT wp_votes_custom_registeration_contestant_pk PRIMARY KEY (id)
);

JavaScript: Еще один вариант загрузки файла по клику на кнопку

Общая идея следующая: по клику на кнопку вызываем AJAX запрос на сервер с определенными параметрами передаваемыми в POST, затем получив в результат запроса файл — формируем в DOM на файл в формате blob, и тут-же её нажимаем. В результате браузер показывает диалоговое окно сохранения файла.

Таким необычным способом мы убиваем несколько зайцев сразу:

  1. Получаем возможность показать ошибку, если вдруг файл на сервере сформировать не удалось. Это полезно если например файл формируется на сервере «на лету» — например файл XLSX с отчётом
  2. Мы можем передать серверу какие-то условия для формирования файла в POST запросе
  3. На странице мы не размещаем заранее данные в тегах <form></form>, как практикуется в подобных решениях
  4. Пользователю достаточно нажать на кнопку один раз для получения результата.

В результате скрипт формирования файла может выглядеть примерно следующим образом:

На клиенте:

/**
 * Формирование отчета
 * @param {type} filename - имя файла на сервере для формирования отчета
 * @param {type} ext - выходное расширение (например xlsx,csv)
 * @returns {Number}
 */
function get_report(filename,ext){
        $("#global").addClass("loading");    
        var xhr = new XMLHttpRequest();
        xhr.open('POST', '?r=reports/'+filename, true);
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.responseType = 'blob';     
        xhr.onload = function(e) {
            $("#global").removeClass("loading");    
            if (this.status == 200) {
                var link=document.createElement('a');
                link.href=window.URL.createObjectURL(this.response);                
                console.log("Расширение:"+ext);
                link.download="report."+ext;
                link.click();
            }
            else {
                console.log(e);
                ToastMessage("error","Произошла ошибка при формировании файла. Попробуйте позднее или обратитесь к администратору системы.");
            }
        };     
        var form_data = new Map();
        form_data.period_from = period_from.value;
        form_data.period_to = period_to.value;
        form_data.area = area_select.value;
        form_data.division_check=division_check.checked;
        form_data.period_check=period_check.checked;
        xhr.send(mapToQueryString(form_data));                                          
};

На сервере:

    public function actionGet_report_by_settlers(){               
       $request = Yii::$app->request;      
       $area= $request->post("area");
       $dir=Yii::$app->basePath."/web/templates";
        
      $oSpreadsheet = IOFactory::load($dir . "/report_1_1.xlsx");
      ...
      формируем файл эксель
      ...
      $oWriter = IOFactory::createWriter($oSpreadsheet, 'Xlsx');
      $oWriter->save('php://output');      
    };
1 16 17 18 19 20 311