FOSSredder 0.5.0
Loading...
Searching...
No Matches
ImportPipelineHelpers.cpp
Go to the documentation of this file.
1
5
8
9#include "ImportConstants.h"
12
13#include <algorithm>
14#include <fstream>
15#include <sstream>
16#include <thread>
17
19using core::application::importing::statement::DefaultStatementParser;
20namespace poppler = core::ports::pdf_rendering;
21namespace tesseract = core::ports::text_recognition;
22
23namespace {
24
25constexpr auto kTsvArtifactPrefix = "tesseract/page_";
26constexpr auto kTsvArtifactSuffix = "_crop_0.tsv";
27constexpr auto kParserLogArtifactPrefix = "parser/page_";
28constexpr auto kParserLogArtifactSuffix = ".log";
29constexpr auto kPausePollInterval = std::chrono::milliseconds(50);
30
31bool importCanceled(const ImportRequest& req)
32{
33 return req.cancelFlag && req.cancelFlag->load();
34}
35
36bool waitWhilePaused(const ImportRequest& req)
37{
38 while (req.pauseFlag && req.pauseFlag->load()) {
39 if (importCanceled(req)) return false;
40 std::this_thread::sleep_for(kPausePollInterval);
41 }
42 return !importCanceled(req);
43}
44
45void safeReleaseLimiter(core::jobs::SlotLimiter* ocrLimiter,
46 core::ports::diagnostics::IErrorReporter* errorReporter,
47 const char* origin)
48{
49 try {
50 if (ocrLimiter) ocrLimiter->release();
51 } catch (...) {
52 core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, origin, std::current_exception());
53 }
54}
55
56core::ports::text_recognition::ExtractResult extractWithLimiter(const std::shared_ptr<core::ports::text_recognition::ITextRecognizer>& tesseractService,
57 const tesseract::ExtractRequest& request,
58 core::jobs::SlotLimiter* ocrLimiter,
59 double& accumulatedOcrSec)
60{
61 if (ocrLimiter) ocrLimiter->acquire();
62 const auto ocrStart = std::chrono::steady_clock::now();
63 auto result = tesseractService->extract(request);
64 accumulatedOcrSec += std::chrono::duration<double>(std::chrono::steady_clock::now() - ocrStart).count();
65 if (ocrLimiter) ocrLimiter->release();
66 return result;
67}
68
69std::string makeTsvArtifactKey(size_t pageIndex)
70{
71 return std::string(kTsvArtifactPrefix) + std::to_string(pageIndex) + kTsvArtifactSuffix;
72}
73
74std::string makeParserLogArtifactKey(size_t pageIndex)
75{
76 return std::string(kParserLogArtifactPrefix) + std::to_string(pageIndex) + kParserLogArtifactSuffix;
77}
78
79void storeTsvArtifact(ImportResult& out,
80 size_t pageIndex,
81 const tesseract::ExtractResult& extractResult,
82 std::mutex& artifactsMutex,
83 core::ports::diagnostics::IErrorReporter* errorReporter)
84{
85 try {
86 std::vector<uint8_t> tsvData(extractResult.tsv.begin(), extractResult.tsv.end());
87 std::lock_guard<std::mutex> guard(artifactsMutex);
88 out.artifacts[makeTsvArtifactKey(pageIndex)] = std::move(tsvData);
89 } catch (...) {
90 core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, "core::import::DefaultImportStatementStrategy::storeTsvArtifact", std::current_exception());
91 }
92}
93
94}
95
96std::vector<uint8_t> readImportBytes(const std::filesystem::path& path)
97{
98 return readImportBytes(path, nullptr);
99}
100
101std::vector<uint8_t> readImportBytes(const std::filesystem::path& path, core::ports::diagnostics::IErrorReporter* errorReporter)
102{
103 std::vector<uint8_t> out;
104 try {
105 std::ifstream ifs(path, std::ios::binary);
106 if (!ifs) return out;
107 out.assign((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
108 } catch (...) { core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, "core::import::DefaultImportStatementStrategy::readBytes", std::current_exception()); }
109 return out;
110}
111
113 size_t totalPages,
114 const ImportRequest& req,
115 const poppler::RenderResult& renderRes,
116 const poppler::ExtractResult& extractRes,
117 const std::shared_ptr<core::ports::document_image_processing::IDocumentImageProcessor>& documentImageProcessor,
118 const std::shared_ptr<core::ports::text_recognition::ITextRecognizer>& tesseract,
119 core::jobs::SlotLimiter* ocrLimiter,
120 const ProgressReporter& report,
121 std::atomic<size_t>& doneUnits,
122 size_t totalUnits,
124 ImportResult& out,
125 std::mutex& artifactsMutex)
126{
127 const auto pageStart = std::chrono::steady_clock::now();
128 PageWork page;
129 page.pageIndex = pageIndex;
130
131 static constexpr size_t unitsPerPage = constants::kUnitsPerPage;
132 size_t localUnitsDone = 0;
133 auto unitDone = [&](size_t inc, const std::string& label) {
134 localUnitsDone += inc;
135 const size_t d = doneUnits.fetch_add(inc) + inc;
136 const double frac = std::min(1.0, static_cast<double>(d) / static_cast<double>(totalUnits));
138 report(progress, std::string("[") + std::to_string(pageIndex + 1) + "/" + std::to_string(totalPages) + "] " + label);
139 };
140 auto finishUnits = [&](const std::string& label) {
141 if (localUnitsDone >= unitsPerPage) return;
142 unitDone(unitsPerPage - localUnitsDone, label);
143 };
144
145 if (req.cancelFlag && req.cancelFlag->load()) {
146 finishUnits(std::string(constants::kProgressCanceled));
147 return page;
148 }
149 if (!waitWhilePaused(req)) {
150 finishUnits(std::string(constants::kProgressCanceled));
151 return page;
152 }
153
154 std::vector<uint8_t> pageBytes;
155 if (pageIndex < renderRes.imageBytes.size() && !renderRes.imageBytes[pageIndex].empty()) {
156 pageBytes = renderRes.imageBytes[pageIndex];
157 } else if (pageIndex < renderRes.images.size() && !renderRes.images[pageIndex].empty()) {
158 pageBytes = readImportBytes(renderRes.images[pageIndex]);
159 }
160 if (pageBytes.empty()) {
161 finishUnits(std::string(constants::pageSteps::kNoImage));
162 return page;
163 }
164 if (!waitWhilePaused(req)) {
165 finishUnits(std::string(constants::kProgressCanceled));
166 return page;
167 }
168
169 auto maskRequest = buildMaskRequest(pageBytes, pageIndex, req, extractRes);
170
171 if (maskRequest.useTesseract) {
172 if (!waitWhilePaused(req)) {
173 finishUnits(std::string(constants::kProgressCanceled));
174 return page;
175 }
176 try {
177 const auto tessRequest = buildMaskOcrRequest(pageBytes, req);
178 const auto tessResponse = extractWithLimiter(tesseract, tessRequest, ocrLimiter, page.ocrSec);
179
180 maskRequest.tesseractTsv = tessResponse.tsv;
181 } catch (...) {
182 safeReleaseLimiter(ocrLimiter, errorReporter, "core::import::DefaultImportStatementStrategy::ocrLimiterReleaseMask");
183 core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, "core::import::DefaultImportStatementStrategy::tesseractMaskExtract", std::current_exception());
184 }
185 }
186
187 if (!waitWhilePaused(req)) {
188 finishUnits(std::string(constants::kProgressCanceled));
189 return page;
190 }
191 auto maskResponse = documentImageProcessor->mask(maskRequest);
192 std::vector<uint8_t> maskedBytes = !maskResponse.maskedImageBytes.empty() ? maskResponse.maskedImageBytes : pageBytes;
193 unitDone(1, std::string(constants::pageSteps::kMask));
194
195 if (!waitWhilePaused(req)) {
196 finishUnits(std::string(constants::kProgressCanceled));
197 return page;
198 }
199 const auto detectRequest = buildDetectRequest(maskedBytes, pageIndex, req);
200 auto detectResponse = documentImageProcessor->detect(detectRequest);
201 unitDone(1, std::string(constants::pageSteps::kDetect));
202
203 if (!detectResponse.detected) {
204 finishUnits(std::string(constants::pageSteps::kNoTable));
205 return page;
206 }
207
208 if (!waitWhilePaused(req)) {
209 finishUnits(std::string(constants::kProgressCanceled));
210 return page;
211 }
212 const auto cropRequest = buildCropRequest(pageBytes, pageIndex, req, detectResponse);
213 auto cropResponse = documentImageProcessor->crop(cropRequest);
214 unitDone(1, std::string(constants::pageSteps::kCrop));
215
216 if (cropResponse.croppedImageBytes.empty() || cropResponse.croppedImageBytes.front().empty()) {
217 finishUnits(std::string(constants::pageSteps::kNoCrop));
218 return page;
219 }
220
221 const auto tableRequest = buildTableOcrRequest(cropResponse.croppedImageBytes.front(), pageIndex, req, detectResponse);
222
223 if (!waitWhilePaused(req)) {
224 finishUnits(std::string(constants::kProgressCanceled));
225 return page;
226 }
227 try {
228 page.ocr = extractWithLimiter(tesseract, tableRequest, ocrLimiter, page.ocrSec);
229 } catch (...) {
230 safeReleaseLimiter(ocrLimiter, errorReporter, "core::import::DefaultImportStatementStrategy::ocrLimiterReleaseTable");
231 core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, "core::import::DefaultImportStatementStrategy::tesseractTableExtract", std::current_exception());
232 finishUnits(std::string(constants::pageSteps::kOcrFailed));
233 return page;
234 }
235
236 unitDone(1, std::string(constants::pageSteps::kOcr));
237
238 page.hasTable = true;
239 page.table = detectResponse.table;
240 page.cropBytes = std::move(cropResponse.croppedImageBytes.front());
241 page.ocrWords = page.ocr.words.size();
242
243 storeTsvArtifact(out, pageIndex, page.ocr, artifactsMutex, errorReporter);
244
245 finishUnits(std::string(constants::pageSteps::kDone));
246 page.totalSec = std::chrono::duration<double>(std::chrono::steady_clock::now() - pageStart).count();
247 return page;
248}
249
251 const std::vector<PageWork>& pages,
252 const std::shared_ptr<core::ports::document_image_processing::IDocumentImageProcessor>& documentImageProcessor,
253 ImportResult& out,
254 std::vector<core::application::importing::draft::TransactionDraft>& all,
255 std::string& carriedBookingDate,
256 int& nextTxIndex,
258 const ProgressReporter& report,
259 std::mutex& artifactsMutex)
260{
261 FinalizeStats stats;
262 const size_t finalizePageCount = std::max<size_t>(1, pages.size());
263 for (size_t pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
264 if (!waitWhilePaused(req)) {
265 report(0.0, std::string(constants::kProgressCanceled));
266 break;
267 }
268 if (req.cancelFlag && req.cancelFlag->load()) {
269 report(0.0, std::string(constants::kProgressCanceled));
270 break;
271 }
272
273 const auto& page = pages[pageIndex];
274 if (!page.hasTable) continue;
275
276 const double fraction = static_cast<double>(pageIndex + 1) / static_cast<double>(finalizePageCount);
277 const double progress = constants::kProgressFinalizeBase + constants::kProgressFinalizeSpan * fraction;
278 report(progress, std::string("[") + std::to_string(pageIndex + 1) + "/" + std::to_string(finalizePageCount) + "] " + std::string(constants::pageSteps::kFinalize));
279
280 ++stats.pagesWithTable;
281 stats.totalOcrWords += page.ocrWords;
282 stats.maxPageSec = std::max(stats.maxPageSec, page.totalSec);
283 stats.sumPageSec += page.totalSec;
284 stats.sumOcrSec += page.ocrSec;
285
286 auto parsed = DefaultStatementParser::parse(page.table,
287 page.ocr,
288 std::string(),
289 documentImageProcessor,
290 page.cropBytes,
291 carriedBookingDate,
292 nextTxIndex);
293 carriedBookingDate = parsed.lastBookingDate;
294 nextTxIndex = parsed.nextTransactionIndex;
295
296 if (!parsed.transactions.empty()) {
297 for (auto& transaction : parsed.transactions) all.push_back(std::move(transaction));
298 }
299
300 if (!parsed.debugLines.empty()) {
301 try {
302 std::ostringstream outLog;
303 for (const auto& line : parsed.debugLines) outLog << line << '\n';
304 const auto serialized = outLog.str();
305 std::lock_guard<std::mutex> guard(artifactsMutex);
306 out.artifacts[makeParserLogArtifactKey(page.pageIndex)] = std::vector<uint8_t>(serialized.begin(), serialized.end());
307 } catch (...) {
308 core::errors::reportException(errorReporter,
310 "core::import::DefaultImportStatementStrategy::storeParserLogArtifact",
311 std::current_exception());
312 }
313 }
314
315 if (!parsed.artifacts.empty()) {
316 try {
317 std::lock_guard<std::mutex> guard(artifactsMutex);
318 for (auto& [key, value] : parsed.artifacts) out.artifacts.emplace(key, std::move(value));
319 } catch (...) { core::errors::reportException(errorReporter, core::errors::ErrorSeverity::Warning, "core::import::DefaultImportStatementStrategy::mergeParsedArtifacts", std::current_exception()); }
320 }
321 }
322
323 return stats;
324}
325
326}
Declares the public default statement parser used by the import pipeline.
Provides shared helpers for reporting structured errors and exceptions.
Defines private constants shared by core import orchestration.
Declares private request-building helpers for per-page import processing.
Declares private helper types and functions for the import pipeline implementation.
static ParseResult parse(const core::ports::document_image_processing::Table &table, const core::ports::text_recognition::ExtractResult &ocr, const std::string &pageCropImagePath, std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > documentImageProcessor, const std::vector< uint8_t > &pageCropImageBytes, std::string initialBookingDate={}, int initialTransactionIndex=1)
Parses an OCR-backed table into transaction drafts and artifacts.
Limits concurrent access to a bounded number of slots.
Definition Scheduler.h:27
void release()
Release one acquired slot and wake a waiting caller.
Definition Scheduler.cpp:34
void acquire()
Block until a slot is available and acquire it.
Definition Scheduler.cpp:27
Port for reporting structured diagnostic error events.
constexpr std::string_view kProgressCanceled
PageWork processImportPage(size_t pageIndex, size_t totalPages, const ImportRequest &req, const poppler::RenderResult &renderRes, const poppler::ExtractResult &extractRes, const std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > &documentImageProcessor, const std::shared_ptr< core::ports::text_recognition::ITextRecognizer > &tesseract, core::jobs::SlotLimiter *ocrLimiter, const ProgressReporter &report, std::atomic< size_t > &doneUnits, size_t totalUnits, core::ports::diagnostics::IErrorReporter *errorReporter, ImportResult &out, std::mutex &artifactsMutex)
FinalizeStats finalizeParsedPages(const ImportRequest &req, const std::vector< PageWork > &pages, const std::shared_ptr< core::ports::document_image_processing::IDocumentImageProcessor > &documentImageProcessor, ImportResult &out, std::vector< core::application::importing::draft::TransactionDraft > &all, std::string &carriedBookingDate, int &nextTxIndex, core::ports::diagnostics::IErrorReporter *errorReporter, const ProgressReporter &report, std::mutex &artifactsMutex)
std::function< void(double, const std::string &)> ProgressReporter
std::vector< uint8_t > readImportBytes(const std::filesystem::path &path)
core::ports::document_image_processing::DetectRequest buildDetectRequest(const std::vector< uint8_t > &maskedBytes, size_t pageIndex, const ImportRequest &req)
core::ports::document_image_processing::CropRequest buildCropRequest(const std::vector< uint8_t > &pageBytes, size_t pageIndex, const ImportRequest &req, const core::ports::document_image_processing::DetectResult &detectResponse)
core::ports::text_recognition::ExtractRequest buildTableOcrRequest(const std::vector< uint8_t > &croppedBytes, size_t pageIndex, const ImportRequest &req, const core::ports::document_image_processing::DetectResult &detectResponse)
core::ports::document_image_processing::MaskRequest buildMaskRequest(const std::vector< uint8_t > &pageBytes, size_t pageIndex, const ImportRequest &req, const core::ports::pdf_rendering::ExtractResult &extractRes)
core::ports::text_recognition::ExtractRequest buildMaskOcrRequest(const std::vector< uint8_t > &pageBytes, const ImportRequest &req)
void reportException(core::ports::diagnostics::IErrorReporter *reporter, ErrorSeverity severity, const char *origin, std::exception_ptr exception)
Report an exception through the reporter's default exception handling.
std::map< std::string, std::vector< std::uint8_t > > artifacts
core::ports::text_recognition::ExtractResult ocr
core::ports::document_image_processing::Table table