Skip to content

Latest commit

 

History

History
112 lines (93 loc) · 7.54 KB

File metadata and controls

112 lines (93 loc) · 7.54 KB

plotparse-cpp

C++-порт plotparse — детерминированного (без нейросетей) распознавания графиков в PDF и подбора аналитической формулы. Алгоритм 1:1 с Python-версией, см. PORTING.md там же для теории; здесь — только то, что изменилось при переносе.

Сборка

Зависимости (Ubuntu 24.04, устанавливались как apt-пакеты):

apt-get install cmake ninja-build pkg-config \
  libmupdf-dev mupdf-tools libeigen3-dev libceres-dev \
  libgflags-dev libgoogle-glog-dev \
  libfreetype-dev libjpeg-dev libjbig2dec0-dev libopenjp2-7-dev \
  libharfbuzz-dev libgumbo-dev libmujs-dev \
  libopencv-dev libtesseract-dev tesseract-ocr tesseract-ocr-rus
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

Даёт build/analyze_pdf.

Запуск

./build/analyze_pdf chart.pdf                 # текстовое описание
./build/analyze_pdf chart.pdf --json          # машиночитаемый вывод
./build/analyze_pdf scan.pdf --csv out/       # + выгрузка точек кривой
./build/analyze_pdf chart.pdf --force raster  # принудительно через CV

Структура

Файл Соответствует Библиотеки
calib.{hpp,cpp} calib.py Eigen (QR вместо np.polyfit)
pdf_backend.{hpp,cpp} pymupdf-вызовы внутри vector.py/raster.py MuPDF C API
vector.cpp vector.py — (геометрия, чистый C++)
raster.cpp raster.py OpenCV, Tesseract
fit.{hpp,cpp} fit.py Eigen (полиномы), Ceres (нелинейные модели)
report.cpp report.py —
main.cpp analyze_pdf.py —

pdf_backend.hpp и raster.hpp — единственные места, знающие про MuPDF и OpenCV/Tesseract соответственно; vector.cpp/report.cpp работают только с их структурами (PageContent, RawPath, TextSpan, PdfDocument::Raster).

Отличия от Python-версии

Помимо замены библиотек (см. таблицу в ../plotparse-src/plotparse-1.0/PORTING.md):

  • Солвер нелинейных моделей — Ceres вместо scipy.curve_fit. Формулы и R² победившей модели совпадают с эталоном (reference/expected.txt) почти всегда посимвольно; 2-3-е места в списке «альтернативы» иногда отличаются — Ceres на заведомо плохих моделях (например, гауссиана поверх синусоиды) сходится к другому локальному оптимуму, чем LM у scipy. На итоговый выбор модели это не влияет.
  • --force vector теперь действительно означает «только вектор». В оригинале force разветвлялся только для "raster"; "vector" не отключал автоподхват растровой веткой при неудаче — это несогласованность с собственным CLI-хелпом Python-версии, здесь исправлено.
  • Отчёт на английском (в оригинале был русский). Перевод сверен по хэшу всех числовых токенов вывода до и после — совпал, то есть изменились только слова. Побочный эффект: побайтовая сверка с Python-эталоном больше невозможна, поэтому добавлен отдельный эталон reference/expected_cpp.txt.
  • Подписи кривых. Каждой серии ищется своя подпись (в Python-версии серии различались только цветом и порядковым номером). Два случая: легенда — если слева от текста стоит короткий цветной образец, подпись уходит серии того же цвета (а не геометрически ближайшей — иначе все записи легенды прилипают к кривой, проходящей мимо неё); подпись у самой кривой — берётся ближайшая по расстоянию серия, не дальше 15% меньшей стороны поля. Соответствие один-к-одному, жадно по возрастанию «цены». Спаны, уже занятые под числа осей/подписи осей/заголовок, в кандидаты не попадают. Работает в векторной ветке; в растровой подписи кривых пока не ищутся (нужен OCR по всей области графика, а не по узким полосам у осей).
  • OCR кириллицы работает, если стоит tesseract-ocr-rus. Эталонный reference/expected.txt был получен в окружении, где эта языковая модель, похоже, отсутствовала (подписи остались нечитаемым набором латинских букв даже после OCR-фолбэка) — с установленным пакетом здесь подписи восстанавливаются корректно («Время, с», «Сигнал», …).

Два бага, найденных при портировании (не описаны в исходном PORTING.md)

  1. Двойной flip координат страницы. fz_bound_page/fz_run_page уже отдают «page space» с origin в левом верхнем углу и y вниз — в отличие от сырых координат внутри fz_path (которые видит fz_path_walker до применения ctm). Матрица перехода должна быть только сдвигом (fz_make_matrix(1,0,0,1,-x0,-y0)), без переворота — переворот даёт зеркально перевёрнутую страницу.
  2. Задвоение геометрии рамки осей. PDF-оператор «залить+обвести» (B) MuPDF разбирает на два отдельных вызова fz_device — fill_path, затем stroke_path с тем же fz_path* и тем же ctm. PyMuPDF's get_drawings() отдаёт это одним объектом (type: "fs", fill и color вместе); без слияния в pdf_backend.cpp рамка/фон осей задваивается и портит подсчёт grid-линий и тиков в скоринге. Слияние — по указателю fz_path* и совпадению ctm между последовательными fill_path→stroke_path.

Тесты

reference/ — те же PDF и expected.txt, что и в Python-версии. Прогон:

for f in exp sin logy scatter_parabola power_en no_chart raster_exp; do
  ./build/analyze_pdf reference/$f.pdf
done