Odborný článok

Extrakcia tabuliek zo strán PDF v Delphi s PDFium

PDFium Component deteguje tabuľky na strane PDF a vracia ich ako mriežku buniek s rozpätiami riadkov a stĺpcov, hlavičkovými riadkami a hodnotou spoľahlivosti, prostredníctvom ExtractTables pre jednu stranu a ExtractDocumentTables pre celý dokument. Každá tabuľka sa jedným volaním konvertuje na CSV alebo JSON a tabuľky, ktoré pokračujú cez zalomenie strany, sa dajú prepojiť do reťazca pokračovania

PDF nemá objekt tabuľky. Tabuľka v PDF je množina textových behov umiestnených tak, aby ich človek čítal ako mriežku, niekedy s okolo nakreslenými čiarami a často bez nich. Rekonštrukcia mriežky znamená rekonštrukciu zámeru, ktorý súbor nikdy nezaznamenal, a preto každý extrakčný nástroj produkuje mierne odlišné výsledky a preto je nástroj, ktorý vám povie svoju spoľahlivosť, užitočnejší než ten, ktorý to neurobí

Dva režimy detekcie pre dva druhy tabuliek

Detekcia podľa čiar používa nakreslené čiary. Každý obtiahnutý segment cesty sa transformuje do súradníc strany cez maticu objektu strany, horizontálne a vertikálne čiary sa pretínajú a priesečníky tvoria spojité komponenty. Každý komponent sa stane vlastnou zoradenou mriežkou pozícií X a Y, čo je to, čo zabraňuje tomu, aby sa dve samostatné tabuľky na tej istej strane zlúčili do jednej nezmyselnej mriežky

Detekcia podľa medzier spracúva tabuľky nakreslené zarovnaním namiesto čiar. Rámiky slov sa zoskupia do vizuálnych riadkov, medzery vnútri riadku ho rozdelia na kandidátske stĺpce a tabuľka sa prijme iba vtedy, keď sa aspoň MinRows riadkov opakuje aspoň MinColumns-krát so zarovnanými vľavo kotvami v rámci AlignmentTolerance. Faktor medzery medzi riadkami má predvolenú hodnotu 3, čo pokrýva zhruba 30-bodové rozstupy základní typické pre 12-bodový text bez toho, aby dovolil jednému riadku obsahujúcemu niekoľko textových behov predstierať tabuľku

Diagram pipeline detekcie tabuliek komponentu PDFium v Delphi, kde priesečníky nakreslených čiar a zarovnané riadky rámikov slov napájajú jeden ohodnotený záznam tabuľky s exportom do CSV a JSON
Detekcia podľa čiar pretína nakreslené ťahy, zatiaľ čo detekcia podľa medzier počíta zarovnané riadky rámikov slov; kandidáti, ktorí prejdú cez MinRows a MinColumns, vzniknú so skóre spoľahlivosti a pripojeným DetectionMode
uses
  PDFium;

var
  Pdf: TPdf;
  Options: TPdfTableExtractionOptions;
  Tables: TPdfTables;
  I: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'annual-report.pdf';
    Pdf.LoadDocument;
    Pdf.PageNumber := 12;                    // od 1

    Options := TPdfTableExtractionOptions.Default;
    Options.DetectRuledTables := True;
    Options.DetectWhitespaceTables := True;
    Options.MinConfidence := 0.6;            // predvolená hodnota je 0,5
    Options.HeaderRowCount := 1;

    Tables := Pdf.ExtractTables(Options);
    for I := 0 to High(Tables) do
      Writeln(Format('table %d: %dx%d cells, confidence %.2f, mode %d',
        [I, Tables[I].RowCount, Tables[I].ColumnCount,
         Tables[I].Confidence, Ord(Tables[I].DetectionMode)]));

    if Length(Tables) > 0 then
      SaveText('page12-table0.csv', Tables[0].ToCsv);
  finally
    Pdf.Free;
  end;
end;

Ako sa obnovujú zlúčené bunky?

Toto je časť, ktorú naivné extraktory robia nesprávne. Zlúčenú bunku nemožno identifikovať iba z globálnej mriežky, pretože mriežka sa odvodzuje zo všetkých čiar na strane a zlúčenej oblasti jednoducho chýba vnútorná čiara, ktorá by ju bola oddelila

Pravidlo použité tu je lokálne: dve susedné základné bunky sa zlúčia, keď žiadna hraničná čiara nepokrýva interval medzi nimi. Union-find ich spojí, výsledné obdĺžnikové komponenty sa stanú hodnotami RowSpan a ColumnSpan a text sa priradí k základnej bunke podľa jej stredového bodu a potom nasleduje túto bunku až po jej koreň zlúčenia. Tento spôsob tiež udržiava náklady lineárne v počte slov plus buniek, namiesto kvadratického prehľadávania, ktoré by ste dostali testovaním každého slova proti každej bunke

Diagram obnovy zlúčených buniek pri extrakcii tabuliek PDFium v Delphi, kde union-find spája susedné základné bunky vždy, keď žiadna hraničná čiara nepokrýva interval medzi nimi, čo vytvára RowSpan a ColumnSpan
Union-find zlučuje susediace základné bunky, ktorých spoločný interval nenesie žiadnu nakreslenú hranicu, takže zlúčená hlavička sa vráti ako jedna bunka s nastaveným ColumnSpan namiesto jednej vyplnenej bunky obklopenej prázdnymi

Praktickým efektom je, že finančná tabuľka so zlúčenou hlavičkou „Total" rozpínajúcou sa cez tri stĺpce vyjde ako jedna bunka s rozpätím tri, namiesto jednej vyplnenej bunky a dvoch záhadne prázdnych

