graphinius
Version:
Generic graph library in Typescript
273 lines (210 loc) • 9.68 kB
text/typescript
import * as $N from '../core/base/BaseNode';
import * as $E from '../core/base/BaseEdge';
import * as $G from '../core/base/BaseGraph';
import * as $MC from '../mincutmaxflow/MinCutMaxFlowBoykov';
export type EnergyFunctionTerm = (arg1: string, arg2: string) => number;
// type EnergyFunctionDataTerm = (arg1: string, ...args: any[]) => number;
// type EnergyFunction = (...args: any[]) => number;
export interface EMEConfig {
directed: boolean; // do we
labeled: boolean;
interactionTerm: EnergyFunctionTerm;
dataTerm: EnergyFunctionTerm;
}
export interface EMEResult {
graph: $G.IGraph;
}
export interface IEMEBoykov {
calculateCycle(): EMEResult;
constructGraph(): $G.IGraph;
initGraph(graph: $G.IGraph): $G.IGraph;
prepareEMEStandardConfig(): EMEConfig;
}
export interface EMEState {
expansionGraph: $G.IGraph;
labeledGraph: $G.IGraph;
activeLabel: string;
energy: number;
}
/**
*
*/
class EMEBoykov implements IEMEBoykov {
private _config: EMEConfig;
private _state: EMEState = {
expansionGraph: null,
labeledGraph: null,
activeLabel: '',
energy: Infinity
};
private _interactionTerm: EnergyFunctionTerm;
private _dataTerm: EnergyFunctionTerm;
constructor(private _graph: $G.IGraph,
private _labels: Array<string>,
config?: EMEConfig) {
this._config = config || this.prepareEMEStandardConfig();
// set the energery functions
this._interactionTerm = this._config.interactionTerm;
this._dataTerm = this._config.dataTerm;
// initialize graph => set labels
this._graph = this.initGraph(_graph);
// init state
this._state.labeledGraph = this._graph.cloneStructure();
this._state.activeLabel = this._labels[0];
}
calculateCycle() {
let success: boolean = true;
let mincut_options: $MC.MCMFConfig = { directed: true };
while (success) {
success = false;
// for each label
for (let i = 0; i < this._labels.length; i++) {
this._state.activeLabel = this._labels[i];
// find a new labeling f'=argminE(f') within one expansion move of f
this._state.expansionGraph = this.constructGraph(); // construct new expansion graph
let source: $N.IBaseNode = this._state.expansionGraph.getNodeById("SOURCE");
let sink: $N.IBaseNode = this._state.expansionGraph.getNodeById("SINK");
// logger.log("compute mincut");
let MinCut: $MC.IMCMFBoykov;
MinCut = new $MC.MCMFBoykov(this._state.expansionGraph, source, sink, mincut_options);
let mincut_result: $MC.MCMFResult = MinCut.calculateCycle();
// logger.log("done mincut");
if (mincut_result.cost < this._state.energy) {
this._state.energy = mincut_result.cost;
this._state.labeledGraph = this.labelGraph(mincut_result, source);
success = true;
}
}
// the minimum can be found in one cycle
if (this._labels.length <= 2) {
break;
}
}
let result: EMEResult = {
graph: this._state.labeledGraph
};
return result;
}
constructGraph(): $G.IGraph {
let graph: $G.IGraph = this._state.labeledGraph.cloneStructure();
// copy node information
let nodes: { [key: string]: $N.IBaseNode } = graph.getNodes();
let node_ids: Array<string> = Object.keys(nodes);
// add source (alpha) and sink (not alpha) to graph
let source: $N.IBaseNode = graph.addNodeByID("SOURCE");
let sink: $N.IBaseNode = graph.addNodeByID("SINK");
// copy edge information now -> will cause infinite loop otherwise
// let edges: {[key: string] : $E.IBaseEdge} = graph.getUndEdges();
let edge_ids: Array<string> = Object.keys(graph.getUndEdges());
let edges_length: number = edge_ids.length;
// for all nodes add
// edge to source and edge to sink
for (let i = 0; i < node_ids.length; i++) {
let node: $N.IBaseNode = nodes[node_ids[i]];
let edge_options: $E.BaseEdgeConfig = { directed: true, weighted: true, weight: 0 };
// add edge to source
edge_options.weight = this._dataTerm(this._state.activeLabel, this._graph.getNodeById(node.getID()).getLabel());
let edge_source: $E.IBaseEdge = graph.addEdgeByID(node.getID() + "_" + source.getID(), node, source, edge_options);
let edge_source_reverse: $E.IBaseEdge = graph.addEdgeByID(source.getID() + "_" + node.getID(), source, node, edge_options);
// add edge to sink
edge_options.weight = (node.getLabel() == this._state.activeLabel) ? Infinity : this._dataTerm(node.getLabel(), this._graph.getNodeById(node.getID()).getLabel());
let edge_sink: $E.IBaseEdge = graph.addEdgeByID(node.getID() + "_" + sink.getID(), node, sink, edge_options);
let edge_sink_source: $E.IBaseEdge = graph.addEdgeByID(sink.getID() + "_" + node.getID(), sink, node, edge_options);
}
// for all neighboring pixels {p, q} where label(p) != label(q) :
// add auxiliary node a_{p_q}
// add edge from auxiliary node to sink
// add edges to auxiliary node (from p and q)
// remove edge between p and q
for (let i = 0; i < edges_length; i++) {
// let edge: $E.IBaseEdge = edges[Object.keys(edges)[i]];
let edge: $E.IBaseEdge = graph.getEdgeById(edge_ids[i]);
let node_p: $N.IBaseNode = edge.getNodes().a;
let node_q: $N.IBaseNode = edge.getNodes().b;
let edge_options: $E.BaseEdgeConfig = { directed: true, weighted: true, weight: 0 };
// we don't care further for nodes of same labels
if (node_p.getLabel() == node_q.getLabel()) {
// just set the edge weight and convert to directed
edge_options.weight = this._interactionTerm(node_p.getLabel(), this._state.activeLabel);
graph.deleteEdge(edge);
graph.addEdgeByID(node_p.getID() + "_" + node_q.getID(), node_p, node_q, edge_options);
graph.addEdgeByID(node_q.getID() + "_" + node_p.getID(), node_q, node_p, edge_options);
continue;
}
// add auxiliary node
let node_aux: $N.IBaseNode = graph.addNodeByID("aux_" + node_p.getID() + "_" + node_q.getID());
// add 3 edges [{p, aux}, {aux, q}, {aux, sink}]
// add edge {p, aux}
edge_options.weight = this._interactionTerm(node_p.getLabel(), this._state.activeLabel);
let edge_p_aux: $E.IBaseEdge = graph.addEdgeByID(node_p.getID() + "_" + node_aux.getID(), node_p, node_aux, edge_options);
let edge_p_aux_reverse: $E.IBaseEdge = graph.addEdgeByID(node_aux.getID() + "_" + node_p.getID(), node_aux, node_p, edge_options);
// add edge {aux, q}
edge_options.weight = this._interactionTerm(this._state.activeLabel, node_q.getLabel());
let edge_aux_q: $E.IBaseEdge = graph.addEdgeByID(node_aux.getID() + "_" + node_q.getID(), node_aux, node_q, edge_options);
let edge_aux_q_reverse: $E.IBaseEdge = graph.addEdgeByID(node_q.getID() + "_" + node_aux.getID(), node_q, node_aux, edge_options);
// add edge {aux, sink}
edge_options.weight = this._interactionTerm(node_p.getLabel(), node_q.getLabel());
let edge_aux_sink: $E.IBaseEdge = graph.addEdgeByID(node_aux.getID() + "_" + sink.getID(), node_aux, sink, edge_options);
let edge_aux_sink_reverse: $E.IBaseEdge = graph.addEdgeByID(sink.getID() + "_" + node_aux.getID(), sink, node_aux, edge_options);
// remove the edge between p and q
graph.deleteEdge(edge);
}
return graph;
}
// label a graph based on the result of a mincut
labelGraph(mincut: $MC.MCMFResult, source: $N.IBaseNode) {
let graph: $G.IGraph = this._state.labeledGraph;
source = this._state.expansionGraph.getNodeById("SOURCE");
for (let i = 0; i < mincut.edges.length; i++) {
let edge: $E.IBaseEdge = mincut.edges[i];
let node_a: $N.IBaseNode = edge.getNodes().a;
let node_b: $N.IBaseNode = edge.getNodes().b;
if (node_a.getID() == source.getID()) {
graph.getNodeById(node_b.getID()).setLabel(this._state.activeLabel);
continue;
}
if (node_b.getID() == source.getID()) {
graph.getNodeById(node_a.getID()).setLabel(this._state.activeLabel);
}
}
return graph;
}
initGraph(graph: $G.IGraph) {
let nodes = graph.getNodes();
let node_ids: Array<string> = Object.keys(nodes);
let nodes_length: number = node_ids.length;
for (let i = 0; i < nodes_length; i++) {
// let node: $N.IBaseNode = nodes[Object.keys(nodes)[i]];
let node: $N.IBaseNode = nodes[node_ids[i]];
node.setLabel(node.getFeature('label'));
}
return graph;
}
prepareEMEStandardConfig(): EMEConfig {
// we use the potts model as standard interaction term
let interactionTerm: EnergyFunctionTerm = function (label_a: string, label_b: string) {
return (label_a == label_b) ? 0 : 1;
};
// squared difference of new label and observed label as standard data term
// label: new label; node_id is needed to get the original(observed) label
let dataTerm: EnergyFunctionTerm = function (label: string, observed: string) {
// get the label of the same node in the original graph
// let observed: string = this._graph.getNodeById(node_id).getLabel();
let label_number: number = Number(label);
let observed_number: number = Number(observed);
if (isNaN(label_number) || isNaN(observed_number)) {
throw new Error('Cannot convert labels to numbers!');
}
return 1.5 * Math.pow(label_number - observed_number, 2);
};
return {
directed: false,
labeled: false,
interactionTerm: interactionTerm,
dataTerm: dataTerm
}
}
}
export {
EMEBoykov
};