All Downloads are FREE. Search and download functionalities are using the official Maven repository.

maltcms.commands.distances.dtw.ADynamicTimeWarp Maven / Gradle / Ivy

Go to download

Similarities for Feature Vectors and Time Series thereof, such as Cosine and Dynamic Time Warping.

The newest version!
/*
 * Maltcms, modular application toolkit for chromatography-mass spectrometry.
 * Copyright (C) 2008-2014, The authors of Maltcms. All rights reserved.
 *
 * Project website: http://maltcms.sf.net
 *
 * Maltcms may be used under the terms of either the
 *
 * GNU Lesser General Public License (LGPL)
 * http://www.gnu.org/licenses/lgpl.html
 *
 * or the
 *
 * Eclipse Public License (EPL)
 * http://www.eclipse.org/org/documents/epl-v10.php
 *
 * As a user/recipient of Maltcms, you may choose which license to receive the code
 * under. Certain files or entire directories may not be covered by this
 * dual license, but are subject to licenses compatible to both LGPL and EPL.
 * License exceptions are explicitly declared in all relevant files or in a
 * LICENSE file in the relevant directories.
 *
 * Maltcms 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. Please consult the relevant license documentation
 * for details.
 */
package maltcms.commands.distances.dtw;

import cross.Factory;
import cross.annotations.Configurable;
import cross.datastructures.StatsMap;
import cross.datastructures.Vars;
import cross.datastructures.fragments.IFileFragment;
import cross.datastructures.tools.EvalTools;
import cross.datastructures.tuple.Tuple2D;
import cross.datastructures.tuple.Tuple2DI;
import cross.datastructures.workflow.IWorkflow;
import cross.datastructures.workflow.WorkflowSlot;
import cross.exception.ResourceNotAvailableException;
import cross.tools.StringTools;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import lombok.Data;

import maltcms.commands.distances.DtwRecurrence;
import maltcms.commands.distances.PairwiseFeatureSimilarity;
import maltcms.commands.scanners.ArrayStatsScanner;
import maltcms.datastructures.alignment.AnchorPairSet;
import maltcms.datastructures.array.ArrayFactory;
import maltcms.datastructures.array.IArrayD2Double;
import maltcms.datastructures.fragments.PairwiseAlignment;
import maltcms.datastructures.ms.IAnchor;
import maltcms.tools.ImageTools;
import maltcms.tools.MaltcmsTools;
import org.apache.commons.configuration.Configuration;
import org.jdom2.Element;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.statistics.HistogramDataset;
import org.slf4j.LoggerFactory;
import ucar.ma2.Array;
import ucar.ma2.ArrayByte;
import ucar.ma2.ArrayDouble;
import ucar.ma2.MAMath;

/**
 * Base class for dynamic time warping implementations.
 *
 * @author Nils Hoffmann
 * 
 */

