Utente:Alex brollo/hOCRlab.js
Nota: dopo aver pubblicato, potrebbe essere necessario pulire la cache del proprio browser per vedere i cambiamenti.
- Firefox / Safari: tieni premuto il tasto delle maiuscole Shift e fai clic su Ricarica, oppure premi Ctrl-F5 o Ctrl-R (⌘-R su Mac)
- Google Chrome: premi Ctrl-Shift-R (⌘-Shift-R su un Mac)
- Internet Explorer / Edge: tieni premuto il tasto Ctrl e fai clic su Aggiorna, oppure premi Ctrl-F5
- Opera: premi Ctrl-F5.
/*
First, routines from User:Alex brollo/OCR.js are imported. These functions upload localStorage.ws_hOCR, t.i. hOCR of a djvu page,
obtained from Phe tool that converts mapped text into hOCR.
To upload localStorage.ws_hOCR of a page, use from console:
alex_do_hocr(name of the page);
e
*/
// new routine to analyze localStorage.ws_hOCR
/* logics:
1. extracts words and adds them to the list mw.pagina.parole;
2. groups words splitting them in lines;
3. builds lines objects;
4. bullds whole text from lines
*/
mw.hocr={};
// input: class,html, output: list of word obiects mw.hocr.htmlParse()
mw.hocr.htmlParse=function (className,html) {
var parola={};
var wordList=[];
var span="";
var coordinate="";
$("."+className,$(html)).each(function () {
span=this.outerHTML;
coordinate= span.match(/bbox (\d+) (\d+) (\d+) (\d+)/);
parola= {
x1:coordinate[1]*1,
y1:coordinate[2]*1,
x2:coordinate[3]*1,
y2:coordinate[4]*1,
text: $(this).text(),
id: $(this).attr("id")
};
// elimino valori anomali da tesseract
if (parola.x1!==0
&& parola.y1!==0
&& parola.x2<mw.pagina.dimensioniPagina.x2
&& parola.y2<mw.pagina.dimensioniPagina.y2) {
wordList.push(parola);
}
});
return wordList;
}
mw.hocr.mainXy = function(lista) {
var X1=lista[0].x1;
var Y1=lista[0].y1;
var X2=lista[0].x2;
var Y2=lista[0].y2;
for (i=0;i<lista.length;i+=1) {
if (lista[i].x1<X1) X1=lista[i].x1;
if (lista[i].y1<Y1)Y1=lista[i].y1;
if (lista[i].x2>X2) X2=lista[i].x2;
if (lista[i].y2>Y2) Y2=lista[i].y2;
}
return {x1:X1, y1:Y1, x2:X2, y2:Y2};
}
mw.hocr.lineBuild=function(parole,numeroLinea) {
if (numeroLinea===undefined) numeroLinea=1;
var gruppiParole=[];
var gruppo=[];
var linee=[];
// splitting word array in groups
for (i=0;i<parole.length-1;i+=1) {
gruppo.push(parole[i]);
if (gruppo.length>0) {
if (parole[i+1].x1 < parole[i].x1 || parole[i+1].y1>parole[i].y2 ) {
gruppiParole.push(gruppo);
gruppo=[];
}
}
}
gruppo.push(parole[i]);
gruppiParole.push(gruppo);
// converting groups of words into line objects
linea={};
//var numeroLinea=1;
for (i=0;i<gruppiParole.length;i+=1) {
linea={};
for (p=0;p<gruppiParole[i].length;p+=1) {
if ($.isEmptyObject(linea)) {
linea={text:gruppiParole[i][p].text,
x1:gruppiParole[i][p].x1,
y1:gruppiParole[i][p].y1,
x2:gruppiParole[i][p].x2,
y2:gruppiParole[i][p].y2};
} else {
linea.text+=" "+gruppiParole[i][p].text;
linea.x1=Math.min(linea.x1,gruppiParole[i][p].x1);
linea.y1=Math.min(linea.y1,gruppiParole[i][p].y1);
linea.x2=Math.max(linea.x2,gruppiParole[i][p].x2);
linea.y2=Math.max(linea.y2,gruppiParole[i][p].y2);
}
}
// assigning a numerical ID to lines and storing line objects into mw.pagina.linee
linea.id=numeroLinea++;
linee.push(linea);
}
// calculating and storing some derivative attributes
for (i=0;i<linee.length;i+=1) {
linee[i].lgap=linee[i].x1-mw.pagina.dimensioniTesto.x1;
linee[i].rgap=mw.pagina.dimensioniTesto.x2-linee[i].x2;
linee[i].height=linee[i].y2-linee[i].y1;
linee[i].width=linee[i].x2-linee[i].x1;
try {linee[i].between=linee[i+1].y1-linee[i].y2;}
catch(err) {}
}
return linee;
}
function analisiPagina() {
mw.pagina={};
mw.pagina.dimensioniPagina=dimElemento("ocr_page");
//hOCR parsing
mw.pagina.parole=mw.hocr.htmlParse("ocrx_word",localStorage.ws_hOCR);
// storing whole text conners to dimensioniTesto
mw.pagina.dimensioniTesto= mw.hocr.mainXy(mw.pagina.parole);
// word to lines
mw.pagina.linee=mw.hocr.lineBuild(mw.pagina.parole,1);
// building a rough whole text from line texts
mw.pagina.testo=estraiLista(mw.pagina.linee,"text").join("\n");
localStorage.ws_hOCR_page=JSON.stringify(mw.pagina);
return mw.pagina.testo;
}
/* altra versione
function estraiLista(lista,campo){
var l=[];
for (i=0;i<lista.length;i+=1) {l.push(lista[i][campo]);}
return l;
}
*/
//importScript("User:Alex brollo/OCR.js");
// funzioni essenziali
/*function datiParola(parola) {
var dati={};
dati.testo=parola.text();
dati.coordinate=parola.attr("title").match(/bbox (\d+) (\d+) (\d+) (\d+)/).splice(1);
return dati;
} */
function linee() {
if ($(".ocr_line",$(localStorage.ws_hOCR)).length===1) {
var parole=$(".ocrx_word",$(localStorage.ws_hOCR));
var testo="";
for (i=0;i<parole.length-1;i+=1) {
testo+=datiParola(parole.eq(i)).testo;
//if (datiParola(parole.eq(i)).coordinate[0]*1>datiParola(parole.eq(i+1)).coordinate[0]*1)
if (datiParola(parole.eq(i)).coordinate[0]*1>datiParola(parole.eq(i+1)).coordinate[0]*1
// if following word has a shorter lgap...
|| datiParola(parole.eq(i+1)).coordinate[3]*1-datiParola(parole.eq(i)).coordinate[3]*1 > dimElemento("ocr_page")[3]/100) {
// ..or y2 is suddenly higher....
testo+="\n";
} else {
testo+=" ";
}
}
testo+=datiParola(parole.eq(i)).testo;
} else testo=pageText();
// a new Detail instance is built into mw.pagina
details();
// mw.pagina is deeperly analyzed
analisi(mw.pagina);
localStorage.ws_pagina=JSON.stringify(mw.pagina);
return testo;
}
// legge localStorage.ws_hOCR e estrae il testo suddiviso in paragrafi
function pageText() {
var testoPagina="";
$(".ocr_par", $(localStorage.ws_hOCR)).each(function() {testoPagina+=textPar($(this));})
return testoPagina;
// riceve un elemento $(".ocr_par",$(localStorage.ws_hOCR)) e restituisce il testo in un blocco
// di linee separate da a capo e con doppio acapo alla fine paragrafo; vale negli elementi suddivisi in aree, paragrafi, linee ecc
}
function textPar(par) {
var testo="";
$(".ocr_line",par).each(function() {
testo+=$.trim($(this).text())+"\n";
});
testo+="\n";
return testo;
}
// Caricamento pagina test
//alex_do_hocr("Pagina:Elogio della pazzia.djvu/30");
// builds something like a page object;
function details() {
mw.pagina={};
mw.pagina.parole=$(".ocrx_word",$(localStorage.ws_hOCR));
mw.pagina.linee=newLinee(mw.pagina.parole);
//hOCR=localStorage.ws_hOCR;
mw.pagina.dimensioniPagina=dimElemento("ocr_page");
/*function () {
var box_pagina=find_stringa(localStorage.ws_hOCR,"<div class='ocr_page'",">");
var coordinate=box_pagina.match(/bbox (\d+ \d+ \d+ \d+)/)[1].split(" ");
return integerConversion(coordinate);
}*/
mw.pagina.dimensioniTesto=dimElemento("ocr_carea");
/*function () {
var box_testo=find_stringa(localStorage.ws_hOCR,"<div class='ocr_carea'",">");
var coordinate=box_testo.match(/bbox (\d+ \d+ \d+ \d+)/)[1].split(" ");
return integerConversion(coordinate);
} */
}
// re-builds line texts matched with whole line coordinates;
function dimElemento(classe) {
var box_testo=find_stringa(localStorage.ws_hOCR,"<div class='"+classe+"'",">");
var coordinate=box_testo.match(/bbox (\d+ \d+ \d+ \d+)/)[1].split(" ");
return {x1:coordinate[0]*1,y1:coordinate[1]*1,x2:coordinate[2]*1,y2:coordinate[3]*1};
}
;
function newLinee(parole) {
var linee=[];
//var coordinate=[];
var coord=[];
var testo="";
datiParolaCorrente={};
for (i=0;i<parole.length-1;i+=1) {
datiParolaCorrente=datiParola(parole.eq(i));
// se è la prima parola di una riga carica le coordinate in coord
if (testo==="") {
coord[0]=datiParolaCorrente.coordinate[0]*1;
coord[1]=datiParolaCorrente.coordinate[1]*1;
coord[2]=datiParolaCorrente.coordinate[2]*1;
coord[3]=datiParolaCorrente.coordinate[3]*1;
if (i<3) console.log(coord.toString()+" "+testo)
}
// altrimenti aggiorna coord
else {
coord[0]=Math.min(coord[0]*1,datiParolaCorrente.coordinate[0]*1);
coord[1]=Math.min(coord[1]*1,datiParolaCorrente.coordinate[1]*1);
coord[2]=Math.max(coord[2]*1,datiParolaCorrente.coordinate[2]*1);
coord[3]=Math.max(coord[3]*1,datiParolaCorrente.coordinate[3]*1);
}
testo+=datiParola(parole.eq(i)).testo;
//coord=datiParola(parole.eq(i)).coordinate;
if (datiParola(parole.eq(i)).coordinate[0]*1>datiParola(parole.eq(i+1)).coordinate[0]*1
// se la parola seguente è più a sinistra
|| datiParola(parole.eq(i+1)).coordinate[3]*1-datiParola(parole.eq(i)).coordinate[3]*1 > 10)
// oppure la parola seguente è bruscamente più bassa, allora la linea è finita
{
linee.push({Id:linee.length, x1:coord[0]*1,y1:coord[1]*1,x2:coord[2]*1,y2:coord[3]*1,testo:testo});
testo="";
coord=[];
} else {
testo+=" ";
}
}
linee.push({Id:linee.length, x1:coord[0]*1,y1:coord[1]*1,x2:coord[2]*1,y2:coord[3]*1,testo:testo});
return linee;
}
function analisi(pagina) {
// page box and text box retrieval and conversion into integers
// coordinates and other values are converted into objects to make content clearer
//var paginaBox=pagina.dimensioniPagina;
//var testoBox=pagina.dimensioniTesto;
//linee=[];
//dati={};
// var linee=pagina.linee;
//var testo="";
for (i=0;i<pagina.linee.length;i+=1){
pagina.linee[i].length=pagina.linee[i].x2-pagina.linee[i].x1;
pagina.linee[i].lgap=pagina.linee[i].x1-pagina.dimensioniTesto[0];
pagina.linee[i].rgap=pagina.dimensioniTesto[2]-pagina.linee[i].x2;
pagina.linee[i].height=pagina.linee[i].y2-pagina.linee[i].y1;
//dati.length=dati.x2-dati.x1;
//dati.lgap=dati.x1-testoBox[0];
//dati.rgap=testoBox[3]-dati.x2;
//dati.height=dati.y2-dati.y1;
//testo+=JSON.stringify(dati);
//linee.push([dati,pagina.linee[i][1]]);
}
return;
}
// extract coordinates of a word
function datiParola(parola) {
var dati={};
dati.testo=parola.text();
dati.coordinate=parola.attr("title").match(/bbox (\d+) (\d+) (\d+) (\d+)/).splice(1);
return dati;
}
// go ahead with lines data analysis calculating:
// lenght
// left indent
// right indent relative to text box
// line height relative to text box
// interline height
// appends data to coordinate list of each line
// The last line have no interline height
function integerConversion(list) {
for (var i=0; i<list.length;i+=1) {
list[i]=list[i]*1;
}
return list
}
// converts a linee object (a list of objects) into a tab separated text of lines, text lines being separated by \n chars
// first line contains a tab separated list of object keys
// resulting text can be exported into Excel by simple paste and copy
// pag is a Detail object
function visualizzaLinee(pag) {
var linee=$.extend([],pag.linee);
var testo="";
var k=[];
var v=[];
for (key in linee[0]) {k.push(key);}
k.sort();
testo+=k.join("\t")+"\n";
for (i=0;i<linee.length;i+=1) {
v=[]
for (j=0;j<k.length;j+=1) {v.push(linee[i][k[j]]);}
testo+=v.join("\t")+"\n";
}
return testo;
}
// unused
function estraiParametro(parametri) {
var l=[];
var ll=[];
for (i=0;i<pagina.linee.length;i+=1) {
ll.push(i);
for (j=0; j<parametri.length;j+=1){
ll.push(pagina.linee[i][parametri[j]]);
}
l.push(ll);
ll=[]
}
return l;
}
function testvSpace(linee) {
var gr=grouped(linee,5,"between",true);
linee=groupsMerge(gr,"vSpace");
return linee;
}
function testIndent(linee) {
var gr=grouped(linee,10,"lgap",false);
linee=groupsMerge(gr, "indent");
return linee;
}
// bozza di funzione raggruppatrice
// restituisce lista suddivisa in n sottoliste in base ai valori di campo;
// se pop è true, elimina l'ultimo elemento perchè si tratta di valori intervallari (es. between)
// esempio: gruppi=grouped(mw.pagina.linee,10,"between",true) raggruppa le linee in base al sottostante spazio interlinea
// gruppi=grouped(mw.pagina.linee,10,"height") raggruppa le linee in base alla loro altezza
function grouped(lista,n,campo,pop) {
if (pop===undefined) pop=false;
var l=$.extend([],lista)
var popped="";// sorting lista
if (pop) popped=l.pop();
var lmax=l[0][campo];
var lmin=l[0][campo];
for (i=1; i<l.length;i+=1) {
if (lista[i][campo] !== undefined) {
if (lmax<l[i][campo]) lmax=l[i][campo];
if (lmin>l[i][campo]) lmin=l[i][campo];
}
}
// calculating step
var step=(lmax-lmin)/n;
// creating h
var h={};
var pos=0;
// initializing
for (i=0;i<n+1;i+=1) {h[i]=[];}
// counting
$.each(l, function (i,v) {
pos=Math.ceil((v[campo]-lmin)/step);
h[pos].push(v);
});
if (pop) h[0].push(popped);
h.step=step;
h.min=lmin;
h.max=lmax;
return h;
}
function groupsMerge(gr,param) {
var gruppi=[];
var vuoto=false;
var n=0;
var i=0;
while (gr[i]) { // for (i=0;i<11;i+=1)
if (gr[i].length>0) { // caso gr corrente non vuoto
vuoto=false;
if (gruppi[n]===undefined) {
gruppi[n]=[].concat(gr[i]);
} else {
gruppi[n]=gruppi[n].concat(gr[i]);
}
} else { // caso gr corrente vuoto
if (! vuoto) {
vuoto=true;
n+=1;
}
}
i+=1;
}
//return gruppi;
for (i=0;i<gruppi.length;i+=1) {
}
var lista=[];
for (i=0;i<gruppi.length;i+=1) {
for (j=0;j<gruppi[i].length;j+=1) {
gruppi[i][j][param]=i;
}
lista=lista.concat(gruppi[i]);
}
lista.sort(function(a,b) {return a.id-b.id;});
return lista;
}
/*********** statistical functions from http://www.endmemo.com/program/js/jstatistics.php ************/
// funzioni: isNum, mean, variance, median, standarDeviation, standardError
//Check whether is a number or not
function isNum(args)
{
args = args.toString();
if (args.length == 0) return false;
for (var i = 0; i<args.length; i++)
{
if ((args.substring(i,i+1) < "0" || args.substring(i, i+1) > "9") && args.substring(i, i+1) != "."&& args.substring(i, i+1) != "-")
{
return false;
}
}
return true;
}
//calculate the mean of a number array
function mean(arr)
{
var len = 0;
var sum = 0;
for(var i=0;i<arr.length;i++)
{
if (arr[i] == ""){}
else if (!isNum(arr[i]))
{
alert(arr[i] + " is not number!");
return;
}
else
{
len = len + 1;
sum = sum + parseFloat(arr[i]);
}
}
return sum / len;
}
function variance(arr)
{
var len = 0;
var sum=0;
for(var i=0;i<arr.length;i++)
{
if (arr[i] == ""){}
else if (!isNum(arr[i]))
{
alert(arr[i] + " is not number, Variance Calculation failed!");
return 0;
}
else
{
len = len + 1;
sum = sum + parseFloat(arr[i]);
}
}
var v = 0;
if (len > 1)
{
var mean = sum / len;
for(var i=0;i<arr.length;i++)
{
if (arr[i] == ""){}
else
{
v = v + (arr[i] - mean) * (arr[i] - mean);
}
}
return v / len;
}
else
{
return 0;
}
}
function median(arr)
{
arr.sort(function(a,b){return a-b});
var median = 0;
if (arr.length % 2 == 1)
{
median = arr[(arr.length+1)/2 - 1];
}
else
{
median = (1 * arr[arr.length/2 - 1] + 1 * arr[arr.length/2] )/2;
}
return median
}
//Standard deviation
function standardDeviation(arr) { return Math.sqrt(variance(arr)); }
//Standard error
function standardError(arr) { return Math.sqrt(variance(arr)/(arr.length-1)); }
// Standard error fraction (standardError/mean)
function standardErrorFraction(arr) { return Math.sqrt(variance(arr)/(arr.length-1))/mean(arr); }
//Intersezione fra intervalli; riceve due liste [da,a]
//restituisce false se non esiste intersezione, l'intervallo di intersezione se esiste
function inters(a,b) {
if (!(a[1]>=b[0] && a[0]<=b[1])) return false;
else return [Math.max(a[0],b[0]),Math.min(a[1],b[1])];
}
function estraiLista(linee,dato,ini,fin) {
var l=[];
if (ini===undefined && fin==undefined) {
ini=0;
fin=linee.length;
}
for (i=ini;i<fin;i+=1) {
l.push(linee[i][dato]);
}
return l;
}
function near(a,b,percent) {
if (percent===undefined) percent=0.01;
var pix=Math.round(mw.pagina.dimensioniPagina.x2*percent);
return Math.abs(a-b)<pix;
}
/* la funzione aggiunge un campo gruppo a ciascuno degli elementi di listaOggetto, che contiene il numero d'ordine
del raggruppamento in base al valore di campo; limite è il valore che distingue una differenza "piccola" da una "grande"
Esempio: raggruppa2(mw.pagina.linee, "lgap",10) classifica le linee in gruppi, a seconda dell'allineamento a sinistra
*/
function raggruppa2(listaOggetti, campo, limite) {
"use strict";
var lista = [], i = 0, j = 0, spl = [];
if (limite === undefined) {limite = 10; }
for (i = 0; i < listaOggetti.length; i += 1) {
lista.push(listaOggetti[i][campo]);
}
lista.sort(function (a, b) {return a - b; });
for (i = 0; i < lista.length - 1; i += 1) {
//console.log(lista[i + 1] - lista[i]);
if (lista[i + 1] - lista[i] > limite) {
spl.push(lista[i]);
}
}
// console.log(JSON.stringify(spl));
for (i = 0; i < listaOggetti.length; i += 1) {
listaOggetti[i].gruppo = spl.length - 1;
for (j = 0; j < spl.length; j += 1) {
//console.log(i+" "+listaOggetti[i][campo]+" "+spl[j]);
if (listaOggetti[i][campo] <= spl[j]) {
listaOggetti[i].gruppo = j;
break;
}
}
}
}