Utente:Alex brollo/hOCRlab.js

Da Wikisource.

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;
			}
		}
	}
}