Pokračovanie naprieč stranami

Dlhé tabuľky sa lámu naprieč stranami a zaobchádzanie s fragmentom každej strany ako s nezávislou tabuľkou núti volajúceho ich zošívať. ExtractDocumentTables ich namiesto toho dokáže prepojiť, ale iba za prísnych podmienok: fragment musí byť najnižšou tabuľkou na skoršej strane, ďalší musí byť najvrchnejšou tabuľkou na nasledujúcej strane, čísla strán musia byť susedné a hranice stĺpcov sa musia zhodovať

Práve všetky štyri podmienky spolu zabraňujú zjavnej chybe, ktorou je reťazenie každej štvorstĺpcovej tabuľky v dokumente do jednej pomyselnej megatabuľky len preto, že sa zhodou okolností zdieľa počet stĺpcov. Keď podmienky platia, tabuľky zdieľajú identifikátor skupiny pokračovania a nesú metadáta pokračovania; keď neplatia, dostanete samostatné tabuľky a môžete sa rozhodnúť sami

Diagram pokračovania tabuľky naprieč stranami PDF v Delphi, kde štyri prísne brány rozhodujú, či sa najnižší fragment na jednej strane spojí s najvrchnejším fragmentom na ďalšej
Extrakcia na úrovni dokumentu prepája fragmenty iba vtedy, keď platia všetky štyri brány, čo zabraňuje splynutiu nesúvisiacich štvorstĺpcových tabuliek do jednej pomyselnej megatabuľky

Extrakcia na úrovni dokumentu zdieľa rozpočty MaxCells a MaxTables naprieč stranami namiesto ich resetovania na každej strane, a aktívnu stranu obnovuje v bloku finally, takže beh extrakcie v prehliadači ponechá používateľa hľadieť na stranu, na ktorej bol

Export bez poškodenia dát

Oba exportéry sú zámerne dôsledné pri escapovaní. CSV vždy uzatvára polia do úvodzoviek a zdvojuje vnútorné úvodzovky, čím sa vyhýba klasickému zlyhaniu, kde bunka obsahujúca čiarku potichu premení jednu bunku na dva stĺpce. Pri zlúčených bunkách sa obsah emituje iba pri kotve vľavo hore, takže cyklus cez CSV neduplikuje rozpínajúcu sa hlavičku naprieč stĺpcami, ktoré pokrýva

JSON zachováva Unicode namiesto toho, aby ho escapoval do ASCII, escapuje riadiace znaky a obsahuje metadáta, ktoré spotrebiteľ potrebuje na posúdenie kvality: režim detekcie, spoľahlivosť, hranice, hodnoty rozpätí, príznaky hlavičky a informácie o pokračovaní. Ak posielate extrahované tabuľky do nadväzujúceho systému, uprednostnite JSON, pretože riadok CSV vám nepovie, že tabuľka, z ktorej pochádza, dosiahla spoľahlivosť 0,51:

// Extrakcia v rámci celého dokumentu, ponechávajúca iba tabuľky hodné dôvery
Tables := Pdf.ExtractDocumentTables(Options);
for I := 0 to High(Tables) do
begin
  if Tables[I].Confidence < 0.75 then
  begin
    Log(Format('page %d table needs review (%.2f)',
      [Tables[I].PageNumber, Tables[I].Confidence]));
    Continue;
  end;
  if Tables[I].ContinuationGroup > 0 then
    AppendToGroup(Tables[I].ContinuationGroup, Tables[I].ToJson)
  else
    EmitStandalone(Tables[I].ToJson);
end;

Ladenie a vedieť, kedy prestať

Tri nastavenia záležia viac než ostatné. MinConfidence je brána kvality a 0,5 je zámerne permisívna hodnota; zvýšte ju pre automatizované spracovanie a znížte pre UI na kontrolu, kde človek potvrdzuje každý výsledok. MinColumnGap rozhoduje o tom, čo sa počíta ako hranica stĺpca v režime medzier, a tesne nastavené tabuľky v hustých reportoch ju možno budú potrebovať znížiť z predvolenej hodnoty 12 bodov. MaxRowGapFactor rozhoduje, kedy vertikálna vzdialenosť ukončí tabuľku, čo je dôležité pre tabuľky s občasnými prázdnymi riadkami

Buďte úprimní ohľadom limitov. Tabuľky podľa čiar sa extrahujú spoľahlivo. Čisto zarovnané tabuľky podľa medzier sa extrahujú dobre. Tabuľky s otočeným textom, vnorené tabuľky, alebo bunky, ktorých obsah sa zalomí do niečoho, čo vyzerá ako ďalší riadok, budú potrebovať kontrolu bez ohľadu na to, ako sú nastavené parametre. Pre tie vám model štruktúrovaného textu dáva surový materiál na postavenie čitača špecifického pre danú doménu, popísaného v časti bloky štruktúrovaného textu a poradie čítania

Jedna užitočná kombinácia: keď naskenovaný dokument nemá vôbec žiadny text, detekcia tabuliek nemá s čím pracovať, kým neexistuje textová vrstva. Najprv ju pridajte, ako je popísané v časti pridanie prehľadávateľnej textovej vrstvy do naskenovaných PDF, potom extrahujte. Rámiky slov, ktoré vracia poskytovateľ OCR, sú presne ten vstup, ktorý detekcia podľa medzier potrebuje

Extrakcia tabuliek, štruktúrovaný text a reflow všetky čítajú z toho istého modelu strany v Delphi, C++Builder a Lazarus; úplné API je popísané na stránke komponentu PDFium Component pre Delphi