[1] v. partea a VII-a … (v. și I-VI)
În [1]-IV constituisem modelul OCR $TESSDATA_PREFIX/hwr.traineddata
, pe baza unui set [*.png
, *.gt.txt
] cu imagini de cuvinte (și litere), respectiv textul de recunoscut pentru ele; angajasem și o listă "new_words.txt
" cu câte un cuvânt pe fiecare linie, vizând denumiri (unice) de discipline, prenume, sau nume care apar pe orarele claselor. Ulterior (v. [1]-VI), am ordonat această listă cum se cuvine pentru Tesseract: descrescător după frecvența cuvântului; totodată, am mai adăugat câteva exemple de recunoaștere a unor cuvinte — numai că spre deosebire de cuvintele anterioare, pe care le înscrisesem „automat” în fișierele "*.gt.txt
", pentru aceste câteva noi cuvinte am procedat „manual” (cu un editor de text) și nu-i de mirare că apoi (după ce re-antrenasem deja Tesseract), am văzut că unele dintre aceste noi fișiere conțin și câte o linie albă (iar *.gt.txt
trebuie să conțină câte o singură linie, cu textul de recunoscut pe imaginea "*.png
" asociată).
Prin urmare… am luat-o de la capăt, renunțând la "hwr
" și obținând "orr.traineddata
" în care dicționarul cuvintelor este ordonat după frecvență, iar fișierele *gt.txt
sunt corecte… Dar terminând și [1], strădania de a înțelege de ce unele rezultate sunt omise, ne-a făcut să ne tot uităm în urmă; consultând fișierul ".bash_history
", am observat că în comanda "make training
" prin care obținusem "*.traineddata
" (pentru "hwr
", apoi și pentru "orr
") folosisem "--psm 8
" (în loc de valoarea implicită 13
, cel mai potrivită pentru antrenare).
N-ar fi aceasta o problemă: n-avem decât să folosim "make clean MODEL_NAME=orr
" și să reluăm comanda "make training
" dar fără parametrul "--psm
"…
Numai că, obținând fișierul necesar "*.traineddata
" și considerând că rezultatele OCR sunt satisfăcătoare, am șters între timp, definitiv, directorul tesstrain/
(din care, în prezența datelor respective, trebuie lansat "make training
")… Totuși, păstrasem exemplele de recunoaștere în subdirectorul orrGT/
, precum și dicționarul cuvintelor din orare.
A o lua de la capăt, mereu, nu este un obicei tocmai bun… Ne-o fi plăcând, dar puteam evita „pățaniile” evocate mai sus (și altele, întâlnite mai jos), dacă ne obișnuiam și să lucrăm sub controlul sistemului git
(este drept pe de altă parte, că dacă nu este prostească, repetiția este totuși, „mama învățăturii”).
Vom constitui un nou model "cop.traineddata
" — unde "cop" ar aminti "cls|obj|prof
" și de fapt… vine de la "COPy" (că asta facem: „copiem” conținuturi ad-hoc din poze ale orarelor, în scopul de a le organiza ca set coerent de date).
Plecăm de la modelul eng
(varianta "-best") existent deja în $TESSDATA_PREFIX/
; înființăm (prin git clone
) subdirectorul tesstrain/
și instituim subdirectorul exemplelor (copiindu-le din orrGT/
pe care-l constituisem în [1]):
git clone https://github.com/tesseract-ocr/tesstrain.git cd tesstrain/ make tesseract-langdata mkdir -p data/cop-ground-truth cp ../orrGT/* data/cop-ground-truth/
Subliniem că am constituit și fișierul de configurare data/langdata/cop/cop.config
, precizând ce caractere nu sunt necesare pentru a recunoaște imaginile noastre și schimbând față de valorile implicite (dar cam „pe ghicite”), unii coeficienți penalizatori:
tessedit_char_blacklist 0123456789kqwyKQWY\[\{ language_model_penalty_non_dict_word 0.03 segment_penalty_garbage 0.9
Precizăm că Tesseract admite peste 650 de parametri de configurare (pot fi listați prin tesseract --print-parameters
, urmat eventual de "| grep penalty
", de exemplu). Nu știm cât de potrivite sunt valorile alese mai sus (dacă vrem, le putem modifica, reluând apoi antrenarea — dar va fi o experiență cu mici șanse de a se și termina vreodată); aici am vrut doar să exemplificăm o modalitate de configurare.
Cu aceste pregătiri, putem obține acum cop.traineddata
(și-l transferăm în subdirectorul indicat deja de $TESSDATA_PREFIX
):
make training MODEL_NAME=cop START_MODEL=eng \ TESSDATA=$TESSDATA_PREFIX \ PUNC_FILE=~/24Sep/cop.punc \ WORDLIST_FILE=~24Sep/cop.user_words sudo cp data/cop.traineddata $TESSDATA_PREFIX cd ..
cop.traineddata
măsoară $11.2MB$ și eventual, poate fi investigat prin programele combine_tessdata
, wordlist2dawg
, etc. (dar până la urmă, am ajunge firește în impas — știind că nu stăpânim suficient cele necesare, de exemplu despre rețele neuronale).
Deocamdată folosim modelul cop
pentru a extrage textul din celulele PNG corespunzătoare în subdirectorul de lecții LSS/
(v. [1]), uneia dintre clase (după ce, mai întâi constituim lista numelor fișierelor acesteia):
ls LSS/5A* > 5A.tsr # "LSS/5A-0.png", "LSS/5A-1.png", etc.
tesseract 5A.tsr stdout -l cop --psm 6
Am preferat să afișăm pe ecran și să copiem apoi în "5A.txt
" (dacă în loc de "stdout" foloseam "5A", atunci rezulta direct "5A.txt", dar așa informațiile de pagină se rezumau la un separator); formatăm "5A.txt
" încât pentru fiecare celulă să avem câte exact 3 linii:
Page 0 : LSS/5A-0.png # ziua=1, ora=1 (și cls=5A) ed sociala Delia Dascalu Page 1 : LSS/5A-10.png # avem de aici cls|zi|ora cds # obj Adrian Frincu # prof Page 2 : LSS/5A-11.png # obj nerecunoscut (… de ce??) Raluca Crisantha Alexa
Următoarea funcție $\mathbf{R}$ primește numele fișierului text rezultat prin tesseract
și formatat cu câte exact 3 linii consecutive de fiecare pagină și returnează un set de date (de tip data.frame) LSS|obj|prof|zi|ora
; câmpul LSS
precizează clasa și rangul celulei PNG, rang din care prin împărțire la 8, se deduc valorile zi
și ora
:
library(tidyverse) cop2df <- function(cop_txt) { cop <- readLines(cop_txt) nl <- length(cop); if(cop[nl]=="") nl <- nl-1 lss <- str_extract(cop[seq(1,nl,by=3)], "\\/(.*)\\.", group=1) obj <- cop[seq(2,nl,by=3)] prof <- cop[seq(3,nl,by=3)] zo <- as.integer(str_extract(lss, "-(\\d+$)", group=1)) zi <- zo %/% 8 +1; ora <- zo %% 8 +1 data.frame(LSS=lss, obj=obj, prof=prof, zi=zi, ora=ora) %>% arrange(zi, ora) }
Pentru clasa 5A
avem (marcăm cu roșu, eventual și boldat, greșelile existente):
cdf <- cop2df("5A.txt") print(cdf, print.gap=3, row.names=FALSE) LSS obj prof zi ora 5A-0 ed sociala Delia Dascalu 1 1 5A-1 biologie Camelia Alexandriuc 1 2 5A-2 matematica Raluca Crisantha Alexa 1 3 5A-3 engleza Daria Marginean 1 4 5A-4 istorie Mihai Bogdan Dranca 1 5 5A-8 romana lonela-Andreea Sandru 2 1 5A-9 religie Florin Hostiuc 2 2 5A-10 cds Adrian Frincu 2 3 5A-11 ? Raluca Crisantha Alexa 2 4 5A-12 informatica Mihada Cristina ? 2 5 5A-13 cds Nicoleta Bumbu 2 6 5A-16 ed vizuala Adrian Erincu 3 1 5A-17 romana Ionela-Andreea Sandru 3 2 5A-18 ed muzicala Lucian Tablan 3 3 5A-19 i?torie Mihal Bogdan Dranca 3 4 5A-20 consiliere Nicoleta Bumbu 3 5 5A-21 geografie Nicoleta Bumbu 3 6 5A-24 cds Raluca Crisantha Alexa 4 1 5A-25 ed fizica Adrian Cojocaru 4 2 5A-26 germana Camelia Maftei 4 3 5A-27 romana Ionela-Andreea Sandru 4 4 5A-28 matematica Raluca Crisantha Alexa 4 5 5A-32 germana Camelia Maftei 5 1 5A-33 romana lonela-Andreea Sandru 5 2 5A-34 tic ? Hatmanu 5 3 5A-35 matematica Raluca Crisantha Alexa 5 4 5A-36 engleza Daria Marginean 5 5 5A-37 ? Adrian Cojocaru 5 6
Sunt 6 greșeli minore: "$l$" în loc de "$I$" sau $i$, de trei ori; "itorie" în loc de "istorie" și "Mihada" în loc de "Mihaela", respectiv "Erincu" în loc de "Frincu". Probabil, asemenea greșeli „minore” s-ar putea evita, reantrenând Tesseract cu valori mai potrivite pentru anumiți parametri de configurare (dar documentația aferentă parametrilor este tulbure)…
Sunt trei greșeli importante: nu s-a recunoscut obj
pentru 5A-11
și 5A-37
(și nu reușim să vedem motivele); respectiv (din cauza separării pe două linii, în celulele respective), numele unui profesor apare numai cu prenume pentru 5A-12
și numai cu "nume" pentru 5A-34
(subliniem că acest ultim aspect, care decurge din ignorarea din start a faptului că numele este o entitate indivizibilă — este foarte greu de „reparat” altfel decât "manual"…).
Geșelile semnalate sunt totuși ușor de corectat, în mod interactiv; de exemplu:
cdf$prof[cdf$prof %in% c( "Mihada Cristina", "Hatmanu")] <- "Mihaela Cristina Hatmanu"
Dacă apelăm tesseract
nu cu "5A.tsr
", ci cu lista numelor tuturor celor 822 de lecții înregistrate în LSS/
și apoi folosim pentru rezultat, funcția cop2df()
— atunci setul de date notat mai sus "cdf
" ar reprezenta întregul orar al școlii, [cls|obj|prof|zi|ora
] (mai puțin 16 lecții, pe care le reținusem separat — v. [1]); desigur, în acest caz vor fi multe corecturi de făcut… (dar putem concepe niște funcții ajutătoare, în acest scop).
Însă avem deja altă idee de încercat (avansată cumva în [1]): despărțim lecțiile PNG obj|prof
din LSS
în câte două fișiere PNG (imaginea unei linii obj
, respectiv imaginea pentru prof
, „redusă” cumva la o singură linie); alegem (probabil, aleatoriu) un anumit număr de fișiere de fiecare tip (câte o treime ?) și folosim modelul cop
constituit mai sus — obținând (după corecturile necesare) un set de exemple de recunoaștere pentru linii (nu cuvinte individuale și litere, ca în cazul cop
) aferente fie câmpului obj
, fie câmpului prof
.
Apoi antrenăm Tesseract pe baza noului set de exemple, obținând un nou model OCR — cu așteptarea că acesta va recunoaște mai bine, lecțiile cls|obj|prof
din LSS/
(unde în LSS/
am putea avea celulele oricărui orar în care s-a folosit aceeași scriere "de mână" ca în cazul ilustrat aici, măcar disciplinele școlare fiind cam aceleași).
Modelul cop
constituit mai sus este totuși suficient pentru a ajunge „aproape direct”, la setul de date corespunzător orarului de față.
În LSS/
avem deja toate lecțiile obj|prof
, ca imagini PNG $483\times280px$ („celule PNG”); ne-am asigurat anterior că obj
ocupă o singură linie, aflată cam în prima treime a celulei; a doua treime din celulă, dacă nu este goală, reprezintă prenumele de prof
, iar ultima zonă (pe verticală) reprezintă numele de prof
(sau prenume + nume, după caz):
Segmentăm celulele PNG inițiale în câte 3 imagini (fără margini albe, dar cu câte un border alb mic): una pentru obj
, a doua (care poate fi și „vidă”, $1px\times1px$, pe mai puțin de 300 octeți) pentru prenume și a treia pentru numele profesorului (sau prenume + nume, după caz).
Experimentând puțin, am găsit că valoarea potrivită pentru "treime" este $37.5\%$ din înălțimea celulei inițiale (desigur, pentru a 3-a zonă rămâne doar $25\%$); mai jos, vom vedea totuși 6 excepții ("ala" prin care convenisem pentru "antreprenori \n
ala", ocupă mai mult de $37.5\%$).
Prin următoarea secvență de comenzi Bash, copiem cele 822 de celule PNG din LSS/
într-un nou subdirector LSS3/
, apoi prin mogrify
segmentăm în cele trei zone celulele din LSS3/
, apoi eliminăm dintre imaginile rezultate pe cele „vide”, a căror mărime este mai mică decât 300 octeți, iar pe imaginile rămase (folosind iarăși mogrify
) eliminăm marginile (prin "-trim") și adăugăm un border alb (de $10px$):
mkdir -p LSS3 cp LSS/*.png LSS3/ mogrify +repage -crop 100%x37.5% +repage LSS3/*.png find LSS3/ -type f -size -300c -delete mogrify -trim +repage -bordercolor white -border 10 LSS3/*.png
Subliniem că border-ul adăugat nu este lipsit de importanță pentru Tesseract și dacă ne luăm „după surse”, ar trebui să fie cam o cincime din "x-height" (înălțimea literei "x"); $10px$ pare suficient și pentru obj
(care este scris cu litere mai mari decât cele din prof
).
Înregistrăm lista numelor fișierelor rezultate în LSS3
(în număr de 1829) și aplicăm tesseract
(cu PSM 13) acestei liste:
ls LSS3/*.png > L3.ls # LSS3/5A-0-0.png, LSS3/5A-0-2.png, etc. tesseract L3.ls lss -l cop --psm 13 # produce "lss.txt" sed -i 's/\x0c//' lss.txt # elimină separatorii de pagină
De data aceasta, în loc de "stdout" am indicat "lss": pe ecran se afișează informațiile "Page: ..." curente, iar rezultatele recunoașterii sunt înscrise în fișierul lss.txt
, în care paginile sunt separate între ele prin caracterul "end of text" 0x0c
— caractere pe care le-am eliminat în final, folosind sed
.
Pentru fiecare fișier numit în lista L3.ls
, se afișează pe ecran (sub "Page:") numele respectiv și se adaugă în lss.txt
textul recunoscut pentru acel fișier PNG; altfel spus, L3.ls
indexează liniile din lss.txt
și putem combina informațiile (analog funcției cop2df()
de mai sus):
library(tidyverse) page <- readLines("L3.ls") # fișierul curent text <- readLines("lss.txt") # textul recunoscut pe fișierul curent c_rang <- str_extract(page, "\\/(.*)\\.", group=1) rang <- str_extract(c_rang, "-(\\d+)-", group=1) %>% as.integer() zi <- rang %/% 8 +1 ora <- rang %% 8 +1 Zile <- c("Lu", "Ma", "Mi", "Jo", "Vi") orar <- data.frame(cls=c_rang, ocr=text, zi=zi, ora=ora, png=c_rang) orar <- orar %>% mutate(cls=str_extract(cls, "(^\\w*)", group=1), zi=factor(zi, labels=Zile, ordered=TRUE)) %>% arrange(cls, zi, ora) saveRDS(orar, "orar.RDS")
Am obținut structura de date orar
(de tip "data.frame") și am salvat pe disc (în "orar.RDS
") datele respective, permițând prelucrări ulterioare (prin alte programe $\mathbf{R}$); am păstrat (nu numai pentru verificări !) și numele fișierelor PNG din care provin datele cls|ocr|zi|ora
, unde "ocr
" desemnează textul recunoscut (obj
, prenume, sau prof
) de pe fișierul respectiv.
Un exemplu simplu de „prelucrare ulterioară” ar fi afișarea unui eșantion aleatoriu de înregistrări din orarul curent salvat în orar.RDS
:
library(tidyverse) orr <- readRDS("orar.RDS") print(slice_sample(orr, n=5), row.names=FALSE) cls ocr zi ora png 10E Roxana Valeria Vi 2 10E-33-1 11D biologie Ma 7 11D-14-0 12D Cristian Amoraritei Jo 3 12D-26-2 8A ed sociala Vi 6 8A-37-0 11E Sorin Tanase Lu 3 11E-2-2
În fișierul LSS3/10E-33-1.png
vedem într-adevăr, valoarea indicată pe prima linie în câmpul ocr
; partea finală "-1
" din numele fișierului arată că avem de-a face cu prenumele (iar numele prof
îl găsim în "LSS3/10E-33-2"); partea -33
arată ziua ($33/8=4$. deci ziua "Vi
") și ora ($33\mod 8=1$, deci ora=2
). Analog putem verifica, alte linii de date.
Numele de fișiere din coloana "png
" servesc și pentru eventualitatea că am vrea fișiere "*gt.txt
", (pentru a constitui un nou model OCR); de exemplu, pentru prima linie de mai sus creem fișierul "11E-33-1.gt.txt
" (asociat lui 10E-33-1.png
), înscriindu-i valoarea din câmpul ocr
al liniei respective (valoare care în cazul de față nu necesită corecturi).
Să depistăm disciplinele școlare obj
care au fost incorect recunoscute.
Întâi, ținem seama de faptul că valorile "obj
" sunt cele din câmpul ocr
aflate pe liniile de date în care valorile png
se termină cu "-0
" și ne amintim că în fișierul "cop.user-words
" înscrisesem toate denumirile de discipline (de fapt… cuvintele componente):
cr_obj <- orr %>% filter(str_ends(png, "-0")) %>% select(ocr) %>% unique() words <- readLines("cop.user-words") print(setdiff(cr_obj$ocr, words)) # recunoscut, dar neaflat în dicționar [1] "viz/muz" "ed fizica" "nta." "fiaica" [5] "nla" "nla." "spanicla" "itorie" [9] "ed vizuala" "spaniocla" "ed sociala" "ed muzicala" [13] "ed mugicala" "ed mugzgicala" "geemana" "biotogie"
În dicționarul pe care-l constituisem în [1] avem cuvinte ca "viz", muz", "ed, "biologie" etc., dar nu și denumiri complete (și „corecte”) ca "viz/muz", sau "ed fizica".
Denumirile incorecte sunt doar cele marcate cu roșu, iar dintre acestea unele au corecturi evidente ("geemana"="germana", etc.) și singurele care trebuie totuși investigate sunt cele de la indecșii 3, 5 și 6 (regăsite eventual de mai multe ori, în orr
):
orr %>% filter(ocr %in% c("nta.", "nla", "nla.")) %>% print() cls ocr zi ora png 1 10B nta. Ma 3 10B-10-0 2 10C nta. Lu 7 10C-6-0 3 10D nla Vi 4 10D-35-0 4 10E nla. Vi 1 10E-32-0 5 10F nta. Lu 1 10F-0-0
Consultând LSS/10B-10.png
înțelegem despre ce este vorba:
> orr %>% filter(cls=="10B", zi=="Ma", ora==3) cls ocr zi ora png 1 10B nta. Ma 3 10B-10-0 2 10B uain Ma 3 10B-10-1 3 10B Irina Geanina Harja Ma 3 10B-10-2
Pe celulele inițiale, disciplina "Ed. antreprenorială" figura pe două rânduri: "antreprenori" și dedesubt, "ala"; fiind singura situație de acest fel, am tratat-o ad-hoc: am păstrat numai rândul al doilea — iar ulterior n-am sesizat că zona celulei care îl conține ocupă mai mult de $37.5\%$ din celulă, încât rândul respectiv a fost segmentat în "10B-10-0.png
" (de pe care s-a recunoscut "nta.
") și "10B-10-1.png
" (de pe care s-a recunoscut "uain
"), cum se vede pe imaginea și datele redate mai sus.
Să observăm însă că pentru clasa 10A
, "ala" a fost recunoscut (corect):
> orr %>% filter(cls=="10A", ocr=="ala") cls ocr zi ora png 1 10A ala Ma 6 10A-13-0
În LSS3/10A-13-0.png
, "ala" este urcat ceva mai sus în celulă (față de celelalte 5 cazuri redate mai sus), încât Tesseract a reușit totuși, să recunoască literele respective.
Dacă vrem, putem s-o „luăm de la capăt” — re-segmentând (cu $40\%$ sau chiar $50\%$, în loc de $37.5\%$) cele 6 celule cu "ala"… Preferăm să corectăm direct:
wh <- which(with(orr, ocr %in% c("nta.", "nla", "nla."))) orr[wh, "ocr"] <- "ala" orr[wh, "png"] <- "10A-13-0" orr <- orr[-(wh+1), ] saveRDS(orr, "orar.RDS")
În vectorul wh
am obținut prin which
, indecșii liniilor din orr
corespunzătoare celor 5 cazuri în care trebuia recunoscut "ala"; pe aceste linii am înlocuit cu "ala" valoarea greșită din câmpul ocr
și cu "10A-13-0
" valoarea png
(am văzut mai sus, că "10A-13-0.png
" este „citit” corect, "ala"). Apoi am ținut seama de faptul că orr
fusese ordonat după cls|zi|ora
, ceea ce înseamnă că liniile cu indecșii imediat următori celor din vectorul wh
(rezultați prin (wh+1)
) reprezintă porțiunile inferioare ale literelor "ala", deci am "șters" liniile respective.
În final, am salvat pe disc setul curent orr
, înlocuind vechiul "orar.RDS
".
Dar să observăm că ne-a scăpat ceva: în plus față de cele 5 linii indexate de (wh+1)
, pe care png
se termină cu "-1
" — trebuie ștearsă și linia corespunzătoare clasei 10A
, adică linia pe care în pgn
apare "10A-13-1
"; aflăm indexul acesteia și o "ștergem" din orr
:
> orr[orr$png=="10A-13-1", ] cls ocr zi ora png 26 10A Raaaad Ma 6 10A-13-1 > orr <- orr[-26, ] # "șterge" linia de index 26 > saveRDS(orr, "orar.RDS")
Următoarea funcție simplifică „repararea” valorilor orr$ocr
greșite; fiindcă orr
este în exteriorul funcției, folosim "<<-
" (operatorul de "atribuire globală"):
replace_ocr <- function(old, new) orr$ocr[orr$ocr==old] <<- new
De exemplu, pentru disciplinele greșit recunoscute care ne-au rămas în orr
:
derr <- c("fiaica", "spanicla", "spaniocla", "itorie", "ed mugicala", "ed mugzgicala", "geemana", "biotogie") dcor <- c("fizica", rep("spaniola", 2), "istorie", rep("ed muzicala", 2), "germana", "biologie") for(i in 1:length(derr)) replace_ocr(derr[i], dcor[i]) saveRDS(orr, "orar.RDS")
Textele din fișierele LSS3/*-1.png
și LSS3/*-2.png
conțin două sau trei cuvinte (separate prin spațiu). Următoarea funcție primește ca argument "-1
", sau "-2
" și returnează un vector care conține toate valorile ocr
(distincte între ele) de pe liniile cu "png
" terminat în "-1
", respectiv "-2
", pentru care măcar unul dintre cuvintele componente nu se află în lista cop.user-words
:
orr <- readRDS("orar.RDS") # orarul curent words <- readLines("cop.user-words") diff_ocr <- function(at_end="-1") { v_ocr <- orr %>% filter(str_ends(png, at_end)) %>% pull(ocr) %>% unique() for(i in 1:length(v_ocr)) { vi <- strsplit(v_ocr[i], " ") %>% unlist() if(all(vi %in% words)) v_ocr[i] <- "" # Nu necesită corecturi } v_ocr[v_ocr != ""] # valorile "ocr" care trebuie corectate }
Astfel, prenumele care au fost greșit recunoscute de pe celulele PNG sunt:
> ocr1 <- diff_ocr("-1") [1] "Gabriela Adeiana" "Marilena cristina" "‘Roxana Valeria" [4] "Mihaela cristina" "Reoxana Valeria" "Mchaela Cristina" [7] "Miheaela Cristina" "Elena Carnen" "Elna Carmen"
Pentru corectare, edităm vectorul ocr1
rezultat mai sus (de exemplu, prin edit(ocr1, editor="gedit")
) și reținem (cum se vede, am avut de corectat numai câte o literă):
> ocr1k <- c("Gabriela Adriana", "Marilena Cristina", "Roxana Valeria", "Mihaela Cristina", "Roxana Valeria", "Mihaela Cristina", "Mihaela Cristina", "Elena Carmen", "Elena Carmen")
Folosind cei doi vectori „paraleli”, putem corecta acum datele din orr
:
for(i in 1:length(ocr1)) replace_ocr(ocr1[i], ocr1k[i]) saveRDS(orr, "orar.RDS")
Ne-a rămas să „reparăm” valorile din ocr
corespunzătoare câmpului prof
:
> ocr2 <- diff_ocr("-2") [1] "Carelia Alexandriuc" "BE/cCA" [3] "Simona Sefroni" "loredana Epure" [5] "BE/CA" "'Alexandra Pascariu" [7] "BE /CA" "Florin Hostiue" [9] "Frincus/Tablan" "Aduian Cojocaru" ... [157] "Raluea Crisantha Alexa" "Mariana Sarin Giosan" [159] "Aduiian Petrisor" "lonela-Andseea Sandsu" [161] "Adusian Cojocaru" "Flovin ioan Cojocaru" [163] "Florin loan Cojecaru" "BE / MD" ... [191] "Adeian Cojocaru" "Mara Morotan"
De data aceasta avem multe corecturi de făcut, dar numai de câte una-două litere, ușor de ghicit; numai în câteva cazuri, pentru a nu greși numele, ar trebui să consultăm lista cop.user-words
sau dacă aceasta nu ajunge, să afișăm orr %>% filter(ocr=="L Gman")
de exemplu — pentru a vedea valoarea din câmpul png
al liniei (sau liniilor) afișate și apoi, fișierul PNG respectiv din LSS3/
(văzând astfel valoarea corectă, în loc de "L Gman
" — anume… "Cojocaru" !). Observând celula PNG respectivă, avem explicația obișnuită a surprizei: pe imaginea respectivă au rămas trasate în partea superioară, niște urme ale liniilor inițiale, neuniforme, dintre celule; dacă ne străduiam mai mult să „curățăm” imaginile din LSS3/
, atunci n-am mai fi avut asemenea cazuri de interpretare surprinzătoare.
După ce terminăm de editat vectorul ocr2
, refolosim secvența de corectare pe care am redat-o mai sus (în cazul ocr1
) și ne rezultă astfel, setul de date final "orar.RDS
".
Obs. 1. Fiindcă am editat manual vectorul ocr2
, tot au mai rămas câteva corecturi de făcut (cum constatăm în final, relansând diff_ocr("-2")
); de exemplu, ne-a scăpat să ștergem apostroful inițial în "'Adrian Frincu" — corectăm ad-hoc, prin replace_ocr("'Adrian Frincu", "Adrian Frincu")
.
2. Credeam că în cop.user-words
am înregistrat toate cuvintele din orarele inițiale ale claselor — dar acum constatăm că nu este așa: în vectorul ocr2
rămâne de exemplu "Gabriela Nenec", fiindcă "Nenec" nu apare în "words"; pentru siguranță, investigăm prin orr %>% filter(ocr=="Gabriela Nenec")
și ne uităm în fișierul PNG indicat în câmpul png
al liniei afișate (constatând că într-adevăr, apare "Nenec").
3. În words înregistrasem și „cuvinte” ca "AS/
MC", iar acestea au fost în general greșit recunoscute (și rămân în ocr2
); corect era să fi ținut seama că inclusesem "/
" drept „semn de punctuație” (în fișierul "cop.punc
", care trebuie să fie prezent, pe lângă "cop.user-words
") și să înregistrăm în words cuvintele "AS" și "MC". Ca urmare, acum trebuie să investigăm (și eventual să corectăm) liniile din orr
pe care în câmpul ocr
apar cuvinte ca "AS/MC"…
În concluzie, recunoașterea textului reușea mult mai bine (și am fi avut ulterior, mult mai puțin de muncă) dacă: 1) verificam mai atent cât de „curate” sunt imaginile din LSS3/
(folosind meniul "Draw" din display
— v. [1] — pentru a elimina eventualele urme rămase pe celulele PNG respective);
2) verificam ca fișierul "cop.user-words
" să aibă conținutul corect și să fie complet.
Obținând "orar.RDS
", putem zice că am terminat cu Tesseract (…rămâne în aer, ideea de a constitui un nou model .traineddata
, antrenându-l pe un set de valori dintre cele înregistrate în câmpurile ocr
și png
de pe câte o aceeași linie din setul orr
).
Să observăm acum că organizarea datelor din orar.RDS
este defectuoasă: coloana ocr
conține și valori ale variabilei obj
și valori de prof
; de exemplu:
> orr %>% filter(cls=="5A", zi=="Vi") cls ocr zi ora png 1 5A germana Vi 1 5A-32-0 2 5A Camelia Maftei Vi 1 5A-32-2 3 5A romana Vi 2 5A-33-0 4 5A Ionela-Andreea Sandru Vi 2 5A-33-2 5 5A tic Vi 3 5A-34-0 6 5A Mihaela Cristina Vi 3 5A-34-1 7 5A Hatmanu Vi 3 5A-34-2 8 5A matematica Vi 4 5A-35-0 9 5A Raluca Crisantha Alexa Vi 4 5A-35-2 10 5A engleza Vi 5 5A-36-0 11 5A Daria Marginean Vi 5 5A-36-2 12 5A ed fizica Vi 6 5A-37-0 13 5A Adrian Cojocaru Vi 6 5A-37-2
Iar uneori, prof
este reprezentat pe două linii consecutive — ca mai sus pe liniile 6 și 7, pentru obiectul "tic
".
Reținem în orr0
liniile din orr
corespunzătoare disciplinelor — cele în care valoarea din câmpul "png
" se termină cu "-0
"; fie orr2
setul liniilor din orr
neincluse în orr0
:
orr0 <- orr %>% filter(str_ends(png, "-0")) orr2 <- anti_join(orr, orr0) # png se termină cu "-1" sau "-2"
Fie pren_rows
vectorul indecșilor de linii din orr2
pentru care valoarea din câmpul png
se termină cu "-1
"; dacă row
∈ pren_rows
, atunci orr2[row, 2]
conține prenumele, iar orr2[row+1, 2]
conține numele profesorului curent — îmbinăm prenumele și numele, pe linia row+1
(în câmpul 2, corespunzător variabilei "ocr
" din orr2
):
pren_rows <- which(with(orr2, str_ends(png, "-1"))) for(row in pren_rows) { pren <- orr2[row, 2] prof <- paste(pren, orr2[row+1, 2]) orr2[row+1, 2] <- prof }
Eliminăm din orr2
liniile corespunzătoare prenumelor; orr2
rămâne astfel cu același număr de linii ca și orr0
, iar liniile sunt în aceeași ordine: profesorului de pe linia de rang j din orr2
îi corespunde disciplina de pe linia de rang j din orr0
. Instituim în setul orr2
coloana obj
, copiind disciplinele din coloana ocr
a lui orr0
; apoi, în orr2
adoptăm numele prof
, în loc de vechiul nume "ocr
":
orr2 <- orr2[-pren_rows, ] orr2$obj <- orr0$ocr orr2 <- orr2 %>% rename(prof=ocr)
În final, eliminăm câmpul "png
" (nu are de ce să ne mai intereseze), adoptăm numele de linie obișnuite (liniile rămase după ignorarea celor indicate de pren_rows
păstrau numele de linie inițiale) și salvăm pe disc setul orr2
, în orar_2.RDS
:
orr2$png <- NULL rownames(orr2) <- NULL saveRDS(orr2, "orar_2.RDS")
Acum, setul de date orr2
reprezintă orarul în „forma normală” (fiecare coloană conține valori ale unei aceleiași variabile, iar cele 5 variabile sunt independente între ele; în plus, liniile sunt indexate obișnuit și nu prin indecși moșteniți):
> glimpse(orr2) Rows: 822 Columns: 5 $ cls <chr> "10A", "10A", "10A", "10A", "10A", "10A", "1… $ prof <chr> "Camelia Alexandriuc", "BE/CA", "Lucian Lungu", "Cri… $ zi <ord> Lu, Lu, Lu, Lu, Lu, Lu, Lu, Ma, Ma, Ma, Ma, Ma, M… $ ora <dbl> 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6,… $ obj <chr> "biologie", "engleza", "fizica", "matematica", "psih…
Bineînțeles că pentru ora
ne-ar conveni să avem valori de tip int
, în loc de "double" (care ocupă de cel puțin două ori, mai multă memorie); dar oricum vom ignora în cele din urmă, variabilele curente zi
și ora
…
De observat că iar am uitat: trebuie să adăugăm în orr2
și cele 16 lecții pe care le reținusem separat în [1]; bineînțeles că între timp am rătăcit însemnările respective, dar știm clasele (9B
și 10B
pe "informatică" și 9E
pe "franceză") și ne vom uita în fișierele PDF inițiale, pentru celulele colapsate orizontal câte două sau câte trei.
[ Apropo. Cum se vede (că de!… o luăm mereu "de la capăt"), ne place să repetăm și…
repetăm: obiceiul (facilitate chipurile, oferită de Excel) de a colapsa vizual pe un tabel de date, nu are de-a face, nicidecum, cu știința datelor, ci este un nărav funcționăresc prost, dar tipic: încurcă lucrurile și te pune pe drumuri. ]
Acum putem investiga datele respective (convenind în prealabil asupra unor codificări) și putem constata în ce măsură sunt respectate anumite principii general valabile pentru un orar școlar; apoi, putem anula câmpurile zi
și ora
, pentru a constitui pe datele respective (asumând consecvent anumite principii de repartizare a lecțiilor) un nou orar (important fiind, subliniem, nu orarul ca atare, ci logica / procesul de constituire a acestuia).
vezi Cărţile mele (de programare)