@Data
public abstract class ADynamicTimeWarp implements IDynamicTimeWarp {

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(ADynamicTimeWarp.class);

//    @Configurable(name = "alignment.algorithm.windowsize")
//    protected transient double maxdeviation = 1.0d;
    /**
     *
     */
    protected transient IArrayD2Double alignment = null;
    /**
     *
     */
    protected transient IArrayD2Double distance = null;
    /**
     *
     */
    protected transient ArrayByte.D2 predecessors = null;
    /**
     *
     */
    protected transient IFileFragment resF = null;
    /**
     *
     */
    protected transient IFileFragment refF = null;
    /**
     *
     */
    protected transient IFileFragment queryF = null;
    /**
     *
     */
    protected transient ArrayDouble.D0 result = null;
    /**
     *
     */
    protected transient ArrayDouble.D1 resultVector = null;
    /**
     *
     */
    protected transient int ref_num_scans = 0;
    /**
     *
     */
    protected transient int query_num_scans = 0;
//    @Configurable(name = "alignment.cumulative.distance")
    /**
     *
     */
    protected DtwRecurrence recurrence = new DtwRecurrence();
    /**
     *
     */
    @Configurable(name = "var.scan_acquisition_time")
    protected String scan_acquisition_time = "scan_acquisition_time";
//    @Configurable(name = "alignment.pairwise.distance")
    /**
     *
     */
    protected PairwiseFeatureSimilarity pairwiseFeatureSimilarity = new PairwiseFeatureSimilarity();
    private StatsMap statsMap;
    private boolean useAnchors = true;
    private int anchorRadius = 10;
    private int minScansBetweenAnchors = 10;
    private IWorkflow workflow;
    @Configurable(name = "alignment.precalculatePairwiseDistances",
            value = "true")
    private boolean precalculatePairwiseDistances = true;
    private PairwiseAlignment pa;
    @Configurable(name = "alignment.globalBand", value = "true")
    private boolean globalBand = true;
    private double bandWidthPercentage = 1.0d;
    private String extension = "";
    @Configurable(name = "alignment.saveLayoutImage", value = "false")
    private boolean saveLayoutImage = false;
    private boolean sample = false;
    private boolean saveDtwMatrix = false;
    private boolean savePairwiseSimilarityMatrix = false;
    private boolean normalizeAlignmentValue = false;

