Jump to content

Algorithm Implementation/Graphs/Maximum flow/Boykov & Kolmogorov

From Wikibooks, open books for an open world
import java.util.LinkedList;
/**
 * 
 * @author bastien Jacquet
 *
 * Implements a graph to cut
 * Very complicated to program and debug
 * It worked for me, but it may still have bugs
 * See the paper for more details
 * Yuri Boykov, Vladimir Kolmogorov: An Experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision. IEEE Trans. Pattern Anal. Mach. Intell. 26(9): 1124-1137 (2004)
 */
class Node {
	int index;
	int	prevEdgeIndex;		/* Edge to previous node of the path */
	double maxCapToHere=0;
	//V2
	int dist;
	
}

class Edge {
	int	initial_vertex;	/* initial vertex of this edge */
	int	terminal_vertex;	/* terminal vertex of this edge */
	double	capacity;	/* capacity */
	double flow;
	int invEdgeIndex;
}
public class GraphCutBoykovKolmogorov {
	boolean debug=true;
	/** We assume that an edge with eps capacity is saturated	 */
	private final double eps=1e-3;
	private int	nbNode,nbEdges;
	//private int	sourceNode=0,sinkNode=1;	/* start node, terminate node */
	private int w,h;
	private Node	node[];
	private Edge	edge[];
	/** startingEdge[nodeIndex] is an array of edges indexes that starts from node nodeIndex */
	private int[][]   startingEdge;
	/** Retrieve the node index for the (x,y) pixel. Inlined*/
	private int indice(int x,int y){
		return x*h+y+2;
	}
	/** Retrieve the starting edge index in the <b>startingEdge</b> array to the (x,y) pixel from source or sink. Inlined*/
	private int indicePart(int x,int y){
		return x*h+y;
	}
	/**
	 * Construct an width x height Graph, with C-8 connectivity
	 * It creates all the edges, with 0 capacity
	 * @param width
	 * @param height
	 */
	GraphCutBoykovKolmogorov (int width,int height){
		w=width;h=height;
		// neighborhood=8;
		//int[][] voisins=new int[][]{{+1,0},{+1,-1},{+1,+1},{0,+1},{0,-1},{-1,+1},{-1,0},{-1,-1}};
		int[][] voisinsEdgeACreer=new int[][]{{+1,0},{+1,-1},{0,-1},{-1,-1}};
		this.nbNode=w*h+2;this.nbEdges=w*h*4+(h-2)*(w-2)*8+2*5*(h+w-4)+4*3;
		node=new Node[this.nbNode];
		edge=new Edge[this.nbEdges];
		startingEdge=new int[this.nbNode][];
		int[] curNbVoisins=new int[this.nbNode];
		int vx,vy,x,y,v,curEdge=0,i1,i2;
		//Create nodes
		node[0]=new Node();node[0].index=0;
		node[1]=new Node();node[1].index=1;
		for(x=0;x<w;x++){
			for(y=0;y<h;y++){
				i1=x*h+y+2;
				node[i1]=new Node();
				node[i1].index=i1;
			}
		}
		//Create array of starting Edges
		startingEdge[0]=new int[this.nbNode-2];
		startingEdge[1]=new int[this.nbNode-2];
		if((w==1)||(h==1)){ // Cas h==1 or w==1
			for(x=0;x<w;x++){
				for(y=0;y<h;y++){
					if(x*(x+1-w)==0 && y*(y+1-h)==0){//Coins
						startingEdge[(x*h+y+2)]=new int[1+1];
					}else if(x*(x+1-w)==0 || y*(y+1-h)==0){//Edges
						startingEdge[(x*h+y+2)]=new int[2+1];
					}
				}
			}
		}else{
			for(x=0;x<w;x++){
				for(y=0;y<h;y++){
					if(x*(x+1-w)==0 && y*(y+1-h)==0){//Coins
						startingEdge[(x*h+y+2)]=new int[3+2];
					}else if(x*(x+1-w)==0 || y*(y+1-h)==0){//Edges
						startingEdge[(x*h+y+2)]=new int[5+2];
					}else{//Milieu
						startingEdge[(x*h+y+2)]=new int[8+2];
					}
				}
			}
		}
		// T-links : Sink Edges
		for(x=0;x<w;x++){
			for(y=0;y<h;y++){
				i1=x*h+y+2;i2=1;
				edge[curEdge]=new Edge();
				edge[curEdge].initial_vertex=i1;
				edge[curEdge].terminal_vertex=i2;
				edge[curEdge].invEdgeIndex=curEdge+1;
				startingEdge[i1][curNbVoisins[i1]++]=curEdge;
				curEdge++;
				
				edge[curEdge]=new Edge();
				edge[curEdge].initial_vertex=i2;
				edge[curEdge].terminal_vertex=i1;
				edge[curEdge].invEdgeIndex=curEdge-1;
				startingEdge[i2][(x*h+y)]=curEdge;
				curEdge++;
			}
		}
		// N-Links
		for(x=0;x<w;x++){
			for(y=0;y<h;y++){
				i1=x*h+y+2;
				for(v=0;v<voisinsEdgeACreer.length;v++){
					vx=x+voisinsEdgeACreer[v][0];
					vy=y+voisinsEdgeACreer[v][1];
					if(vx<0 || vx>=w || vy<0 || vy>=h) continue;
					i2=vx*h+vy+2;
					edge[curEdge]=new Edge();
					edge[curEdge].initial_vertex=i1;
					edge[curEdge].terminal_vertex=i2;
					edge[curEdge].invEdgeIndex=curEdge+1;
					startingEdge[i1][curNbVoisins[i1]++]=curEdge;
					curEdge++;
					
					edge[curEdge]=new Edge();
					edge[curEdge].initial_vertex=i2;
					edge[curEdge].terminal_vertex=i1;
					edge[curEdge].invEdgeIndex=curEdge-1;
					startingEdge[i2][curNbVoisins[i2]++]=curEdge;
					curEdge++;
				}
			}
		}
		// T-links : Source Edges
		for(x=0;x<w;x++){
			for(y=0;y<h;y++){
				i1=0;i2=x*h+y+2;
				edge[curEdge]=new Edge();
				edge[curEdge].initial_vertex=i1;
				edge[curEdge].terminal_vertex=i2;
				edge[curEdge].invEdgeIndex=curEdge+1;
				startingEdge[i1][(x*h+y)]=curEdge;
				curEdge++;
				
				edge[curEdge]=new Edge();
				edge[curEdge].initial_vertex=i2;
				edge[curEdge].terminal_vertex=i1;
				edge[curEdge].invEdgeIndex=curEdge-1;
				startingEdge[i2][curNbVoisins[i2]++]=curEdge;
				//TODO:Cannot return to source
				curEdge++;
			}
		}
		if(curEdge!=nbEdges) System.out.println("Pb creation edges");
	}
	/**
	 * retrieve the edge between (x1,y1) and (x2,y2)
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return the matching edge if exists, null otherwise
	 */
	private  Edge getEdge(int x1,int y1,int x2,int y2){
		int i1=x1*h+y1+2,i2=x2*h+y2+2;
		for(int v=0;v<startingEdge[i1].length;v++){
			if(edge[startingEdge[i1][v]].terminal_vertex==i2)
				return edge[startingEdge[i1][v]];
		}
		return null;
	}
	/**
	 * set the edge between (x1,y1) and (x2,y2) to the capacity w
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2 
	 * @param w capacity
	 */
	public void setInternWeight(int x1,int y1,int x2,int y2,double w){
		int i1=x1*h+y1+2,i2=x2*h+y2+2;
		for(int v=0;v<startingEdge[i1].length;v++){
			if(edge[startingEdge[i1][v]].terminal_vertex==i2){
				edge[startingEdge[i1][v]].capacity=w;
				edge[edge[startingEdge[i1][v]].invEdgeIndex].capacity=w;
			}
		}
	}
	/**
	 * set the edge between Source and (x1,y1) to the capacity w
	 * @param x1
	 * @param y1
	 * @param w capacity
	 */
	public void setSourceWeight(int x1,int y1,double w){
		int i1=0,i2=x1*h+y1;
		edge[startingEdge[i1][i2]].capacity=w;
		edge[edge[startingEdge[i1][i2]].invEdgeIndex].capacity=w;
	}
	/**
	 * set the edge between Sink and (x1,y1) to the capacity w
	 * @param x1
	 * @param y1
	 * @param w capacity
	 */
	public void setSinkWeight(int x1,int y1,double w){
		int i1=1,i2=x1*h+y1;
		edge[startingEdge[i1][i2]].capacity=w;
		edge[edge[startingEdge[i1][i2]].invEdgeIndex].capacity=w;
	}
	/**
	 *  set the edge between Source and (x1,y1) to the capacity wSource
	 *  set the edge between Sink and (x1,y1) to the capacity wSink
	 * @param x1
	 * @param y1
	 * @param wSource capacity to the source
	 * @param wSink capacity to the sink
	 */
	public void setTWeight(int x1,int y1,double wSource,double wSink){
		int i2=x1*h+y1;
		edge[startingEdge[0][i2]].capacity=wSource;
		edge[edge[startingEdge[0][i2]].invEdgeIndex].capacity=-wSource;
		edge[startingEdge[1][i2]].capacity=-wSink;
		edge[edge[startingEdge[1][i2]].invEdgeIndex].capacity=wSink;
	}
	/**
	 * return the part of the graph where (x1,y1) is
	 * @param x1
	 * @param y1
	 * @return 0 if connected to Source, 1 if connected to Sink
	 * 2 if isolated
	 */
	public int linkedTo(int x1,int y1){
		int i2=x1*h+y1; 
		if (edge[startingEdge[0][i2]].capacity-edge[startingEdge[0][i2]].flow>eps)
			return 0;
		else if(edge[edge[startingEdge[1][i2]].invEdgeIndex].capacity-edge[edge[startingEdge[1][i2]].invEdgeIndex].flow>eps){
			return 1;
		}else{
			return 2;
		}
	}
	/**
	 * Calculate current Flow
	 * @return current Flow
	 */
	public double getFlow(){
		double f=0;int x,y;
		for(x=0;x<w;x++){
			for(y=0;y<h;y++){
				f+=edge[edge[startingEdge[1][(x*h+y)]].invEdgeIndex].flow;
			}
		}
		return f;
	}
/**
 * Reset the flow in the graph
 */
	private void resetFlow(){
		for(int i=0;i<edge.length;i++)
			edge[i].flow=0;
	}
	/**
	 * Based on Boykov & Kolmogorov Implementation
	 * By Bastien Jacquet see "An experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision" Boykov & Kolmogorov [2004]
	 */
	LinkedList<Integer> orphan;boolean[] isInS;LinkedList<Integer> active;boolean[] isInA;
	public void doCut(){
		resetFlow();
		isInS=new boolean[nbNode];isInS[0]=true;
		active=new LinkedList<Integer>();active.add(0);isInA=new boolean[nbNode];isInA[0]=true;
		orphan=new LinkedList<Integer>();

		for(int i=0;i<node.length;i++)node[i].prevEdgeIndex=-1;
		while(true){
			int lastEdge=growthStage();
			if(lastEdge==-1) return;
			augmentationStage(lastEdge);
			adoptionStage();
		}
		
	}
	/**
	 * Phase 1 : growth Stage
	 * Build the entire tree by setting prevEdgeIndex for each node
	 * @return the last edge index of the path to Sink, -1 if no path
	 */
	private int growthStage(){
		//if(debug) System.out.println("growthStage osize:"+orphan.size());
		if (isInS[1]) return node[1].prevEdgeIndex;
		int curNodeIndex,curStartingEdgeIndex;Edge curEdge;
		while (!active.isEmpty()){
			curNodeIndex=active.peek();
			//Speed up we just flag an active node for deletion
			if(!isInA[curNodeIndex]){active.poll();continue;}
			
			for(curStartingEdgeIndex=0 ;curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){
				curEdge=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]];
				if(curEdge.capacity-curEdge.flow<=eps) continue;
				if(!isInS[curEdge.terminal_vertex]){ //if is in T
					active.add(curEdge.terminal_vertex);isInA[curEdge.terminal_vertex]=true;
					isInS[curEdge.terminal_vertex]=true;
					node[curEdge.terminal_vertex].prevEdgeIndex=startingEdge[curNodeIndex][curStartingEdgeIndex];
				}
				if(curEdge.terminal_vertex==1) {
					return startingEdge[curNodeIndex][curStartingEdgeIndex];
				}
			}
			active.poll();isInA[curNodeIndex]=false;
		}
		return -1;
	}
	/**
	 * Phase 2 : augmentation Stage
	 * saturate the path starting with the edge lastEdgeIndex
	 * @param lastEdgeIndex
	 */
	private void augmentationStage(int lastEdgeIndex){
		double bottleNeckCap;
		try {
			bottleNeckCap = edge[lastEdgeIndex].capacity-edge[lastEdgeIndex].flow;
			for(int curNodeIndex=edge[lastEdgeIndex].initial_vertex;curNodeIndex!=0;curNodeIndex=edge[node[curNodeIndex].prevEdgeIndex].initial_vertex){
				if(bottleNeckCap>edge[node[curNodeIndex].prevEdgeIndex].capacity-edge[node[curNodeIndex].prevEdgeIndex].flow){
					bottleNeckCap=edge[node[curNodeIndex].prevEdgeIndex].capacity-edge[node[curNodeIndex].prevEdgeIndex].flow;
				}
			}
		} catch (Exception e) {
			//TODO : This should never happens
			e.printStackTrace();
			//if(node[edge[lastEdgeIndex].initial_vertex].prevEdgeIndex==-1)
			isInS=new boolean[nbNode];isInS[0]=true;
			active=new LinkedList<Integer>();active.add(0);isInA=new boolean[nbNode];isInA[0]=true;
			orphan=new LinkedList<Integer>();
			return;
		}
		int prevEdgeIndex=-1;
		for(int curEdgeIndex=lastEdgeIndex;curEdgeIndex!=-1;curEdgeIndex=prevEdgeIndex){
			edge[curEdgeIndex].flow+=bottleNeckCap;
			edge[edge[curEdgeIndex].invEdgeIndex].flow-=bottleNeckCap;
			prevEdgeIndex=node[edge[curEdgeIndex].initial_vertex].prevEdgeIndex;
			if(edge[curEdgeIndex].capacity-edge[curEdgeIndex].flow<=eps){
				node[edge[curEdgeIndex].terminal_vertex].prevEdgeIndex=-1;
				orphan.addFirst(edge[curEdgeIndex].terminal_vertex);
			}
		}
	}
	private int getRootOf(int nodeIndex){
		int curRootNodeIndex=nodeIndex;
		while(node[curRootNodeIndex].prevEdgeIndex>0){
			curRootNodeIndex=edge[node[curRootNodeIndex].prevEdgeIndex].initial_vertex;
		}
		return curRootNodeIndex;
	}
	/**
	 * Phase 3 : adoption Stage
	 * Repair the search tree by processing orphans
	 */
	private void adoptionStage(){
		int curNodeIndex,curStartingEdgeIndex;Edge curEdge;
		while (!orphan.isEmpty()){
			curNodeIndex=orphan.poll();
			boolean hasFindParent=false;
			//searching parent
			for(curStartingEdgeIndex=0 ;!hasFindParent && curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){
				//For each incoming edge
				curEdge=edge[edge[startingEdge[curNodeIndex][curStartingEdgeIndex]].invEdgeIndex];
				if(curEdge.capacity-curEdge.flow<=eps) continue;
				if(!isInS[curEdge.initial_vertex])continue;
				int curRootNodeIndex=curEdge.initial_vertex;
				while(node[curRootNodeIndex].prevEdgeIndex>0){
					curRootNodeIndex=edge[node[curRootNodeIndex].prevEdgeIndex].initial_vertex;
				}
				if(curRootNodeIndex!=0)continue;
				hasFindParent=true;
				node[curNodeIndex].prevEdgeIndex=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]].invEdgeIndex;
				break;
			}
			// no parents possible
			if(!hasFindParent){
				isInS[curNodeIndex]=false;
				//Speed up we just flag an active node for deletion
				isInA[curNodeIndex]=false;
				for(curStartingEdgeIndex=0 ;curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){
					curEdge=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]];
					//Children becomes orphans
					if(node[curEdge.terminal_vertex].prevEdgeIndex==startingEdge[curNodeIndex][curStartingEdgeIndex]){
						node[curEdge.terminal_vertex].prevEdgeIndex=-1;
						orphan.addLast(curEdge.terminal_vertex);
					}
					if(!isInS[curEdge.terminal_vertex]) continue;
					curEdge=edge[curEdge.invEdgeIndex];
					//Add in active if not already here
					if(curEdge.capacity-curEdge.flow>eps && !isInA[curEdge.initial_vertex]) {
						active.add(curEdge.initial_vertex);
						isInA[curEdge.initial_vertex]=true;
					}
				}
			}
		}
		
	}
}