package com.akjava.gwt.lib.client.experimental.lbp;
import static com.google.common.base.Preconditions.checkState;
import java.util.List;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
public class SimpleLBP {
public static final int NORTH_WEST=0;
public static final int NORTH=1;
public static final int NORTH_EAST=2;
public static final int EAST=3;
public static final int SOUTH_EAST=4;
public static final int SOUTH=5;
public static final int SOUTH_WEST=6;
public static final int WEST=7;
private static final String[] direction={"NW","N","NE","E","SE","S","SW","W"};
private static int[] atx={-1,0,1, 1,1,0, -1,-1};
private static int[] aty={-1,-1,-1,0,1,1, 1,0};
/**
* maybe 8samples 1 neighbors
*
* 1 pixel edges should be ignore
* @param useImprovedLBP
*/
public SimpleLBP(){
this(true,1,false);
}
public SimpleLBP(boolean improved){
this(improved,1,false);
}
public SimpleLBP(boolean improved,int neighbor){
this(improved,neighbor,false);
}
public SimpleLBP(boolean improved,boolean useNumber){
this(improved,1,useNumber);
}
/**
*
* @param improved return average value as center
* @param useNumber return 0-9 value instead of 0-255(I believe it's better to use small range to machine learning)
*/
public SimpleLBP(boolean improved,int neighbor,boolean useNumber){
this.useImprovedLBP=improved;
this.neighbor=neighbor;
this.useNumber=useNumber;
container=new int[3][3];
}
private int neighbor;
private int[][] container;
boolean useImprovedLBP=true; //use center value is around average
boolean useNumber;//return 0-9 value.
//i think default 0-256 color is for human eye
//x,y watch out
public int[][] convertXY(int[][] arrays){
int result[][]=new int[arrays.length][arrays[0].length];
for(int x=0;x<arrays.length;x++){
for(int y=0;y<arrays[0].length;y++){
String value="";
int number=0;
int[][] centers=getAroundValuesXY(x,y,arrays);
int center=getCenterValue(centers);
//check
for(int i=0;i<atx.length;i++){
int offx=1+atx[i];
int offy=1+aty[i];
int otherValue=0;
if(neighbor==1){
otherValue=centers[offx][offy];
}else{
otherValue=getOtherValue(arrays, x+atx[i]*neighbor,y+ aty[i]*neighbor);
}
if(otherValue>center){
//System.out.println("at "+x+","+y+","+otherValue+">"+center+" add "+direction[i]);
value+="1";
number++;
}else{
value+="0";
}
}
//LogUtils.log(x+","+y+","+value);
if(useNumber){
result[x][y]=number;
}else{
result[x][y]=Integer.parseInt(value, 2);
}
}
}
return result;
}
/**
*
* @param arrays arrays must be [y][x], this is easy to debug
* @return [y][x] ints
*/
public int[][] convert(int[][] arrays){
int result[][]=new int[arrays.length][arrays[0].length];
for(int y=0;y<arrays.length;y++){
for(int x=0;x<arrays[0].length;x++){
String value="";
int number=0;
int[][] centers=getAroundValues(x,y,arrays);
int center=getCenterValue(centers);
//check
for(int i=0;i<atx.length;i++){
int offx=1+atx[i];
int offy=1+aty[i];
int otherValue=0;
if(neighbor==1){
otherValue=centers[offy][offx];
}else{
otherValue=getOtherValue(arrays, x+atx[i]*neighbor,y+ aty[i]*neighbor);
}
if(otherValue>center){
//System.out.println("at "+x+","+y+","+otherValue+">"+center+" add "+direction[i]);
//value+="1";
value="1"+value;
number++;
}else{
//value+="0";
value="0"+value;
}
}
//LogUtils.log(x+","+y+","+value);
if(useNumber){
result[y][x]=number;
}else{
result[y][x]=Integer.parseInt(value, 2);
}
}
}
return result;
}
public int[][] convertAverageValueForImprovedLBPTest(int[][] arrays){
int result[][]=new int[arrays.length][arrays[0].length];
for(int y=0;y<arrays.length;y++){
for(int x=0;x<arrays[0].length;x++){
int[][] centers=getAroundValues(x,y,arrays);
int center=getCenterValue(centers);
result[y][x]=center;
}
}
return result;
}
public static String toBinaryPatternToDebug(int[] pattern,int splitX,int splitY){
List<String> lines=Lists.newArrayList();
checkState(pattern.length==splitX*splitY*8);
int index=1;
for(int y=0;y<splitY;y++){
for(int x=0;x<splitX;x++){
List<String> result=Lists.newArrayList();
int offset=(x+y*splitX)*8;
for(int i=0;i<8;i++){
int v=pattern[offset+i];
if(v>0){
result.add(direction[i]+"="+v);
}
}
lines.add(index+" block:"+x+"x"+y+" "+Joiner.on(",").join(result));
index++;
}
}
return Joiner.on("\n").join(lines);
}
public static String toDirectionLabelForDebug(int value){
List<String> result=Lists.newArrayList();
String v=Strings.padStart(Integer.toBinaryString(value),8,'0');
for(int i=8-1;i>=0;i--){
char ch=v.charAt(i);
if(ch=='1'){
result.add(Strings.padStart(direction[7-i],2,' '));
}
}
if(result.isEmpty()){
result.add(" 0");
}
return Joiner.on(":").join(result);
}
/*
* basically simple-lbp support only 8 samples
* right now 8 bit fixed
*/
public static int[] flipHorizontal(int[] binaryPattern,int splitW,int splitH){
checkState(binaryPattern.length==8*splitW*splitH);
int[] converted=new int[binaryPattern.length];
for(int y=0;y<splitH;y++){
for(int x=0;x<splitW;x++){
int srcOffset=(y*splitW+x)*8;
int destOffset=(y*splitW+(splitW-1-x))*8;
for(int i=0;i<8;i++){
int nindex=0;
switch(i){
case SimpleLBP.NORTH_WEST:
nindex=SimpleLBP.NORTH_EAST;
break;
case SimpleLBP.NORTH:
nindex=SimpleLBP.NORTH;
break;
case SimpleLBP.NORTH_EAST:
nindex=SimpleLBP.NORTH_WEST;
break;
case SimpleLBP.EAST:
nindex=SimpleLBP.WEST;
break;
case SimpleLBP.SOUTH_EAST:
nindex=SimpleLBP.SOUTH_WEST;
break;
case SimpleLBP.SOUTH:
nindex=SimpleLBP.SOUTH;
break;
case SimpleLBP.SOUTH_WEST:
nindex=SimpleLBP.SOUTH_EAST;
break;
case SimpleLBP.WEST:
nindex=SimpleLBP.EAST;
break;
}
converted[destOffset+nindex]=binaryPattern[srcOffset+i];
}
}
}
return converted;
}
public static double[] flipHorizontal(double[] binaryPattern,int splitW,int splitH){
checkState(binaryPattern.length==8*splitW*splitH);
double[] converted=new double[binaryPattern.length];
for(int y=0;y<splitH;y++){
for(int x=0;x<splitW;x++){
int srcOffset=(y*splitW+x)*8;
int destOffset=(y*splitW+(splitW-1-x))*8;
for(int i=0;i<8;i++){
int nindex=0;
switch(i){
case SimpleLBP.NORTH_WEST:
nindex=SimpleLBP.NORTH_EAST;
break;
case SimpleLBP.NORTH:
nindex=SimpleLBP.NORTH;
break;
case SimpleLBP.NORTH_EAST:
nindex=SimpleLBP.NORTH_WEST;
break;
case SimpleLBP.EAST:
nindex=SimpleLBP.WEST;
break;
case SimpleLBP.SOUTH_EAST:
nindex=SimpleLBP.SOUTH_WEST;
break;
case SimpleLBP.SOUTH:
nindex=SimpleLBP.SOUTH;
break;
case SimpleLBP.SOUTH_WEST:
nindex=SimpleLBP.SOUTH_EAST;
break;
case SimpleLBP.WEST:
nindex=SimpleLBP.EAST;
break;
}
converted[destOffset+nindex]=binaryPattern[srcOffset+i];
}
}
}
return converted;
}
public static String toBinaryForDebug(int value){
String v=Integer.toBinaryString(value);
while(v.length()<8){
v="0"+v;
}
Strings.padStart(v, 8, '0');
return v;
}
/**
* binary pattern
*
* has int[8] x splitX x splitY
* Y first,first 8 byte is X:Y 0:0,second is 0:1,third is 1:0,forth is 1:1
*
* 8byte[] is first one[0] is W ,last[7] is NW
*
* I'm not sure why this is.
*
* maybe should change TODO
* X first ,NW to W
*
* @param arrays
* @param edgeX
* @param edgeY
* @return
*/
//split is fixed;
public int[] dataToBinaryPattern(int[][] arrays,int edgeX,int edgeY){
return dataToBinaryPattern(arrays,2,2,edgeX,edgeY);
}
/**
*
* @param arrays
* @param splitW must be possible divide (arrays[0].length-edgeX)
* @param splitH must be possible divide (arrays.length-edgeY)
* @param edgeX
* @param edgeY
* @return
*/
public int[] dataToBinaryPattern(int[][] arrays,int splitW,int splitH,int edgeX,int edgeY){
//int split=2;
int w=arrays[0].length;
int h=arrays.length;
int resultW=(w-edgeX)/splitW;
int resultH=(h-edgeY)/splitH;
int[] retInt=new int[8*splitW*splitH];
int halfEdgeX=edgeX/2;
int helfEdgeY=edgeY/2;
//ignore edge
for(int x=halfEdgeX;x<w-halfEdgeX;x++){
for(int y=helfEdgeY;y<h-helfEdgeY;y++){
int[][] centers=getAroundValues(x,y,arrays);
int center=getCenterValue(centers);
int retX=(x-halfEdgeX)/resultW;
int retY=(y-helfEdgeY)/resultH;
int retIndexOffset=8*(retY*splitW+retX);
//System.out.println("x="+x+",y="+y+",retX="+retX+",retY="+retY);
//check
for(int i=0;i<atx.length;i++){
int offx=1+atx[i];
int offy=1+aty[i];
int otherValue=0;
if(neighbor==1){
otherValue=centers[offy][offx];
}else{
otherValue=getOtherValue(arrays, x+atx[i]*neighbor,y+ aty[i]*neighbor);
}
if(otherValue>center){
retInt[i+retIndexOffset]++;
}
}
}
}
return retInt;
}
private int getOtherValue(int[][] arrays, int offx, int offy) {
if(offy<0 || offy>=arrays.length || offx<0 || offx>=arrays[0].length){
//if(offx<0 || offx>=arrays.length || offy<0 || offy>=arrays[0].length){
return -1;
}
return arrays[offy][offx];
}
public int[] count(int[][] arrays){
int result[]=new int[8];
for(int x=0;x<arrays.length;x++){
for(int y=0;y<arrays.length;y++){
int[][] centers=getAroundValues(x,y,arrays);
int center=getCenterValue(centers);
//check
for(int i=0;i<atx.length;i++){
int offx=1+atx[i];
int offy=1+aty[i];
int otherValue=centers[offx][offy];
if(x!=0 && y!=0&& x!=arrays.length-1 && y!=arrays[0].length-1){//ignore edge.no means?
if(otherValue>center){
result[i]++;
}else{
}
}
}
}
}
return result;
}
private int getCenterValue(int[][] around){
if(useImprovedLBP){
int total=0;
int exists=0;
for(int x=0;x<3;x++){
for(int y=0;y<3;y++){
if(around[y][x]>=0){//-1 means out
total+=around[y][x];
exists++;
}
}
}
return total/exists;//improved LBP average;
}else{
return around[1][1];//just return center;
}
}
/**
*
* @param tx
* @param ty
* @param arrays MUST be [y][x] value for easy debug
* @return
*/
private int[][] getAroundValues(int tx,int ty,int[][] arrays){
for(int i=0;i<atx.length;i++){
container[1][1]=arrays[ty][tx];
int offx=tx+atx[i];
int offy=ty+aty[i];
if(offx>=0 && offy>=0 && offx<arrays[0].length&&offy<arrays.length){
container[1+aty[i]][1+atx[i]]=arrays[offy][offx];
}else{
container[1+aty[i]][1+atx[i]]=-1;//null
}
}
return container;
}
/*
* return [X][Y] ints
*/
private int[][] getAroundValuesXY(int tx,int ty,int[][] arrays){
for(int i=0;i<atx.length;i++){
container[1][1]=arrays[tx][ty];
int offx=tx+atx[i];
int offy=ty+aty[i];
if(offx>=0 && offy>=0 && offx<arrays.length&&offy<arrays[0].length){
container[1+atx[i]][1+aty[i]]=arrays[offx][offy];
}else{
container[1+atx[i]][1+aty[i]]=-1;//
}
}
return container;
}
//TODO make map for reduce calcurate time
private static Integer[][][] turnoffsets=new Integer[3][3][8];//caching last values
private static int findNewTurnOffset(final int sx,final int sy,final int move){
if(turnoffsets[sx][sy][move]!=null){
return turnoffsets[sx][sy][move];
}
int x=sx;
int y=sy;
//System.out.println("input "+x+"x"+y);
if(x!=1 || y!=1){// 1x1 is center & no need move
for(int i=0;i<move;i++){
if(x==0){
if(y>0){
y--;
}else{
x++;
}
}else if(x==1){
if(y==0){
x++;
}else{
x--;
}
}else if(x==2){
if(y<2){
y++;
}else{
x--;
}
}
//System.out.println(i+" "+x+"x"+y);
}
}
int v=(y*3+x)*8;
turnoffsets[sx][sy][move]=v;
return v;
}
/*
* angle must be 0-360;
* 8 samples LBP only 45 base
*/
public static int[] turn3x3(int[] binaryPattern,int angle){
if(binaryPattern.length!=72){//for 3x3 8samples
return null;
}
int[] result=new int[72];
if(angle<0){
angle=360+angle;
}
int move=angle/45; //turn based 45 ,0-7
for(int y=0;y<3;y++){
for(int x=0;x<3;x++){
int srcOffset=(y*3+x)*8;
int destOffset=findNewTurnOffset(x,y,move);
for(int i=0;i<8;i++){
int newIndex=i+move;
if(newIndex>=8){
newIndex-=8;
}
result[destOffset+newIndex]=binaryPattern[srcOffset+i];
}
}
}
return result;
}
}