org.photovault.image.ImageOpChain.java Source code

Java tutorial

Introduction

Here is the source code for org.photovault.image.ImageOpChain.java

Source

/*
  Copyright (c) 2009 Harri Kaimio
    
  This file is part of Photovault.
    
  Photovault is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
    
  Photovault is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.
    
  You should have received a copy of the GNU General Public License
  along with Photovault; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

package org.photovault.image;

import com.google.protobuf.Message;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.photovault.common.PhotovaultException;
import org.photovault.imginfo.ProtobufConverter;
import org.photovault.common.ProtobufSupport;
import org.photovault.dcraw.RawConversionSettings;
import org.photovault.dcraw.RawSettingsFactory;
import org.photovault.image.ImageOpDto.ImageOpChain.Builder;

/**
 * ImageOpChain describes the image processing operations that are applied to
 * original iamge to achieve desired result.
 * @author Harri Kaimio
 * @since 0.6.0
 */
@XStreamAlias("processing")
public class ImageOpChain
        implements ProtobufSupport<ImageOpChain, ImageOpDto.ImageOpChain, ImageOpDto.ImageOpChain.Builder> {

    static private Log log = LogFactory.getLog(ImageOpChain.class);

    public ImageOpChain() {
    }

    /**
     * Copy constructor. Makes deep copy of the given chain
     * @param chain
     */
    public ImageOpChain(ImageOpChain chain) {
        // Copy operations
        for (Map.Entry<String, ImageOp> e : chain.operations.entrySet()) {
            ImageOp op = e.getValue().createCopy();
            setOperation(e.getKey(), op);
        }
        head = chain.head;
        for (Map.Entry<String, String> e : chain.sources.entrySet()) {
            setConnection(e.getKey(), e.getValue());
        }
    }

    /**
     * Operations that are part of this chain, keyed by their name
     */
    @XStreamOmitField
    Map<String, ImageOp> operations = new HashMap<String, ImageOp>();

    /**
     * Map from name of input port to name of the output it is connected
     */
    Map<String, String> sources = new HashMap<String, String>();

    /**
     * Map from name of output port to set of input port names it is connected
     * to.
     */
    Map<String, Set<String>> sinks = new HashMap<String, Set<String>>();
    public String head;

    /**
     * Get name of head of this chain
     * @return
     */
    public String getHead() {
        return head;
    }

    /**
     * Set head of the chain
     * @param outputName Name of the output port that is to be used as head
     */
    public void setHead(String outputName) {
        head = outputName;
    }

    /**
     * Get name of output prot that is connected to given input
     * @param sinkName Name of the input port
     * @return Name of output connected to sinkName, or null if none.
     */
    public String getConnection(String sinkName) {
        ImageOp.Sink sink = getInputPort(sinkName);
        return (sink == null) ? null : sink.getSourceName();
    }

    /**
     * Connect given sink and source together
     * @param sinkName Name of the sink
     * @param sourceName Name of the soruce
     */
    public void setConnection(String sinkName, String sourceName) {
        String oldSource = sources.get(sinkName);
        sources.put(sinkName, sourceName);
        Set<String> sinksOfSource = sinks.get(sourceName);
        if (sinksOfSource == null) {
            sinksOfSource = new HashSet<String>();
            sinks.put(sourceName, sinksOfSource);
        }
        sinksOfSource.add(sinkName);

        if (oldSource != null) {
            sinks.get(oldSource).remove(sinkName);
        }

    }

    ImageOp.Source getSource(ImageOp.Sink sink) {
        ImageOp.Source ret = null;
        String sourceName = sources.get(sink.getPortName());
        if (sourceName != null) {
            ret = getOutputPort(sourceName);
        }
        return ret;
    }

    Set<ImageOp.Sink> getSinks(ImageOp.Source source) {
        Set<ImageOp.Sink> ret = new HashSet<ImageOp.Sink>();
        Set<String> sinkNames = sinks.get(source.getPortName());
        if (sinkNames != null) {
            for (String s : sinkNames) {
                ret.add(getInputPort(s));
            }
        }
        return ret;
    }

    /**
     * Disconnect sink and soruce
     * @param sourceName Name of the source
     * @param sinkNameName of the sink
     * @throws IllegalArgumentException if either of the ports is not known
     * @throws IllegalStateException if the ports are not connected
     */
    public void removeConnection(String sourceName, String sinkName) {
        ImageOp.Source src = getOutputPort(sourceName);
        if (src == null) {
            throw new IllegalArgumentException(sourceName + " not known!");
        }
        ImageOp.Sink sink = getInputPort(sinkName);
        if (sink == null) {
            throw new IllegalArgumentException(sinkName + " not known!");
        }
        if (sink.getSource() != src) {
            throw new IllegalStateException(sourceName + " is not connected to " + sinkName);
        }
        sink.setSource(null);
    }

    /**
     * Get operation by its name
     * @param name Name of the operation
     * @return Operation with given name or <code>null</code> if it does not
     * exist
     */
    public ImageOp getOperation(String name) {
        return operations.get(name);
    }

    public void addOperation(ImageOp op) {
        operations.put(op.getName(), op);
        op.chain = this;
    }

    /**
     * Add operation to the chain
     * @param name Name of the operation
     * @param op The operation to add
     */
    public void setOperation(String name, ImageOp op) {
        op.setName(name);
        operations.put(name, op);
        op.chain = this;
    }

    public ImageOp.Sink getInputPort(String name) {
        String parts[] = name.split("\\.");
        if (parts.length != 2) {
            throw new IllegalArgumentException(name + " is not a valid sink name");
        }
        ImageOp op = getOperation(parts[0]);
        ImageOp.Sink sink = op.getInputPort(parts[1]);
        return sink;
    }

    public ImageOp.Source getOutputPort(String name) {
        String parts[] = name.split("\\.");
        if (parts.length != 2) {
            throw new IllegalArgumentException(name + " is not a valid sink name");
        }
        ImageOp op = getOperation(parts[0]);
        ImageOp.Source src = op.getOutputPort(parts[1]);
        return src;
    }

    static XStream xs = null;

    private synchronized static XStream getXStream() {
        if (xs == null) {
            xs = new XStream();
            initXStream(xs);
        }
        return xs;
    }

    /**
     * Initialize an XStream XML converter to recognize element types & conerters
     * associated with ImageOpChain.
     * @param xstream
     */
    public static void initXStream(XStream xstream) {
        xstream.processAnnotations(ImageOp.class);
        xstream.processAnnotations(DCRawOp.class);
        xstream.processAnnotations(DCRawMapOp.class);
        xstream.processAnnotations(CropOp.class);
        xstream.processAnnotations(ImageOpChain.class);
        xstream.processAnnotations(ChanMapOp.class);
        xstream.processAnnotations(ColorCurve.class);
        xstream.registerConverter(new ImageOpChainXmlConverter(xstream.getMapper()));
    }

    public String getAsXml() {
        return getXStream().toXML(this);
    }

    public static ImageOpChain fromXml(String xml) {
        return (ImageOpChain) getXStream().fromXML(xml);
    }

    /**
     * Helper function to convert {@link RawConversionSettings} into operations
     * in this chain.
     * @param rawSettings
     */
    public void applyRawConvSetting(RawConversionSettings rawSettings) {
        if (rawSettings != null) {
            // Copy state to processing graph
            DCRawOp rawConv = (DCRawOp) getOperation("dcraw");
            if (rawConv == null) {
                rawConv = new DCRawOp(this, "dcraw");
            }
            rawConv.setBlueGreenRatio(rawSettings.getBlueGreenRatio());
            rawConv.setDaylightBlueGreenRatio(rawSettings.getDaylightBlueGreenRatio());
            rawConv.setDaylightRedGreenRatio(rawSettings.getDaylightRedGreenRatio());
            rawConv.setHlightRecovery(rawSettings.getHlightRecovery());
            rawConv.setMedianFilterPassCount(rawSettings.getMedianPassCount());
            rawConv.setRedGreenRatio(rawSettings.getRedGreenRatio());
            rawConv.setWaveletThreshold(rawSettings.getWaveletThreshold());

            DCRawMapOp mapping = (DCRawMapOp) getOperation("raw-map");
            if (mapping == null) {
                mapping = new DCRawMapOp();
                mapping.setName("raw-map");
                addOperation(mapping);
                mapping.setBlack(rawSettings.getBlack());
                mapping.setWhite(rawSettings.getWhite());
                mapping.setEvCorr(rawSettings.getEvCorr());
                mapping.setHlightCompr(rawSettings.getHighlightCompression());
                setConnection("raw-map.in", "dcraw.out");
            }
            //            mapping.getInputPort( "in" ).setSource(
            //                    rawConv.getOutputPort( "out" ) );
            ImageOp cropOp = getOperation("crop");
            ImageOp chanOp = getOperation("chan-map");
            if (chanOp != null) {
                setConnection("chan-map.in", "raw-map.out");
                //                chanOp.getInputPort( "in" ).setSource(
                //                        mapping.getOutputPort( "out" ) );
            } else if (cropOp != null) {
                setConnection("crop.in", "raw-map.out");
                //                cropOp.getInputPort( "in" ).setSource(
                //                        mapping.getOutputPort( "out" ) );
            } else {
                setHead("raw-map.out");
            }
        }
    }

    /**
     * Helper functions that converts raw conversion realted nodes in this chain
     * into {@link RawConversionSettings}
     * @return
     */
    public RawConversionSettings getRawConvSettings() {
        DCRawOp dcraw = (DCRawOp) getOperation("dcraw");
        DCRawMapOp mapping = (DCRawMapOp) getOperation("raw-map");
        if (dcraw == null || mapping == null) {
            return null;
        }
        RawSettingsFactory rf = new RawSettingsFactory();
        rf.setBlack(mapping.getBlack());
        rf.setWhite(mapping.getWhite());
        rf.setDaylightMultipliers(
                new double[] { dcraw.getDaylightRedGreenRatio(), 1.0, dcraw.getDaylightBlueGreenRatio() });
        rf.setBlueGreenRatio(dcraw.getBlueGreenRatio());
        rf.setEvCorr(mapping.getEvCorr());
        rf.setHlightComp(mapping.getHlightCompr());
        rf.setHlightRecovery(dcraw.getHlightRecovery());
        rf.setMedianPassCount(dcraw.getMedianFilterPassCount());
        rf.setRedGreenRation(dcraw.getRedGreenRatio());
        rf.setWaveletThreshold((float) dcraw.getWaveletThreshold());
        try {
            return rf.create();
        } catch (PhotovaultException ex) {
            return null;
        }
    }

    /**
     * Helper function to apply Photovault style cropping to this chain
     * @param cropBounds Crop bounds.
     */
    public void applyCropping(Rectangle2D cropBounds) {
        CropOp crop = (CropOp) getOperation("crop");
        if (crop == null) {
            crop = createCropOp();
        }
        crop.setMinX(cropBounds.getMinX());
        crop.setMaxX(cropBounds.getMaxX());
        crop.setMinY(cropBounds.getMinY());
        crop.setMaxY(cropBounds.getMaxY());
    }

    public void applyRotation(double rot) {
        CropOp crop = (CropOp) getOperation("crop");
        if (crop == null) {
            crop = createCropOp();
        }
        crop.setRot(rot);
    }

    public Rectangle2D getCropping() {
        CropOp crop = (CropOp) getOperation("crop");
        if (crop == null) {
            return new Rectangle2D.Double(0.0, 0.0, 1.0, 1.0);
        }
        return new Rectangle2D.Double(crop.getMinX(), crop.getMinY(), crop.getMaxX() - crop.getMinX(),
                crop.getMaxY() - crop.getMinY());
    }

    public double getRotation() {
        CropOp crop = (CropOp) getOperation("crop");
        return (crop != null) ? crop.getRot() : 0.0;
    }

    private CropOp createCropOp() {
        CropOp crop = new CropOp();
        crop.setName("crop");
        addOperation(crop);
        String oldHead = getHead();
        if (oldHead != null) {
            setConnection("crop.in", oldHead);
            //             crop.getInputPort( "in" ).setSource( oldHead );
        }
        setHead("crop.out");
        return crop;
    }

    /**
     * Heper function to create {@link ChanMapOp} node based on
     * {@link ChannelMapOperation}
     * @param map
     */
    public void applyChanMap(ChannelMapOperation map) {
        if (map == null) {
            return;
        }
        ChannelMapOperationFactory cf = new ChannelMapOperationFactory();
        ChanMapOp mapOp = (ChanMapOp) getOperation("chan-map");
        if (mapOp != null) {
            mapOp.channels.clear();
        } else {
            mapOp = new ChanMapOp(this, "chan-map");
            ImageOp dmapOp = (DCRawMapOp) getOperation("raw-map");
            Set<String> nextOps = new HashSet<String>();
            if (dmapOp != null) {
                nextOps = dmapOp.getOutputPort("out").getSinkNames();
                setConnection("chan-map.in", "raw-map.out");
                //                mapOp.getInputPort( "in" ).setSource( dmapOp.getOutputPort( "out") );
            }
            if (nextOps.size() > 0) {
                for (String sink : nextOps) {
                    setConnection(sink, "chan-map.out");
                    //                    sink.setSource( mapOp.getOutputPort( "out" ) );
                }
            } else {
                setHead("chan-map.out");
            }
        }
        for (String chan : map.getChannelNames()) {
            mapOp.setChannel(chan, map.getChannelCurve(chan));
        }
    }

    public ChannelMapOperation getChanMap() {
        ChanMapOp map = (ChanMapOp) getOperation("chan-map");
        if (map == null) {
            return null;
        }

        ChannelMapOperationFactory cf = new ChannelMapOperationFactory();
        for (Map.Entry<String, ColorCurve> e : map.channels.entrySet()) {
            cf.setChannelCurve(e.getKey(), e.getValue());
        }
        return cf.create();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!obj.getClass().equals(ImageOpChain.class)) {
            return false;
        }
        ImageOpChain other = (ImageOpChain) obj;

        if ((this.head == null ? other.head != null : !this.head.equals(other.head))) {
            return false;
        }

        if (this.sources == null ? other.sources != null : !this.sources.equals(other.sources)) {
            return false;
        }

        if (this.operations == null ? other.operations != null : !operations.equals(other.operations)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return operations.hashCode();
    }

    public Builder getBuilder() {
        Builder b = ImageOpDto.ImageOpChain.newBuilder();
        for (Map.Entry<String, ImageOp> e : operations.entrySet()) {
            ImageOpDto.ImageOp.Builder ob = ImageOpDto.ImageOp.newBuilder();
            ob.setName(e.getKey());
            ImageOp op = e.getValue();
            if (op instanceof DCRawOp) {
                ob.setRawOp(((DCRawOp) op).getBuilder());
            } else if (op instanceof DCRawMapOp) {
                ob.setRawMapOp(((DCRawMapOp) op).getBuilder());
            } else if (op instanceof ChanMapOp) {
                ob.setChanMapOp(((ChanMapOp) op).getBuilder());
            } else if (op instanceof CropOp) {
                ob.setCropOp(((CropOp) op).getBuilder());
            } else {
                throw new IllegalStateException("Unknown image operation " + op.getClass().getName());
            }
            b.addOperations(ob);
        }

        if (head != null) {
            b.setHead(head);
        } else {
            log.debug("Head missing");
            b.setHead("");
        }
        for (Map.Entry<String, String> e : sources.entrySet()) {
            b.addLinks(ImageOpDto.Link.newBuilder().setSource(e.getValue()).setSink(e.getKey()));
        }
        return b;
    }

    public ImageOpChain(ImageOpDto.ImageOpChain d) {
        setHead(d.getHead());
        for (ImageOpDto.Link l : d.getLinksList()) {
            sources.put(l.getSink(), l.getSource());
        }
        for (ImageOpDto.ImageOp op : d.getOperationsList()) {
            ImageOp o = null;
            if (op.hasRawOp()) {
                o = DCRawOp.create(op.getRawOp());
            } else if (op.hasRawMapOp()) {
                o = new DCRawMapOp(op.getRawMapOp());
            } else if (op.hasChanMapOp()) {
                o = new ChanMapOp(op.getChanMapOp());
            } else if (op.hasCropOp()) {
                o = new CropOp(op.getCropOp());
            } else {
                throw new IllegalStateException("unknown operation type econutered while partisn ImageOpChain");
            }
            o.setName(op.getName());
            o.setChain(this);
        }
    }

    public static class ProtobufConv implements ProtobufConverter<ImageOpChain> {

        public Message createMessage(ImageOpChain obj) {
            return obj.getBuilder().build();
        }

        public ImageOpChain createObject(Message msg) {
            return new ImageOpChain((ImageOpDto.ImageOpChain) msg);
        }

    }
}