    /**
     * 

Constructor for ADynamicTimeWarp.

*/ public ADynamicTimeWarp() { super(); } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#align(cross.datastructures * .tuple.Tuple2D, maltcms.datastructures.AnchorPairSet, double) */ /** {@inheritDoc} */ @Override public IArrayD2Double align(final Tuple2D, List> tuple, final AnchorPairSet ris, final double maxdev1, final ArrayDouble.D1 sat_ref, final ArrayDouble.D1 sat_query) { EvalTools.notNull(tuple, this); // log.info("Starting pairwise alignment!"); final List ref = tuple.getFirst(); final List query = tuple.getSecond(); EvalTools.neqI(0, ref.size(), this); EvalTools.neqI(0, query.size(), this); final int rows = ref.size(); final int cols = query.size(); if (this.pairwiseFeatureSimilarity.getSimilarityFunction().minimize()) { log.debug("Initializing matrices with {}", Double.POSITIVE_INFINITY); initMatrices(rows, cols, Double.POSITIVE_INFINITY, ris, maxdev1); } else { log.debug("Initializing matrices with {}", Double.NEGATIVE_INFINITY); initMatrices(rows, cols, Double.NEGATIVE_INFINITY, ris, maxdev1); } //saveLayoutImage(ris,"test"); EvalTools.notNull(new Object[]{this.distance, this.alignment}, this); final long start = System.currentTimeMillis(); if (this.precalculatePairwiseDistances) { log.info("Precalculating pairwise distances/similarities"); this.pairwiseFeatureSimilarity.calculatePairwiseDistances( this.distance, sat_ref, sat_query, ref, query); } calculateCumulativeDistances(this.distance, this.alignment, rows, cols, ref, query, sat_ref, sat_query, this.predecessors); // log.info(alignment.toString()); final long time = System.currentTimeMillis() - start; if (getStatsMap() != null) { getStatsMap().put("totalMatrixCalculationTime", (double) time); } log.info("Time to calculate alignment matrices: {} milliseconds", time); log.debug("Shape of alignment: {} X {}, sx {}, sy {}", new Object[]{this.alignment.rows(), this.alignment.columns(), rows, cols}); log.info( "Value of alignment[{}][{}]={}", new Object[]{ this.alignment.rows() - 1, this.alignment.columns() - 1, this.alignment.get(this.alignment.rows() - 1, this.alignment.columns() - 1)}); return this.alignment; } private void samplePWDistance( final Tuple2D, List> tuple, final ArrayDouble.D1 sat_ref, final ArrayDouble.D1 sat_query, int samples, int bins, String name) { Random r1 = new Random(); Random r2 = new Random(); HistogramDataset hd = new HistogramDataset(); double[] values = new double[samples]; for (int i = 0; i < samples; i++) { int i1 = r1.nextInt(tuple.getFirst().size()); int i2 = r2.nextInt(tuple.getSecond().size()); values[i] = this.pairwiseFeatureSimilarity.getDistance(i1, i2, sat_ref.get(i1), sat_query.get(i2), tuple.getFirst().get(i1), tuple.getSecond().get(i2)); log.debug("Sampling {},{} = {}", new Object[]{i1, i2, values[i]}); } hd.addSeries(name, values, bins); JFreeChart jfc = ChartFactory.createHistogram( "Histogram of " + name + " using " + this.pairwiseFeatureSimilarity.getSimilarityFunction().getClass().getName(), "value", "count", hd, PlotOrientation.VERTICAL, true, true, true); ImageTools.writeImage(jfc, new File(getWorkflow().getOutputDirectory(this), name + "-pw-histogram.png"), 1024, 768); } /* * (non-Javadoc) * * @see cross.io.misc.IXMLSerializable#appendXML(org.jdom.Element) */ /** {@inheritDoc} */ @Override public void appendXML(final Element e) { } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#apply(cross.datastructures * .fragments.FileFragment, cross.datastructures.fragments.FileFragment) */ /** {@inheritDoc} */ @Override public IFileFragment apply(final IFileFragment a, final IFileFragment b) { log.info( "#############################################################################"); final String s = this.getClass().getName(); log.info("# {} running", s); log.info( "#############################################################################"); log.info("LHS: {}", a.getName()); log.info("RHS: {}", b.getName()); EvalTools.notNull(new Object[]{a, b}, this); IFileFragment alignmentFragment = calcAlignment(new Tuple2D<>( a, b)); return alignmentFragment; } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#apply(cross.datastructures * .tuple.Tuple2D) */ /** {@inheritDoc} */ @Override public Array[] apply(final Tuple2D t) { EvalTools.notNull(t, this); EvalTools.notNull(new Object[]{this.refF, this.queryF}, this); this.resF = apply(this.refF, this.queryF); EvalTools.notNull(this.result, this); return new Array[]{this.result}; } private AnchorPairSet buildAnchorPairSet( final Tuple2D t) { log.debug("Loading anchors for {} and {}", t.getFirst().getUri(), t.getSecond().getUri()); Tuple2D, List> ris = null; AnchorPairSet aps; if (useAnchors) { ris = MaltcmsTools.getAnchors(t.getFirst(), t.getSecond()); if ((ris.getFirst().size() > 0) && (ris.getSecond().size() > 0)) { log.info("Using {} alignment anchors", ris.getFirst().size()); aps = new AnchorPairSet(ris.getFirst(), ris.getSecond(), this.ref_num_scans, this.query_num_scans, minScansBetweenAnchors); for (final Tuple2D ta : aps.getCorrespondingScans()) { log.debug("{}<->{}", ta.getFirst(), ta.getSecond()); } return aps; } } //return default anchors at 0,0 and N-1,M-1 aps = new AnchorPairSet(new LinkedList(), new LinkedList(), ref_num_scans, query_num_scans, minScansBetweenAnchors); return aps; } /** * @param t * @return */ private IFileFragment calcAlignment( final Tuple2D t) { this.pa = new PairwiseAlignment(); this.pa.setWorkflow(getWorkflow()); this.pa.setSaveCDM(saveDtwMatrix); this.pa.setSavePWDM(savePairwiseSimilarityMatrix); this.pa.setNormalizeByMapLength(normalizeAlignmentValue); this.pa.setFileFragments(t.getFirst(), t.getSecond(), this.getClass()); final Tuple2D, List> tuple = createTuple(t); final AnchorPairSet aps = buildAnchorPairSet(t); final ArrayDouble.D1 sat_ref = getScanAcquisitionTime(t.getFirst()); final ArrayDouble.D1 sat_query = getScanAcquisitionTime(t.getSecond()); if (sample) { samplePWDistance( tuple, sat_ref, sat_query, (int) Math.ceil(0.01 * tuple.getFirst().size() * tuple.getSecond().size()), 10, StringTools.removeFileExt(t.getFirst().getName()) + " " + StringTools.removeFileExt(t.getSecond().getName())); } // calculate the alignment matrix this.alignment = align(tuple, aps, this.bandWidthPercentage, sat_ref, sat_query); EvalTools.notNull(this.alignment, this); // capture state in pairwise alignment this.pa.setTraceMatrix(this.predecessors); this.pa.setAlignment(this.alignment); this.pa.setPairwiseDistances(this.distance); // Possibly take this out final ArrayStatsScanner ass = Factory.getInstance().getObjectFactory(). instantiate(ArrayStatsScanner.class); final StatsMap sm = ass.apply(new Array[]{this.distance.flatten(). getSecond()})[0]; this.pa.setIsMinimizing(minimize()); this.pa.setCumulativeDistance(this.recurrence); this.pa.setPairwiseDistance(getPairwiseFeatureSimilarity()); this.pa.setNumberOfScansReference(this.ref_num_scans); this.pa.setNumberOfScansQuery(this.query_num_scans); this.pa.setAnchors(aps); if (getStatsMap() != null) { getStatsMap().put("avgPWValue", sm.get(Vars.Mean.toString())); getStatsMap().put("minPWValue", sm.get(Vars.Min.toString())); getStatsMap().put("maxPWValue", sm.get(Vars.Max.toString())); getStatsMap().put("nanchors", (double) (aps.getSize() - 2)); getStatsMap().put("lhsNscans", (double) this.ref_num_scans); getStatsMap().put("rhsNscans", (double) this.query_num_scans); getStatsMap().put("longestPath", (double) (this.ref_num_scans + this.query_num_scans - 1)); getStatsMap().put("w_exp", this.recurrence.getExpansionWeight()); getStatsMap().put("w_comp", this.recurrence.getCompressionWeight()); getStatsMap().put("w_diag", this.recurrence.getDiagonalWeight()); getStatsMap().put("gap_global", this.recurrence.getGlobalGapPenalty()); getStatsMap().put("matrixElements", (double) this.alignment.getNumberOfStoredElements()); } // calculate traceback, store path etc. this.resF = this.pa.provideFileFragment(); this.result = this.pa.getResult(); this.resultVector = this.pa.getResultVector(); if (getStatsMap() != null) { getStatsMap().put("pathLength", (double) pa.getPath().size()); } // add the pairwise alignment to bookkeeping // MaltcmsTools.addPairwiseAlignment(t.getFirst(), t.getSecond(), // this.resF, this.extension); // save the path to csv // pt.savePathCSV(this.resF, this.alignment, pa.getPath(), // getWorkflow()); final IFileFragment forwardAlignment = saveState(this.resF); if (this.saveLayoutImage) { saveLayoutImage(aps, StringTools.removeFileExt(forwardAlignment.getName())); } // this.alignment = null; // this.distance = null; // this.predecessors = null; t.getFirst().clearArrays(); t.getSecond().clearArrays(); return forwardAlignment; } /** * @param aps * @param alignmentName */ private void saveLayoutImage(final AnchorPairSet aps, final String alignmentName) { final ArrayFactory f = Factory.getInstance().getObjectFactory(). instantiate(ArrayFactory.class); final BufferedImage bi = f.createLayoutImage(this.distance); final Color c = Color.white; final Graphics2D g2 = (Graphics2D) bi.getGraphics(); if (this.pa != null) { final List l = this.pa.getPath(); final GeneralPath gp = new GeneralPath(); gp.moveTo(0, 0); for (final Tuple2DI pair : l) { gp.lineTo(pair.getSecond(), pair.getFirst()); } g2.setColor(c); g2.draw(gp); } // final Color d = new Color(0, 0, 255, 128); // final List l2 = this.pa.getInterppath(); // final GeneralPath gp2 = new GeneralPath(); // gp2.moveTo(0, 0); // for (final Tuple2DI pair : l2) { // gp2.lineTo(pair.getSecond(), pair.getFirst()); // } // g2.setColor(d); // g2.draw(gp2); g2.setColor(Color.RED); for (final Tuple2D pair : aps.getCorrespondingScans()) { g2.fillRect(pair.getSecond(), pair.getFirst(), 1, 1); } try { ImageIO.write(bi, "PNG", new File(getWorkflow().getOutputDirectory( this), alignmentName + "_matrixLayout.png")); } catch (final IOException e) { log.warn(e.getLocalizedMessage()); } } /** *

calculateCumulativeDistances.

* * @param distance2 a {@link maltcms.datastructures.array.IArrayD2Double} object. * @param alignment2 a {@link maltcms.datastructures.array.IArrayD2Double} object. * @param vrows a int. * @param vcols a int. * @param ref a {@link java.util.List} object. * @param query a {@link java.util.List} object. * @param sat_ref a {@link ucar.ma2.ArrayDouble.D1} object. * @param sat_query a {@link ucar.ma2.ArrayDouble.D1} object. * @param predecessors a {@link ucar.ma2.ArrayByte.D2} object. */ protected void calculateCumulativeDistances(final IArrayD2Double distance2, final IArrayD2Double alignment2, final int vrows, final int vcols, final List ref, final List query, final ArrayDouble.D1 sat_ref, final ArrayDouble.D1 sat_query, final ArrayByte.D2 predecessors) { final long start = System.currentTimeMillis(); log.debug("Calculating the alignment matrix!"); EvalTools.notNull(new Object[]{distance2, alignment2}, this); log.debug("Number of query scans {}", query.size()); log.debug("Number of ref scans {}", ref.size()); // cumulativeDistance.set(compression_penalty, expansion_penalty, // diagonal_penalty); log.debug("Set up anchors!"); final long elements = this.alignment.getNumberOfStoredElements(); log.info("Calculating {} pairwise scores/costs", elements); if (precalculatePairwiseDistances) { for (int i = 0; i < this.alignment.rows(); i++) { final int[] bounds = this.alignment.getColumnBounds(i); for (int j = bounds[0]; j < bounds[0] + bounds[1]; j++) { final double sat_r = sat_ref == null ? -1 : sat_ref.get(i); final double sat_q = sat_query == null ? -1 : sat_query.get(j); this.recurrence.eval(i, j, alignment2, this.pairwiseFeatureSimilarity.getDistance(i, j, sat_r, sat_q, ref.get(i), query.get(j)), predecessors); } } } else { for (int i = 0; i < this.alignment.rows(); i++) { final int[] bounds = this.alignment.getColumnBounds(i); for (int j = bounds[0]; j < bounds[0] + bounds[1]; j++) { final double sat_r = sat_ref == null ? -1 : sat_ref.get(i); final double sat_q = sat_query == null ? -1 : sat_query.get(j); distance2.set(i, j, this.pairwiseFeatureSimilarity.getDistance(i, j, sat_r, sat_q, ref.get(i), query.get(j))); this.recurrence.eval(i, j, alignment2, distance2.get(i, j), predecessors); } } } // log.info("{}%", (int) (Math.ceil(percentDone * 100.0d))); final long time = System.currentTimeMillis() - start; log.debug("Calculated cumulative distance in {} milliseconds", time); if (getStatsMap() != null) { getStatsMap().put("alignmentMatrixCalculationTime", (double) time); } } /** {@inheritDoc} */ @Override public void configure(final Configuration cfg) { log.debug("Configure called on {}", this.getClass().getName()); this.scan_acquisition_time = cfg.getString("var.scan_acquisition_time", "scan_acquisition_time"); } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#getCumulativeDistance() */ /** *

getCumulativeDistance.

* * @return a {@link maltcms.commands.distances.DtwRecurrence} object. */ public DtwRecurrence getCumulativeDistance() { return this.recurrence; } /** *

Getter for the field extension.

* * @return a {@link java.lang.String} object. */ public String getExtension() { return this.extension; } /* * (non-Javadoc) * * @see cross.datastructures.workflow.IWorkflowElement#getWorkflow() */ /** {@inheritDoc} */ @Override public IWorkflow getWorkflow() { return this.workflow; } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#getResultFileFragment() */ /** {@inheritDoc} */ @Override public IFileFragment getResultFileFragment() { EvalTools.notNull(this.resF, this); return this.resF; } private ArrayDouble.D1 getScanAcquisitionTime(final IFileFragment f) { Array sat_ref = null; try { sat_ref = f.getChild(this.scan_acquisition_time).getArray(); EvalTools.eqI(sat_ref.getRank(), 1, this); final ArrayDouble.D1 ret = new ArrayDouble.D1(sat_ref.getShape()[0]); MAMath.copyDouble(ret, sat_ref); return ret; } catch (final ResourceNotAvailableException e) { log.debug("Could not load " + this.scan_acquisition_time + " for " + f.getName()); } // } return null; } /* * (non-Javadoc) * * @see cross.datastructures.workflow.IWorkflowElement#getWorkflowSlot() */ /** {@inheritDoc} */ @Override public WorkflowSlot getWorkflowSlot() { return WorkflowSlot.ALIGNMENT; } /** *

initMatrices.

* * @param rows a int. * @param cols a int. * @param value a {@link java.lang.Double} object. * @param aps a {@link maltcms.datastructures.alignment.AnchorPairSet} object. * @param band a double. */ protected void initMatrices(final int rows, final int cols, final Double value, final AnchorPairSet aps, final double band) { final long start = System.currentTimeMillis(); final ArrayFactory f = Factory.getInstance().getObjectFactory(). instantiate(ArrayFactory.class); final IArrayD2Double parts1 = f.create(rows, cols, aps, this.anchorRadius, band, value.doubleValue(), this.globalBand); this.alignment = parts1; final IArrayD2Double parts2 = f.createSharedLayout(parts1); this.distance = parts2; this.predecessors = new ArrayByte.D2(rows, cols); log.debug("Alignment matrix has {} rows and {} columns", parts1.rows(), parts2.columns()); final long time = System.currentTimeMillis() - start; if (getStatsMap() != null) { getStatsMap().put("alignmentMatrixInitTime", (double) time); } log.debug("Initialized matrices in {} milliseconds", time); } /* * (non-Javadoc) * * @see maltcms.commands.distances.dtw.IDynamicTimeWarp#minimize() */ /** {@inheritDoc} */ @Override public boolean minimize() { final boolean b = (this.pairwiseFeatureSimilarity != null) ? this.pairwiseFeatureSimilarity.getSimilarityFunction().minimize() : true; return b; } /** *

saveState.

* * @param pa a {@link cross.datastructures.fragments.IFileFragment} object. * @return a {@link cross.datastructures.fragments.IFileFragment} object. */ protected IFileFragment saveState(final IFileFragment pa) { final long start = System.currentTimeMillis(); pa.save(); System.gc(); final long time = System.currentTimeMillis() - start; log.debug("Saved state in {} milliseconds", time); return pa; } /** *

Setter for the field extension.

* * @param newExtension a {@link java.lang.String} object. */ public void setExtension(final String newExtension) { if (newExtension != null) { this.extension = newExtension; } else { this.extension = ""; } } /* * (non-Javadoc) * * @see * maltcms.commands.distances.dtw.IDynamicTimeWarp#setFileFragments(cross * .datastructures.fragments.FileFragment, * cross.datastructures.fragments.FileFragment) */ /** {@inheritDoc} */ @Override public void setFileFragments(final IFileFragment a, final IFileFragment b) { EvalTools.notNull(new Object[]{a, b}, this); this.refF = a; this.queryF = b; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy