package org.openpnp.vision.pipeline;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opencv.core.Mat;
import org.openpnp.model.Configuration;
import org.openpnp.spi.Camera;
import org.openpnp.vision.pipeline.CvStage.Result;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.Serializer;
/**
* A CvPipeline performs computer vision operations on a working image by processing in series a
* list of CvStage instances. Each CvStage instance can modify the working image and return a new
* image along with data extracted from the image. After processing the image callers can get access
* to the images and models from each stage.
*
* CvPipeline is serializable using toXmlString and fromXmlString. This makes it easy to export
* pipelines and exchange them with others.
*
* This work takes inspiration from several existing projects:
*
* FireSight by Karl Lew and Šimon Fojtů: https://github.com/firepick1/FireSight
*
* RoboRealm: http://www.roborealm.com/
*
* TODO: Add measuring to image window.
*
* TODO: Add info showing pixel coordinates when mouse is in image window.
*/
@Root
public class CvPipeline {
static {
nu.pattern.OpenCV.loadShared();
System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);
}
@ElementList
private ArrayList<CvStage> stages = new ArrayList<>();
private Map<CvStage, Result> results = new HashMap<CvStage, Result>();
private Mat workingImage;
private Camera camera;
public CvPipeline() {
}
public CvPipeline(String xmlPipeline) {
try {
fromXmlString(xmlPipeline);
}
catch (Exception e) {
throw new Error(e);
}
}
/**
* Add the given CvStage to the end of the pipeline using the given name. If name is null a
* unique one will be generated and set on the stage.
*
* @param name
* @param stage
*/
public void add(String name, CvStage stage) {
if (name == null) {
name = generateUniqueName();
}
stage.setName(name);
stages.add(stage);
}
/**
* Add the given CvStage to the end of the pipeline. If the stage does not have a name a unique
* one will be generated and set on the stage.
*
* @param stage
*/
public void add(CvStage stage) {
add(stage.getName(), stage);
}
public void insert(String name, CvStage stage, int index) {
if (name == null) {
name = generateUniqueName();
}
stage.setName(name);
stages.add(index, stage);
}
public void insert(CvStage stage, int index) {
insert(stage.getName(), stage, index);
}
public void remove(String name) {
remove(getStage(name));
}
public void remove(CvStage stage) {
stages.remove(stage);
}
public List<CvStage> getStages() {
return Collections.unmodifiableList(stages);
}
public CvStage getStage(String name) {
if (name == null) {
return null;
}
for (CvStage stage : stages) {
if (stage.getName().equals(name)) {
return stage;
}
}
return null;
}
/**
* Get the Result returned by the CvStage with the given name. May return null if the stage did
* not return a result.
*
* @param name
* @return
*/
public Result getResult(String name) {
if (name == null) {
return null;
}
return getResult(getStage(name));
}
/**
* Get the Result returned by give CvStage. May return null if the stage did not return a
* result.
*
* @param stage
* @return
*/
public Result getResult(CvStage stage) {
if (stage == null) {
return null;
}
return results.get(stage);
}
/**
* Get the current working image. Primarily intended to be called from CvStage implementations.
*
* @return
*/
public Mat getWorkingImage() {
return workingImage;
}
public void setCamera(Camera camera) {
this.camera = camera;
}
public Camera getCamera() {
return camera;
}
public void process() {
release();
for (CvStage stage : stages) {
// Process and time the stage and get the result.
long processingTimeNs = System.nanoTime();
Result result = null;
try {
if (!stage.isEnabled()) {
throw new Exception("Stage not enabled.");
}
result = stage.process(this);
}
catch (Exception e) {
result = new Result(null, e);
}
processingTimeNs = System.nanoTime() - processingTimeNs;
Mat image = null;
Object model = null;
if (result != null) {
image = result.image;
model = result.model;
}
// If the result image is null and there is a working image, replace the result image
// replace the result image with a clone of the working image.
if (image == null) {
if (workingImage != null) {
image = workingImage.clone();
}
}
// If the result image is not null:
// Release the working image if the result image is different.
// Replace the working image with the result image.
// Clone the result image for storage.
else {
if (workingImage != null && workingImage != image) {
workingImage.release();
}
workingImage = image;
image = image.clone();
}
results.put(stage, new Result(image, model, processingTimeNs));
}
}
/**
* Release any temporary resources associated with the processing of the pipeline. Should be
* called when the pipeline is no longer needed. This is primarily to release retained native
* resources from OpenCV.
*/
public void release() {
if (workingImage != null) {
workingImage.release();
}
for (Result result : results.values()) {
if (result.image != null) {
result.image.release();
}
}
results.clear();
}
/**
* Convert the pipeline to an XML string that can be read back in with #fromXmlString.
*
* @return
* @throws Exception
*/
public String toXmlString() throws Exception {
Serializer ser = Configuration.createSerializer();
StringWriter sw = new StringWriter();
ser.write(this, sw);
return sw.toString();
}
/**
* Parse the pipeline in the given String and replace the current pipeline with the results.
*
* @param s
* @throws Exception
*/
public void fromXmlString(String s) throws Exception {
release();
Serializer ser = Configuration.createSerializer();
StringReader sr = new StringReader(s);
CvPipeline pipeline = ser.read(CvPipeline.class, sr);
stages.clear();
for (CvStage stage : pipeline.getStages()) {
add(stage);
}
}
private String generateUniqueName() {
for (int i = 0;; i++) {
String name = "" + i;
if (getStage(name) == null) {
return name;
}
}
}
@Override
public CvPipeline clone() throws CloneNotSupportedException {
try {
return new CvPipeline(toXmlString());
}
catch (Exception e) {
throw new CloneNotSupportedException(e.getMessage());
}
}
}