summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/result
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/lmu/ifi/dbs/elki/result')
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/AbstractHierarchicalResult.java62
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/AprioriResult.java79
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/BasicResult.java66
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/CollectionResult.java109
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/DBIDSelection.java58
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/DiscardResultHandler.java45
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/HierarchicalResult.java49
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/HistogramResult.java57
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/IterableResult.java45
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/KMLOutputHandler.java409
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/KNNDistanceOrderResult.java62
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/OrderingFromDataStore.java169
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/OrderingResult.java49
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/PixmapResult.java47
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/RangeSelection.java77
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ReferencePointsResult.java58
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/Result.java49
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultAdapter.java36
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultHandler.java37
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultHierarchy.java162
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultListener.java57
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultProcessor.java41
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultUtil.java304
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/ResultWriter.java189
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/SelectionResult.java72
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/SettingsResult.java59
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderEntry.java62
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderResult.java387
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/optics/DoubleDistanceClusterOrderEntry.java152
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/optics/GenericClusterOrderEntry.java145
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/optics/package-info.java26
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/BasicOutlierScoreMeta.java178
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/InvertedOutlierScoreMeta.java91
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/OrderingFromRelation.java115
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/OutlierResult.java97
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/OutlierScoreMeta.java83
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/ProbabilisticOutlierScore.java125
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/QuotientOutlierScoreMeta.java71
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/outlier/package-info.java27
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/package-info.java33
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/MultipleFilesOutput.java187
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/SingleStreamOutput.java148
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/StreamFactory.java49
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriteable.java39
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriter.java431
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterStream.java255
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterWriterInterface.java61
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/naming/NamingScheme.java42
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/naming/SimpleEnumeratingScheme.java109
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/naming/package-info.java26
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/package-info.java27
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterDoubleDoublePair.java53
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectArray.java51
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectComment.java50
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectInline.java54
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterPair.java64
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTextWriteable.java44
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTriple.java72
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterVector.java49
-rw-r--r--src/de/lmu/ifi/dbs/elki/result/textwriter/writers/package-info.java27
60 files changed, 5877 insertions, 0 deletions
diff --git a/src/de/lmu/ifi/dbs/elki/result/AbstractHierarchicalResult.java b/src/de/lmu/ifi/dbs/elki/result/AbstractHierarchicalResult.java
new file mode 100644
index 00000000..113779a1
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/AbstractHierarchicalResult.java
@@ -0,0 +1,62 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Abstract class for a result object with hierarchy
+ *
+ * @author Erich Schubert
+ */
+public abstract class AbstractHierarchicalResult implements HierarchicalResult {
+ /**
+ * The hierarchy storage.
+ */
+ private ResultHierarchy hierarchy;
+
+ /**
+ * Constructor.
+ */
+ public AbstractHierarchicalResult() {
+ super();
+ this.hierarchy = new ResultHierarchy();
+ }
+
+ @Override
+ public final ResultHierarchy getHierarchy() {
+ return hierarchy;
+ }
+
+ @Override
+ public final void setHierarchy(ResultHierarchy hierarchy) {
+ this.hierarchy = hierarchy;
+ }
+
+ /**
+ * Add a child result.
+ *
+ * @param child Child result
+ */
+ public void addChildResult(Result child) {
+ hierarchy.add(this, child);
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/AprioriResult.java b/src/de/lmu/ifi/dbs/elki/result/AprioriResult.java
new file mode 100644
index 00000000..3a8f2aec
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/AprioriResult.java
@@ -0,0 +1,79 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Result class for Apriori Algorithm.
+ *
+ * @author Erich Schubert
+ *
+ */
+public class AprioriResult extends BasicResult {
+ /**
+ * The frequent itemsets.
+ */
+ private List<BitSet> solution;
+
+ /**
+ * The supports of all itemsets.
+ */
+ private Map<BitSet, Integer> supports;
+
+ /**
+ * Constructor.
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param solution Frequent itemsets
+ * @param supports Supports for the itemsets
+ */
+ public AprioriResult(String name, String shortname, List<BitSet> solution, Map<BitSet, Integer> supports) {
+ super(name, shortname);
+ this.solution = solution;
+ this.supports = supports;
+ }
+
+ /**
+ * Returns the frequent item sets.
+ *
+ * @return the frequent item sets.
+ */
+ public List<BitSet> getSolution() {
+ return solution;
+ }
+
+ /**
+ * Returns the frequencies of the frequent item sets.
+ *
+ * @return the frequencies of the frequent item sets
+ */
+ public Map<BitSet, Integer> getSupports() {
+ return supports;
+ }
+
+ // TODO: text writer for AprioriResult!
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/BasicResult.java b/src/de/lmu/ifi/dbs/elki/result/BasicResult.java
new file mode 100644
index 00000000..dc0f048a
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/BasicResult.java
@@ -0,0 +1,66 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Basic class for a result. Much like AbstractHierarchicalResult, except it
+ * stores the required short and long result names.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ */
+// TODO: getter, setter for result names? Merge with AbstractHierarchicalResult?
+public class BasicResult extends AbstractHierarchicalResult {
+ /**
+ * Result name, for presentation
+ */
+ private String name;
+
+ /**
+ * Result name, for output
+ */
+ private String shortname;
+
+ /**
+ * Result constructor.
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ */
+ public BasicResult(String name, String shortname) {
+ super();
+ this.name = name;
+ this.shortname = shortname;
+ }
+
+ @Override
+ public final String getLongName() {
+ return name;
+ }
+
+ @Override
+ public final String getShortName() {
+ return shortname;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/CollectionResult.java b/src/de/lmu/ifi/dbs/elki/result/CollectionResult.java
new file mode 100644
index 00000000..eb6c3fe3
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/CollectionResult.java
@@ -0,0 +1,109 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Simple 'collection' type of result.
+ * For example, a list of NumberVectors.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ *
+ * @param <O> data type
+ */
+public class CollectionResult<O> extends BasicResult implements IterableResult<O> {
+ /**
+ * The collection represented.
+ */
+ private Collection<O> col;
+
+ /**
+ * Meta information (printed into the header)
+ */
+ private Collection<String> header;
+
+ /**
+ * Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Collection represented
+ * @param header Auxiliary information for result headers
+ */
+ public CollectionResult(String name, String shortname, Collection<O> col, Collection<String> header) {
+ super(name, shortname);
+ this.col = col;
+ this.header = header;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Collection represented
+ */
+ public CollectionResult(String name, String shortname, Collection<O> col) {
+ this(name, shortname, col, new ArrayList<String>());
+ }
+
+ /**
+ * Add header information
+ *
+ * @param s Header information string
+ */
+ public void addHeader(String s) {
+ header.add(s);
+ }
+
+ /**
+ * Get header information
+ *
+ * @return header information of the result
+ */
+ public Collection<String> getHeader() {
+ return header;
+ }
+
+ /**
+ * Implementation of the {@link IterableResult} interface, using the backing collection.
+ */
+ @Override
+ public Iterator<O> iterator() {
+ return col.iterator();
+ }
+
+ /**
+ * Get the collection size.
+ *
+ * @return Collection size
+ */
+ public int size() {
+ return col.size();
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/DBIDSelection.java b/src/de/lmu/ifi/dbs/elki/result/DBIDSelection.java
new file mode 100644
index 00000000..41c50bb1
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/DBIDSelection.java
@@ -0,0 +1,58 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+
+/**
+ * Class representing selected Database-IDs and/or a selection range.
+ *
+ * @author Heidi Kolb
+ * @author Erich Schubert
+ */
+public class DBIDSelection {
+ /**
+ * Selected IDs
+ */
+ private DBIDs selectedIds = DBIDUtil.EMPTYDBIDS;
+
+ /**
+ * Constructor with new object IDs.
+ *
+ * @param selectedIds selection IDs
+ */
+ public DBIDSelection(DBIDs selectedIds) {
+ super();
+ this.selectedIds = selectedIds;
+ }
+
+ /**
+ * Getter for the selected IDs
+ *
+ * @return DBIDs
+ */
+ public DBIDs getSelectedIds() {
+ return selectedIds;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/DiscardResultHandler.java b/src/de/lmu/ifi/dbs/elki/result/DiscardResultHandler.java
new file mode 100644
index 00000000..a9f4a589
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/DiscardResultHandler.java
@@ -0,0 +1,45 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/**
+ * A dummy result handler that discards the actual result, for use in
+ * benchmarks.
+ *
+ * @author Erich Schubert
+ */
+public class DiscardResultHandler implements ResultHandler {
+ /**
+ * Default constructor.
+ */
+ public DiscardResultHandler() {
+ // empty constructor
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result newResult) {
+ // always ignore the new result.
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/HierarchicalResult.java b/src/de/lmu/ifi/dbs/elki/result/HierarchicalResult.java
new file mode 100644
index 00000000..a604d287
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/HierarchicalResult.java
@@ -0,0 +1,49 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Result with an internal hierarchy.
+ *
+ * Note: while this often seems a bit clumsy to use, the benefit of having this
+ * delegate is to keep the APIs simpler, and thus make ELKI development easier.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.composedOf ResultHierarchy
+ */
+public interface HierarchicalResult extends Result {
+ /**
+ * Get the objects current hierarchy - may be {@code null}!
+ *
+ * @return current hierarchy. May be {@code null}!
+ */
+ public ResultHierarchy getHierarchy();
+
+ /**
+ * Set (exchange) the hierarchy implementation (e.g. after merging!)
+ *
+ * @param hierarchy New hierarchy
+ */
+ public void setHierarchy(ResultHierarchy hierarchy);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/HistogramResult.java b/src/de/lmu/ifi/dbs/elki/result/HistogramResult.java
new file mode 100644
index 00000000..94c1ce7b
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/HistogramResult.java
@@ -0,0 +1,57 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Collection;
+
+/**
+ * Histogram result.
+ *
+ * @author Erich Schubert
+ *
+ * @param <O> Object class (e.g. {@link de.lmu.ifi.dbs.elki.data.DoubleVector})
+ */
+public class HistogramResult<O> extends CollectionResult<O> {
+ /**
+ * Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Collection
+ */
+ public HistogramResult(String name, String shortname, Collection<O> col) {
+ super(name, shortname, col);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Collection
+ * @param header Header information
+ */
+ public HistogramResult(String name, String shortname, Collection<O> col, Collection<String> header) {
+ super(name, shortname, col, header);
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/IterableResult.java b/src/de/lmu/ifi/dbs/elki/result/IterableResult.java
new file mode 100644
index 00000000..4affcb25
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/IterableResult.java
@@ -0,0 +1,45 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Iterator;
+
+/**
+ * Interface of an "iterable" result (e.g. a list, table) that can be printed one-by-one.
+ * (At least when the class O is TextWriteable)
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ *
+ * @param <O> object class.
+ */
+public interface IterableResult<O> extends Result, Iterable<O> {
+ /**
+ * Retrieve an iterator for the result.
+ *
+ * @return iterator
+ */
+ @Override
+ public Iterator<O> iterator();
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/KMLOutputHandler.java b/src/de/lmu/ifi/dbs/elki/result/KMLOutputHandler.java
new file mode 100644
index 00000000..2d456f8e
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/KMLOutputHandler.java
@@ -0,0 +1,409 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import de.lmu.ifi.dbs.elki.data.spatial.Polygon;
+import de.lmu.ifi.dbs.elki.data.spatial.PolygonsObject;
+import de.lmu.ifi.dbs.elki.data.type.TypeUtil;
+import de.lmu.ifi.dbs.elki.database.Database;
+import de.lmu.ifi.dbs.elki.database.ids.ArrayModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.logging.Logging;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+import de.lmu.ifi.dbs.elki.result.outlier.OutlierResult;
+import de.lmu.ifi.dbs.elki.utilities.DatabaseUtil;
+import de.lmu.ifi.dbs.elki.utilities.FormatUtil;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.FileParameter;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ObjectParameter;
+import de.lmu.ifi.dbs.elki.utilities.scaling.outlier.OutlierLinearScaling;
+import de.lmu.ifi.dbs.elki.utilities.scaling.outlier.OutlierScalingFunction;
+
+/**
+ * Class to handle KML output.
+ *
+ * @author Erich Schubert
+ */
+// TODO: make configurable color scheme
+public class KMLOutputHandler implements ResultHandler, Parameterizable {
+ /**
+ * Logger class to use.
+ */
+ public static final Logging logger = Logging.getLogger(KMLOutputHandler.class);
+
+ /**
+ * Number of styles to use (lower reduces rendering complexity a bit)
+ */
+ private static final int NUMSTYLES = 20;
+
+ /**
+ * Output file name
+ */
+ File filename;
+
+ /**
+ * Scaling function
+ */
+ OutlierScalingFunction scaling;
+
+ /**
+ * Compatibility mode.
+ */
+ private boolean compat;
+
+ /**
+ * Constructor.
+ *
+ * @param filename Output filename
+ * @param scaling Scaling function
+ * @param compat Compatibility mode
+ */
+ public KMLOutputHandler(File filename, OutlierScalingFunction scaling, boolean compat) {
+ super();
+ this.filename = filename;
+ this.scaling = scaling;
+ this.compat = compat;
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseResult, Result newResult) {
+ ArrayList<OutlierResult> ors = ResultUtil.filterResults(newResult, OutlierResult.class);
+ if(ors.size() > 1) {
+ throw new AbortException("More than one outlier result found. The KML writer only supports a single outlier result!");
+ }
+ if(ors.size() == 1) {
+ Database database = ResultUtil.findDatabase(baseResult);
+ try {
+ XMLOutputFactory factory = XMLOutputFactory.newInstance();
+ ZipOutputStream out = new ZipOutputStream(new FileOutputStream(filename));
+ out.putNextEntry(new ZipEntry("doc.kml"));
+ writeKMLData(factory.createXMLStreamWriter(out), ors.get(0), database);
+ out.closeEntry();
+ out.flush();
+ out.close();
+ }
+ catch(XMLStreamException e) {
+ logger.exception(e);
+ throw new AbortException("XML error in KML output.", e);
+ }
+ catch(IOException e) {
+ logger.exception(e);
+ throw new AbortException("IO error in KML output.", e);
+ }
+ }
+ }
+
+ private void writeKMLData(XMLStreamWriter out, OutlierResult outlierResult, Database database) throws XMLStreamException {
+ Relation<Double> scores = outlierResult.getScores();
+ Relation<PolygonsObject> polys = database.getRelation(TypeUtil.POLYGON_TYPE);
+ Relation<String> labels = DatabaseUtil.guessObjectLabelRepresentation(database);
+
+ Collection<Relation<?>> otherrel = new LinkedList<Relation<?>>(database.getRelations());
+ otherrel.remove(scores);
+ otherrel.remove(polys);
+ otherrel.remove(labels);
+ otherrel.remove(database.getRelation(TypeUtil.DBID));
+
+ ArrayModifiableDBIDs ids = DBIDUtil.newArray(scores.getDBIDs());
+
+ scaling.prepare(outlierResult);
+
+ out.writeStartDocument();
+ out.writeCharacters("\n");
+ out.writeStartElement("kml");
+ out.writeDefaultNamespace("http://earth.google.com/kml/2.2");
+ out.writeStartElement("Document");
+ {
+ // TODO: can we automatically generate more helpful data here?
+ out.writeStartElement("name");
+ out.writeCharacters("ELKI KML output for " + outlierResult.getLongName());
+ out.writeEndElement(); // name
+ writeNewlineOnDebug(out);
+ // TODO: e.g. list the settings in the description?
+ out.writeStartElement("description");
+ out.writeCharacters("ELKI KML output for " + outlierResult.getLongName());
+ out.writeEndElement(); // description
+ writeNewlineOnDebug(out);
+ }
+ {
+ // TODO: generate styles from color scheme
+ for(int i = 0; i < NUMSTYLES; i++) {
+ Color col = getColorForValue(i / (NUMSTYLES - 1.0));
+ out.writeStartElement("Style");
+ out.writeAttribute("id", "s" + i);
+ writeNewlineOnDebug(out);
+ {
+ out.writeStartElement("LineStyle");
+ out.writeStartElement("width");
+ out.writeCharacters("0");
+ out.writeEndElement(); // width
+
+ out.writeEndElement(); // LineStyle
+ }
+ writeNewlineOnDebug(out);
+ {
+ out.writeStartElement("PolyStyle");
+ out.writeStartElement("color");
+ // KML uses AABBGGRR format!
+ out.writeCharacters(String.format("%02x%02x%02x%02x", col.getAlpha(), col.getBlue(), col.getGreen(), col.getRed()));
+ out.writeEndElement(); // color
+ // out.writeStartElement("fill");
+ // out.writeCharacters("1"); // Default 1
+ // out.writeEndElement(); // fill
+ out.writeStartElement("outline");
+ out.writeCharacters("0");
+ out.writeEndElement(); // outline
+ out.writeEndElement(); // PolyStyle
+ }
+ writeNewlineOnDebug(out);
+ out.writeEndElement(); // Style
+ writeNewlineOnDebug(out);
+ }
+ }
+ for(DBID id : outlierResult.getOrdering().iter(ids)) {
+ Double score = scores.get(id);
+ PolygonsObject poly = polys.get(id);
+ String label = labels.get(id);
+ if(score == null) {
+ logger.warning("No score for object " + id);
+ }
+ if(poly == null) {
+ logger.warning("No polygon for object " + id + " - skipping.");
+ continue;
+ }
+ out.writeStartElement("Placemark");
+ {
+ out.writeStartElement("name");
+ out.writeCharacters(score + " " + label);
+ out.writeEndElement(); // name
+ StringBuffer buf = makeDescription(otherrel, id);
+ out.writeStartElement("description");
+ out.writeCData("<div>" + buf.toString() + "</div>");
+ out.writeEndElement(); // description
+ out.writeStartElement("styleUrl");
+ int style = (int) (scaling.getScaled(score) * NUMSTYLES);
+ style = Math.max(0, Math.min(style, NUMSTYLES - 1));
+ out.writeCharacters("#s" + style);
+ out.writeEndElement(); // styleUrl
+ }
+ {
+ out.writeStartElement("Polygon");
+ writeNewlineOnDebug(out);
+ if(compat) {
+ out.writeStartElement("altitudeMode");
+ out.writeCharacters("relativeToGround");
+ out.writeEndElement(); // close altitude mode
+ writeNewlineOnDebug(out);
+ }
+ // First polygon clockwise?
+ boolean first = true;
+ for(Polygon p : poly.getPolygons()) {
+ if(first) {
+ out.writeStartElement("outerBoundaryIs");
+ }
+ else {
+ out.writeStartElement("innerBoundaryIs");
+ }
+ out.writeStartElement("LinearRing");
+ out.writeStartElement("coordinates");
+
+ // Reverse anti-clockwise polygons.
+ boolean reverse = (p.testClockwise() >= 0);
+ Iterator<Vector> it = reverse ? p.descendingIterator() : p.iterator();
+ while(it.hasNext()) {
+ Vector v = it.next();
+ out.writeCharacters(FormatUtil.format(v.getArrayRef(), ","));
+ if(compat && (v.getDimensionality() == 2)) {
+ out.writeCharacters(",500");
+ }
+ out.writeCharacters(" ");
+ }
+ out.writeEndElement(); // close coordinates
+ out.writeEndElement(); // close LinearRing
+ out.writeEndElement(); // close *BoundaryIs
+ first = false;
+ }
+ writeNewlineOnDebug(out);
+ out.writeEndElement(); // Polygon
+ }
+ out.writeEndElement(); // Placemark
+ writeNewlineOnDebug(out);
+ }
+ out.writeEndElement(); // Document
+ out.writeEndElement(); // kml
+ out.writeEndDocument();
+ }
+
+ /**
+ * Make an HTML description.
+ *
+ * @param relations Relations
+ * @param id Object ID
+ * @return Buffer
+ */
+ private StringBuffer makeDescription(Collection<Relation<?>> relations, DBID id) {
+ StringBuffer buf = new StringBuffer();
+ for(Relation<?> rel : relations) {
+ Object o = rel.get(id);
+ if(o == null) {
+ continue;
+ }
+ String s = o.toString();
+ // FIXME: strip html characters
+ if(s != null) {
+ if(buf.length() > 0) {
+ buf.append("<br />");
+ }
+ buf.append(s);
+ }
+ }
+ return buf;
+ }
+
+ /**
+ * Print a newline when debugging.
+ *
+ * @param out Output XML stream
+ * @throws XMLStreamException
+ */
+ private void writeNewlineOnDebug(XMLStreamWriter out) throws XMLStreamException {
+ if(logger.isDebugging()) {
+ out.writeCharacters("\n");
+ }
+ }
+
+ private static final Color getColorForValue(double val) {
+ // Color positions
+ double[] pos = new double[] { 0.0, 0.6, 0.8, 1.0 };
+ // Colors at these positions
+ Color[] cols = new Color[] { new Color(0.0f, 0.0f, 0.0f, 0.6f), new Color(0.0f, 0.0f, 1.0f, 0.8f), new Color(1.0f, 0.0f, 0.0f, 0.9f), new Color(1.0f, 1.0f, 0.0f, 1.0f) };
+ assert (pos.length == cols.length);
+ if(val < pos[0]) {
+ val = pos[0];
+ }
+ // Linear interpolation:
+ for(int i = 1; i < pos.length; i++) {
+ if(val <= pos[i]) {
+ Color prev = cols[i - 1];
+ Color next = cols[i];
+ final double mix = (val - pos[i - 1]) / (pos[i] - pos[i - 1]);
+ final int r = (int) ((1 - mix) * prev.getRed() + mix * next.getRed());
+ final int g = (int) ((1 - mix) * prev.getGreen() + mix * next.getGreen());
+ final int b = (int) ((1 - mix) * prev.getBlue() + mix * next.getBlue());
+ final int a = (int) ((1 - mix) * prev.getAlpha() + mix * next.getAlpha());
+ Color col = new Color(r, g, b, a);
+ return col;
+ }
+ }
+ return cols[cols.length - 1];
+ }
+
+ /**
+ * Parameterization class
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ /**
+ * Parameter for scaling functions
+ *
+ * <p>
+ * Key: {@code -kml.scaling}
+ * </p>
+ */
+ public static final OptionID SCALING_ID = OptionID.getOrCreateOptionID("kml.scaling", "Additional scaling function for KML colorization.");
+
+ /**
+ * Parameter for compatibility mode.
+ *
+ * <p>
+ * Key: {@code -kml.compat}
+ * </p>
+ */
+ public static final OptionID COMPAT_ID = OptionID.getOrCreateOptionID("kml.compat", "Use simpler KML objects, compatibility mode.");
+
+ /**
+ * Output file name
+ */
+ File filename;
+
+ /**
+ * Scaling function
+ */
+ OutlierScalingFunction scaling;
+
+ /**
+ * Compatibility mode
+ */
+ boolean compat;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ FileParameter outputP = new FileParameter(OptionID.OUTPUT, FileParameter.FileType.OUTPUT_FILE);
+ outputP.setShortDescription("Filename the KMZ file (compressed KML) is written to.");
+ if(config.grab(outputP)) {
+ filename = outputP.getValue();
+ }
+
+ ObjectParameter<OutlierScalingFunction> scalingP = new ObjectParameter<OutlierScalingFunction>(SCALING_ID, OutlierScalingFunction.class, OutlierLinearScaling.class);
+ if(config.grab(scalingP)) {
+ scaling = scalingP.instantiateClass(config);
+ }
+
+ Flag compatF = new Flag(COMPAT_ID);
+ if(config.grab(compatF)) {
+ compat = compatF.getValue();
+ }
+ }
+
+ @Override
+ protected KMLOutputHandler makeInstance() {
+ return new KMLOutputHandler(filename, scaling, compat);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/KNNDistanceOrderResult.java b/src/de/lmu/ifi/dbs/elki/result/KNNDistanceOrderResult.java
new file mode 100644
index 00000000..8958cac2
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/KNNDistanceOrderResult.java
@@ -0,0 +1,62 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Iterator;
+import java.util.List;
+
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+
+/**
+ * Wraps a list containing the knn distances.
+ *
+ * @author Arthur Zimek
+ * @param <D> the type of Distance used by this Result
+ *
+ */
+public class KNNDistanceOrderResult<D extends Distance<D>> extends BasicResult implements IterableResult<D> {
+ /**
+ * Store the kNN Distances
+ */
+ private final List<D> knnDistances;
+
+ /**
+ * Construct result
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param knnDistances distance list to wrap.
+ */
+ public KNNDistanceOrderResult(String name, String shortname, final List<D> knnDistances) {
+ super(name, shortname);
+ this.knnDistances = knnDistances;
+ }
+
+ /**
+ * Return an iterator.
+ */
+ @Override
+ public Iterator<D> iterator() {
+ return knnDistances.iterator();
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/OrderingFromDataStore.java b/src/de/lmu/ifi/dbs/elki/result/OrderingFromDataStore.java
new file mode 100644
index 00000000..537f04d8
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/OrderingFromDataStore.java
@@ -0,0 +1,169 @@
+package de.lmu.ifi.dbs.elki.result;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import java.util.Collections;
+import java.util.Comparator;
+
+import de.lmu.ifi.dbs.elki.database.datastore.DataStore;
+import de.lmu.ifi.dbs.elki.database.ids.ArrayModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+
+/**
+ * Result class providing an ordering backed by a hashmap.
+ *
+ * @author Erich Schubert
+ *
+ * @param <T> Data type in hash map
+ */
+public class OrderingFromDataStore<T extends Comparable<T>> extends BasicResult implements OrderingResult {
+ /**
+ * HashMap with object values
+ */
+ protected DataStore<? extends T> map;
+
+ /**
+ * Database IDs
+ */
+ protected DBIDs ids;
+
+ /**
+ * Comparator to use when sorting
+ */
+ protected Comparator<T> comparator;
+
+ /**
+ * Factor for ascending (+1) and descending (-1) ordering.
+ */
+ int ascending;
+
+ /**
+ * Constructor with comparator
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param ids DBIDs included
+ * @param map data hash map
+ * @param comparator comparator to use, may be null
+ * @param descending ascending (false) or descending (true) order.
+ */
+ public OrderingFromDataStore(String name, String shortname, DBIDs ids, DataStore<? extends T> map, Comparator<T> comparator, boolean descending) {
+ super(name, shortname);
+ this.map = map;
+ this.ids = ids;
+ this.comparator = comparator;
+ this.ascending = descending ? -1 : 1;
+ }
+
+ /**
+ * Constructor without comparator
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param ids DBIDs included
+ * @param map data hash map
+ * @param descending ascending (false) or descending (true) order.
+ */
+ public OrderingFromDataStore(String name, String shortname, DBIDs ids, DataStore<? extends T> map, boolean descending) {
+ super(name, shortname);
+ this.map = map;
+ this.ids = ids;
+ this.comparator = null;
+ this.ascending = descending ? -1 : 1;
+ }
+
+ /**
+ * Minimal Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param ids DBIDs included
+ * @param map data hash map
+ */
+ public OrderingFromDataStore(String name, String shortname, DBIDs ids, DataStore<? extends T> map) {
+ super(name, shortname);
+ this.map = map;
+ this.ids = ids;
+ this.comparator = null;
+ this.ascending = 1;
+ }
+
+ @Override
+ public DBIDs getDBIDs() {
+ return ids;
+ }
+
+ @Override
+ public IterableIterator<DBID> iter(DBIDs ids) {
+ ArrayModifiableDBIDs sorted = DBIDUtil.newArray(ids);
+ if(comparator != null) {
+ Collections.sort(sorted, new DerivedComparator());
+ }
+ else {
+ Collections.sort(sorted, new ImpliedComparator());
+ }
+ return IterableUtil.fromIterable(sorted);
+ }
+
+ /**
+ * Internal comparator, accessing the map to sort objects
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ protected final class ImpliedComparator implements Comparator<DBID> {
+ @Override
+ public int compare(DBID id1, DBID id2) {
+ T k1 = map.get(id1);
+ T k2 = map.get(id2);
+ assert (k1 != null);
+ assert (k2 != null);
+ return ascending * k1.compareTo(k2);
+ }
+ }
+
+ /**
+ * Internal comparator, accessing the map but then using the provided
+ * comparator to sort objects
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ protected final class DerivedComparator implements Comparator<DBID> {
+ @Override
+ public int compare(DBID id1, DBID id2) {
+ T k1 = map.get(id1);
+ T k2 = map.get(id2);
+ assert (k1 != null);
+ assert (k2 != null);
+ return ascending * comparator.compare(k1, k2);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/OrderingResult.java b/src/de/lmu/ifi/dbs/elki/result/OrderingResult.java
new file mode 100644
index 00000000..57a30e16
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/OrderingResult.java
@@ -0,0 +1,49 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+
+/**
+ * Interface for a result providing an object ordering.
+ *
+ * @author Erich Schubert
+ */
+public interface OrderingResult extends Result {
+ /**
+ * Get the full set of DBIDs this ordering is defined for.
+ *
+ * @return DBIDs
+ */
+ public DBIDs getDBIDs();
+
+ /**
+ * Sort the given ids according to this ordering and return an iterator.
+ *
+ * @param ids Collection of ids.
+ * @return iterator for sorted array of ids
+ */
+ public IterableIterator<DBID> iter(DBIDs ids);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/PixmapResult.java b/src/de/lmu/ifi/dbs/elki/result/PixmapResult.java
new file mode 100644
index 00000000..eae54ad6
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/PixmapResult.java
@@ -0,0 +1,47 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.awt.image.RenderedImage;
+import java.io.File;
+
+/**
+ * Result encapsulating a single image.
+ *
+ * @author Erich Schubert
+ */
+public interface PixmapResult extends Result {
+ /**
+ * Get the image pixmap
+ *
+ * @return the image pixmap
+ */
+ public RenderedImage getImage();
+
+ /**
+ * Get the image result as file (usually a temporary file).
+ *
+ * @return Image file
+ */
+ public File getAsFile();
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/RangeSelection.java b/src/de/lmu/ifi/dbs/elki/result/RangeSelection.java
new file mode 100644
index 00000000..87da7a4b
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/RangeSelection.java
@@ -0,0 +1,77 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
+
+/**
+ * Class representing selected Database-IDs and/or a selection range.
+ *
+ * @author Heidi Kolb
+ * @author Erich Schubert
+ */
+public class RangeSelection extends DBIDSelection {
+ /**
+ * Selection range
+ */
+ private DoubleDoublePair[] ranges = null;
+
+ /**
+ * Constructor.
+ *
+ * @param selectedIds selected IDs
+ */
+ public RangeSelection(DBIDs selectedIds) {
+ super(selectedIds);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param selection
+ * @param ranges
+ */
+ public RangeSelection(DBIDs selection, DoubleDoublePair[] ranges) {
+ super(selection);
+ this.ranges = ranges;
+ }
+
+ /**
+ * Get the selection range.
+ *
+ * @return Selected range. May be null!
+ */
+ public DoubleDoublePair[] getRanges() {
+ return ranges;
+ }
+
+ /**
+ * Get a single selection range.
+ *
+ * @return Selected range. May be null!
+ */
+ public DoubleDoublePair getRange(int dim) {
+ return ranges[dim];
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/ReferencePointsResult.java b/src/de/lmu/ifi/dbs/elki/result/ReferencePointsResult.java
new file mode 100644
index 00000000..5ec78478
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ReferencePointsResult.java
@@ -0,0 +1,58 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Collection;
+
+
+/**
+ * Result used in passing the reference points to the visualizers.
+ *
+ * @author Erich Schubert
+ *
+ * @param <O> data type
+ */
+public class ReferencePointsResult<O> extends CollectionResult<O> {
+ /**
+ * Constructor with collection only.
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Reference Points
+ */
+ public ReferencePointsResult(String name, String shortname, Collection<O> col) {
+ super(name, shortname, col);
+ }
+
+ /**
+ * Full constructor.
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ * @param col Reference Points
+ * @param header Header
+ */
+ public ReferencePointsResult(String name, String shortname, Collection<O> col, Collection<String> header) {
+ super(name, shortname, col, header);
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/Result.java b/src/de/lmu/ifi/dbs/elki/result/Result.java
new file mode 100644
index 00000000..c3fd32ec
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/Result.java
@@ -0,0 +1,49 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Interface for arbitrary result objects.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ * @apiviz.excludeSubtypes
+ */
+public interface Result {
+ /**
+ * A "pretty" name for the result, for use in titles, captions and menus.
+ *
+ * @return result name
+ */
+ // TODO: turn this into an optional annotation? But: no inheritance?
+ public String getLongName();
+
+ /**
+ * A short name for the result, useful for file names.
+ *
+ * @return result name
+ */
+ // TODO: turn this into an optional annotation? But: no inheritance?
+ public String getShortName();
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultAdapter.java b/src/de/lmu/ifi/dbs/elki/result/ResultAdapter.java
new file mode 100644
index 00000000..8dca01dc
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultAdapter.java
@@ -0,0 +1,36 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Marker interface for trivial "adapter" type of results.
+ *
+ * Such results can be hidden by a GUI, since they just provide a "view" on the main result.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+public interface ResultAdapter extends Result {
+ // Empty
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultHandler.java b/src/de/lmu/ifi/dbs/elki/result/ResultHandler.java
new file mode 100644
index 00000000..236dda40
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultHandler.java
@@ -0,0 +1,37 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizable;
+
+/**
+ * Interface for any class that can handle results
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ * @apiviz.uses Result oneway - - processes
+ */
+public interface ResultHandler extends Parameterizable, ResultProcessor {
+ // Empty - moved to ResultProcessor, this interface merely serves UI purposes.
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultHierarchy.java b/src/de/lmu/ifi/dbs/elki/result/ResultHierarchy.java
new file mode 100644
index 00000000..20a681bb
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultHierarchy.java
@@ -0,0 +1,162 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.List;
+
+import javax.swing.event.EventListenerList;
+
+import de.lmu.ifi.dbs.elki.logging.Logging;
+import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.HierarchyHashmapList;
+import de.lmu.ifi.dbs.elki.utilities.datastructures.hierarchy.ModifiableHierarchy;
+
+/**
+ * Class to store a hierarchy of result objects.
+ *
+ * @author Erich Schubert
+ */
+// TODO: add listener merging!
+public class ResultHierarchy extends HierarchyHashmapList<Result> {
+ /**
+ * Logger
+ */
+ private static final Logging logger = Logging.getLogger(ResultHierarchy.class);
+
+ /**
+ * Holds the listener.
+ */
+ private EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Constructor.
+ */
+ public ResultHierarchy() {
+ super();
+ }
+
+ @Override
+ public void add(Result parent, Result child) {
+ super.add(parent, child);
+ if(child instanceof HierarchicalResult) {
+ HierarchicalResult hr = (HierarchicalResult) child;
+ ModifiableHierarchy<Result> h = hr.getHierarchy();
+ // Merge hierarchy
+ hr.setHierarchy(this);
+ // Add children of child
+ for(Result desc : h.getChildren(hr)) {
+ this.add(hr, desc);
+ if(desc instanceof HierarchicalResult) {
+ ((HierarchicalResult) desc).setHierarchy(this);
+ }
+ }
+ }
+ fireResultAdded(child, parent);
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void remove(Result parent, Result child) {
+ // TODO: unlink from hierarchy, fire event
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void put(Result obj, List<Result> parents, List<Result> children) {
+ // TODO: can we support this somehow? Or reduce visibility?
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Register a result listener.
+ *
+ * @param listener Result listener.
+ */
+ public void addResultListener(ResultListener listener) {
+ listenerList.add(ResultListener.class, listener);
+ }
+
+ /**
+ * Remove a result listener.
+ *
+ * @param listener Result listener.
+ */
+ public void removeResultListener(ResultListener listener) {
+ listenerList.remove(ResultListener.class, listener);
+ }
+
+ /**
+ * Signal that a result has changed (public API)
+ *
+ * @param res Result that has changed.
+ */
+ public void resultChanged(Result res) {
+ fireResultChanged(res);
+ }
+
+ /**
+ * Informs all registered {@link ResultListener} that a new result was added.
+ *
+ * @param child New child result added
+ * @param parent Parent result that was added to
+ */
+ private void fireResultAdded(Result child, Result parent) {
+ if(logger.isDebugging()) {
+ logger.debug("Result added: " + child + " <- " + parent);
+ }
+ for(ResultListener l : listenerList.getListeners(ResultListener.class)) {
+ l.resultAdded(child, parent);
+ }
+ }
+
+ /**
+ * Informs all registered {@link ResultListener} that a result has changed.
+ *
+ * @param current Result that has changed
+ */
+ private void fireResultChanged(Result current) {
+ if(logger.isDebugging()) {
+ logger.debug("Result changed: " + current);
+ }
+ for(ResultListener l : listenerList.getListeners(ResultListener.class)) {
+ l.resultChanged(current);
+ }
+ }
+
+ /**
+ * Informs all registered {@link ResultListener} that a new result has been
+ * removed.
+ *
+ * @param child result that has been removed
+ * @param parent Parent result that has been removed
+ */
+ @SuppressWarnings("unused")
+ private void fireResultRemoved(Result child, Result parent) {
+ if(logger.isDebugging()) {
+ logger.debug("Result removed: " + child + " <- " + parent);
+ }
+ for(ResultListener l : listenerList.getListeners(ResultListener.class)) {
+ l.resultRemoved(child, parent);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultListener.java b/src/de/lmu/ifi/dbs/elki/result/ResultListener.java
new file mode 100644
index 00000000..2a79dd87
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultListener.java
@@ -0,0 +1,57 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.EventListener;
+
+/**
+ * Listener interface invoked when new results are added to the result tree.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.has Result
+ */
+public interface ResultListener extends EventListener {
+ /**
+ * A new derived result was added.
+ *
+ * @param child New child result added
+ * @param parent Parent result that was added to
+ */
+ public void resultAdded(Result child, Result parent);
+
+ /**
+ * Notify that the current result has changed substantially.
+ *
+ * @param current Result that has changed.
+ */
+ public void resultChanged(Result current);
+
+ /**
+ * A result was removed.
+ *
+ * @param child result that was removed
+ * @param parent Parent result that was removed from
+ */
+ public void resultRemoved(Result child, Result parent);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultProcessor.java b/src/de/lmu/ifi/dbs/elki/result/ResultProcessor.java
new file mode 100644
index 00000000..1c220c4e
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultProcessor.java
@@ -0,0 +1,41 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/**
+ * Interface for any class that can handle results
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses Result oneway - - processes
+ */
+public interface ResultProcessor {
+ /**
+ * Process a result.
+ *
+ * @param baseResult The base of the result tree.
+ * @param newResult Newly added result subtree.
+ */
+ public abstract void processNewResult(final HierarchicalResult baseResult, final Result newResult);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultUtil.java b/src/de/lmu/ifi/dbs/elki/result/ResultUtil.java
new file mode 100644
index 00000000..20b1a0be
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultUtil.java
@@ -0,0 +1,304 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import de.lmu.ifi.dbs.elki.algorithm.clustering.ClusteringAlgorithm;
+import de.lmu.ifi.dbs.elki.algorithm.clustering.trivial.ByLabelHierarchicalClustering;
+import de.lmu.ifi.dbs.elki.algorithm.clustering.trivial.TrivialAllInOne;
+import de.lmu.ifi.dbs.elki.data.Clustering;
+import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.data.type.NoSupportedDataTypeException;
+import de.lmu.ifi.dbs.elki.database.Database;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.result.outlier.OutlierResult;
+import de.lmu.ifi.dbs.elki.utilities.ClassGenericsUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.EmptyIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+import de.lmu.ifi.dbs.elki.utilities.iterator.MergedIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.OneItemIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.TypeFilterIterator;
+
+/**
+ * Utilities for handling result objects
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses Result oneway - - filters
+ */
+public class ResultUtil {
+ /**
+ * Collect all Annotation results from a Result
+ *
+ * @param r Result
+ * @return List of all annotation results
+ */
+ public static List<Relation<?>> getRelations(Result r) {
+ if(r instanceof Relation<?>) {
+ List<Relation<?>> anns = new ArrayList<Relation<?>>(1);
+ anns.add((Relation<?>) r);
+ return anns;
+ }
+ if(r instanceof HierarchicalResult) {
+ return ClassGenericsUtil.castWithGenericsOrNull(List.class, filterResults((HierarchicalResult) r, Relation.class));
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Collect all ordering results from a Result
+ *
+ * @param r Result
+ * @return List of ordering results
+ */
+ public static List<OrderingResult> getOrderingResults(Result r) {
+ if(r instanceof OrderingResult) {
+ List<OrderingResult> ors = new ArrayList<OrderingResult>(1);
+ ors.add((OrderingResult) r);
+ return ors;
+ }
+ if(r instanceof HierarchicalResult) {
+ return filterResults((HierarchicalResult) r, OrderingResult.class);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Collect all clustering results from a Result
+ *
+ * @param r Result
+ * @return List of clustering results
+ */
+ public static List<Clustering<? extends Model>> getClusteringResults(Result r) {
+ if(r instanceof Clustering<?>) {
+ List<Clustering<?>> crs = new ArrayList<Clustering<?>>(1);
+ crs.add((Clustering<?>) r);
+ return crs;
+ }
+ if(r instanceof HierarchicalResult) {
+ return ClassGenericsUtil.castWithGenericsOrNull(List.class, filterResults((HierarchicalResult) r, Clustering.class));
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Collect all collection results from a Result
+ *
+ * @param r Result
+ * @return List of collection results
+ */
+ public static List<CollectionResult<?>> getCollectionResults(Result r) {
+ if(r instanceof CollectionResult<?>) {
+ List<CollectionResult<?>> crs = new ArrayList<CollectionResult<?>>(1);
+ crs.add((CollectionResult<?>) r);
+ return crs;
+ }
+ if(r instanceof HierarchicalResult) {
+ return ClassGenericsUtil.castWithGenericsOrNull(List.class, filterResults((HierarchicalResult) r, CollectionResult.class));
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Return all Iterable results
+ *
+ * @param r Result
+ * @return List of iterable results
+ */
+ public static List<IterableResult<?>> getIterableResults(Result r) {
+ if(r instanceof IterableResult<?>) {
+ List<IterableResult<?>> irs = new ArrayList<IterableResult<?>>(1);
+ irs.add((IterableResult<?>) r);
+ return irs;
+ }
+ if(r instanceof HierarchicalResult) {
+ return ClassGenericsUtil.castWithGenericsOrNull(List.class, filterResults((HierarchicalResult) r, IterableResult.class));
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Collect all outlier results from a Result
+ *
+ * @param r Result
+ * @return List of outlier results
+ */
+ public static List<OutlierResult> getOutlierResults(Result r) {
+ if(r instanceof OutlierResult) {
+ List<OutlierResult> ors = new ArrayList<OutlierResult>(1);
+ ors.add((OutlierResult) r);
+ return ors;
+ }
+ if(r instanceof HierarchicalResult) {
+ return filterResults((HierarchicalResult) r, OutlierResult.class);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Collect all settings results from a Result
+ *
+ * @param r Result
+ * @return List of settings results
+ */
+ public static List<SettingsResult> getSettingsResults(Result r) {
+ if(r instanceof SettingsResult) {
+ List<SettingsResult> ors = new ArrayList<SettingsResult>(1);
+ ors.add((SettingsResult) r);
+ return ors;
+ }
+ if(r instanceof HierarchicalResult) {
+ return filterResults((HierarchicalResult) r, SettingsResult.class);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Return only results of the given restriction class
+ *
+ * @param <C> Class type
+ * @param restrictionClass Class restriction
+ * @return filtered results list
+ */
+ // We can't ensure that restrictionClass matches C.
+ @SuppressWarnings("unchecked")
+ public static <C> ArrayList<C> filterResults(Result r, Class<?> restrictionClass) {
+ ArrayList<C> res = new ArrayList<C>();
+ try {
+ res.add((C) restrictionClass.cast(r));
+ }
+ catch(ClassCastException e) {
+ // ignore
+ }
+ if(r instanceof HierarchicalResult) {
+ for(Result result : ((HierarchicalResult) r).getHierarchy().iterDescendants(r)) {
+ try {
+ res.add((C) restrictionClass.cast(result));
+ }
+ catch(ClassCastException e) {
+ // ignore
+ }
+ }
+ }
+ return res;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <C extends Result> IterableIterator<C> filteredResults(Result r, Class<?> restrictionClass) {
+ final Class<C> rc = (Class<C>) restrictionClass;
+ // Include the current item
+ IterableIterator<C> curIter;
+ try {
+ curIter = new OneItemIterator<C>(rc.cast(r));
+ }
+ catch(ClassCastException e) {
+ curIter = null;
+ }
+ if(r instanceof HierarchicalResult) {
+ ResultHierarchy hier = ((HierarchicalResult) r).getHierarchy();
+ final Iterable<Result> iterDescendants = hier.iterDescendants(r);
+ final Iterator<C> others = new TypeFilterIterator<Result, C>(rc, iterDescendants);
+ if(curIter != null) {
+ return IterableUtil.fromIterator(new MergedIterator<C>(curIter, others));
+ }
+ else {
+ return IterableUtil.fromIterator(others);
+ }
+ }
+ else {
+ if(curIter != null) {
+ return curIter;
+ }
+ else {
+ return EmptyIterator.STATIC();
+ }
+ }
+ }
+
+ /**
+ * Ensure that the result contains at least one Clustering.
+ *
+ * @param <O> Database type
+ * @param db Database to process
+ * @param result result
+ */
+ public static <O> void ensureClusteringResult(final Database db, final Result result) {
+ Collection<Clustering<?>> clusterings = ResultUtil.filterResults(result, Clustering.class);
+ if(clusterings.size() == 0) {
+ try {
+ ClusteringAlgorithm<Clustering<Model>> split = new ByLabelHierarchicalClustering();
+ Clustering<Model> c = split.run(db);
+ addChildResult(db, c);
+ }
+ catch(NoSupportedDataTypeException e) {
+ Clustering<Model> c = (new TrivialAllInOne()).run(db);
+ addChildResult(db, c);
+ }
+ }
+ }
+
+ /**
+ * Ensure that there also is a selection container object.
+ *
+ * @param db Database
+ * @param result Result
+ */
+ public static void ensureSelectionResult(final Database db, final Result result) {
+ Collection<SelectionResult> selections = ResultUtil.filterResults(result, SelectionResult.class);
+ if(selections.size() == 0) {
+ addChildResult(db, new SelectionResult());
+ }
+ }
+
+ /**
+ * Add a child result.
+ *
+ * @param parent Parent
+ * @param child Child
+ */
+ public static void addChildResult(HierarchicalResult parent, Result child) {
+ parent.getHierarchy().add(parent, child);
+ }
+
+ /**
+ * Find the first database result in the tree.
+ *
+ * @param baseResult Result tree base.
+ * @return Database
+ */
+ public static Database findDatabase(Result baseResult) {
+ final IterableIterator<Database> iter = filteredResults(baseResult, Database.class);
+ if(iter.hasNext()) {
+ return iter.next();
+ }
+ else {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/ResultWriter.java b/src/de/lmu/ifi/dbs/elki/result/ResultWriter.java
new file mode 100644
index 00000000..cc3e0f5a
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/ResultWriter.java
@@ -0,0 +1,189 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.File;
+import java.io.IOException;
+
+import de.lmu.ifi.dbs.elki.database.Database;
+import de.lmu.ifi.dbs.elki.logging.Logging;
+import de.lmu.ifi.dbs.elki.result.textwriter.MultipleFilesOutput;
+import de.lmu.ifi.dbs.elki.result.textwriter.SingleStreamOutput;
+import de.lmu.ifi.dbs.elki.result.textwriter.StreamFactory;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriter;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.AbstractParameterizer;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.FileParameter;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Flag;
+
+/**
+ * Result handler that feeds the data into a TextWriter
+ *
+ * @author Erich Schubert
+ */
+public class ResultWriter implements ResultHandler {
+ /**
+ * The logger for this class.
+ */
+ private static final Logging logger = Logging.getLogger(ResultWriter.class);
+
+ /**
+ * Flag to control GZIP compression.
+ * <p>
+ * Key: {@code -out.gzip}
+ * </p>
+ */
+ public static final OptionID GZIP_OUTPUT_ID = OptionID.getOrCreateOptionID("out.gzip", "Enable gzip compression of output files.");
+
+ /**
+ * Flag to suppress overwrite warning.
+ * <p>
+ * Key: {@code -out.silentoverwrite}
+ * </p>
+ */
+ public static final OptionID OVERWRITE_OPTION_ID = OptionID.getOrCreateOptionID("out.silentoverwrite", "Silently overwrite output files.");
+
+ /**
+ * Holds the file to print results to.
+ */
+ private File out;
+
+ /**
+ * Whether or not to do gzip compression on output.
+ */
+ private boolean gzip = false;
+
+ /**
+ * Whether or not to warn on overwrite
+ */
+ private boolean warnoverwrite = true;
+
+ /**
+ * Constructor.
+ *
+ * @param out
+ * @param gzip
+ * @param warnoverwrite
+ */
+ public ResultWriter(File out, boolean gzip, boolean warnoverwrite) {
+ super();
+ this.out = out;
+ this.gzip = gzip;
+ this.warnoverwrite = warnoverwrite;
+ }
+
+ @Override
+ public void processNewResult(HierarchicalResult baseresult, Result result) {
+ TextWriter writer = new TextWriter();
+
+ StreamFactory output;
+ try {
+ if(out == null) {
+ output = new SingleStreamOutput(gzip);
+ }
+ else if(out.exists()) {
+ if(out.isDirectory()) {
+ if(warnoverwrite && out.listFiles().length > 0) {
+ logger.warning("Output directory specified is not empty. Files will be overwritten and old files may be left over.");
+ }
+ output = new MultipleFilesOutput(out, gzip);
+ }
+ else {
+ if(warnoverwrite) {
+ logger.warning("Output file exists and will be overwritten!");
+ }
+ output = new SingleStreamOutput(out, gzip);
+ }
+ }
+ else {
+ // If it doesn't exist yet, make a MultipleFilesOutput.
+ output = new MultipleFilesOutput(out, gzip);
+ }
+ }
+ catch(IOException e) {
+ throw new IllegalStateException("Error opening output.", e);
+ }
+ try {
+ Database db = ResultUtil.findDatabase(baseresult);
+ writer.output(db, result, output);
+ }
+ catch(IOException e) {
+ throw new IllegalStateException("Input/Output error while writing result.", e);
+ }
+ catch(UnableToComplyException e) {
+ throw new IllegalStateException("Unable to comply while writing result.", e);
+ }
+ output.closeAllStreams();
+ }
+
+ /**
+ * Parameterization class.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ public static class Parameterizer extends AbstractParameterizer {
+ /**
+ * Holds the file to print results to.
+ */
+ private File out = null;
+
+ /**
+ * Whether or not to do gzip compression on output.
+ */
+ private boolean gzip = false;
+
+ /**
+ * Whether or not to warn on overwrite
+ */
+ private boolean warnoverwrite = true;
+
+ @Override
+ protected void makeOptions(Parameterization config) {
+ super.makeOptions(config);
+ FileParameter outputP = new FileParameter(OptionID.OUTPUT, FileParameter.FileType.OUTPUT_FILE, true);
+ if(config.grab(outputP)) {
+ out = outputP.getValue();
+ }
+
+ Flag gzipF = new Flag(GZIP_OUTPUT_ID);
+ if(config.grab(gzipF)) {
+ gzip = gzipF.getValue();
+ }
+
+ Flag overwriteF = new Flag(OVERWRITE_OPTION_ID);
+ if(config.grab(overwriteF)) {
+ // note: inversed meaning
+ warnoverwrite = !overwriteF.getValue();
+ }
+ }
+
+ @Override
+ protected ResultWriter makeInstance() {
+ return new ResultWriter(out, gzip, warnoverwrite);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/SelectionResult.java b/src/de/lmu/ifi/dbs/elki/result/SelectionResult.java
new file mode 100644
index 00000000..b4b1de43
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/SelectionResult.java
@@ -0,0 +1,72 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/**
+ * Selection result wrapper.
+ *
+ * Note: we did not make the DBIDSelection a result in itself. Instead, the
+ * DBIDSelection object should be seen as static contents of this result.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.composedOf DBIDSelection
+ */
+public class SelectionResult implements Result {
+ /**
+ * The actual selection
+ */
+ DBIDSelection selection = null;
+
+ /**
+ * Constructor.
+ */
+ public SelectionResult() {
+ super();
+ }
+
+ /**
+ * @return the selection
+ */
+ public DBIDSelection getSelection() {
+ return selection;
+ }
+
+ /**
+ * @param selection the selection to set
+ */
+ public void setSelection(DBIDSelection selection) {
+ this.selection = selection;
+ }
+
+ @Override
+ public String getLongName() {
+ return "Selection";
+ }
+
+ @Override
+ public String getShortName() {
+ return "selection";
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/SettingsResult.java b/src/de/lmu/ifi/dbs/elki/result/SettingsResult.java
new file mode 100644
index 00000000..71e678ca
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/SettingsResult.java
@@ -0,0 +1,59 @@
+package de.lmu.ifi.dbs.elki.result;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Collection;
+
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter;
+import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
+
+/**
+ * Result that keeps track of settings that were used in generating this
+ * particular result.
+ *
+ * @author Erich Schubert
+ */
+public class SettingsResult extends BasicResult {
+ /**
+ * Settings storage.
+ */
+ Collection<Pair<Object, Parameter<?,?>>> settings;
+
+ /**
+ * Constructor.
+ *
+ * @param settings Settings to store
+ */
+ public SettingsResult(Collection<Pair<Object, Parameter<?,?>>> settings) {
+ super("Settings", "settings");
+ this.settings = settings;
+ }
+
+ /**
+ * Get the settings
+ * @return the settings
+ */
+ public Collection<Pair<Object, Parameter<?,?>>> getSettings() {
+ return settings;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderEntry.java b/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderEntry.java
new file mode 100644
index 00000000..6dd42e2d
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderEntry.java
@@ -0,0 +1,62 @@
+package de.lmu.ifi.dbs.elki.result.optics;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+
+/**
+ * Generic Cluster Order Entry Interface.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ *
+ * @apiviz.composedOf DBID
+ * @apiviz.composedOf Distance
+ *
+ * @param <D> Distance type
+ */
+public interface ClusterOrderEntry<D extends Distance<D>> {
+ /**
+ * Returns the object id of this entry.
+ *
+ * @return the object id of this entry
+ */
+ public DBID getID();
+
+ /**
+ * Returns the id of the predecessor of this entry if this entry has a
+ * predecessor, null otherwise.
+ *
+ * @return the id of the predecessor of this entry
+ */
+ public DBID getPredecessorID();
+
+ /**
+ * Returns the reachability distance of this entry
+ *
+ * @return the reachability distance of this entry
+ */
+ public D getReachability();
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderResult.java b/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderResult.java
new file mode 100644
index 00000000..d135f53e
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/optics/ClusterOrderResult.java
@@ -0,0 +1,387 @@
+package de.lmu.ifi.dbs.elki.result.optics;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import de.lmu.ifi.dbs.elki.data.type.SimpleTypeInformation;
+import de.lmu.ifi.dbs.elki.data.type.TypeUtil;
+import de.lmu.ifi.dbs.elki.database.Database;
+import de.lmu.ifi.dbs.elki.database.ids.ArrayModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.ModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.result.BasicResult;
+import de.lmu.ifi.dbs.elki.result.IterableResult;
+import de.lmu.ifi.dbs.elki.result.OrderingResult;
+import de.lmu.ifi.dbs.elki.result.ResultAdapter;
+import de.lmu.ifi.dbs.elki.result.ResultHierarchy;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIteratorAdapter;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableUtil;
+
+/**
+ * Class to store the result of an ordering clustering algorithm such as OPTICS.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ *
+ * @apiviz.has ClusterOrderEntry oneway - - contains
+ * @apiviz.composedOf ClusterOrderResult.ClusterOrderAdapter
+ * @apiviz.composedOf ClusterOrderResult.ReachabilityDistanceAdapter
+ * @apiviz.composedOf ClusterOrderResult.PredecessorAdapter
+ *
+ * @param <D> distance type.
+ */
+public class ClusterOrderResult<D extends Distance<D>> extends BasicResult implements IterableResult<ClusterOrderEntry<D>> {
+ /**
+ * Cluster order storage
+ */
+ private ArrayList<ClusterOrderEntry<D>> clusterOrder;
+
+ /**
+ * Map of object IDs to their cluster order entry
+ */
+ private HashMap<DBID, ClusterOrderEntry<D>> map;
+
+ /**
+ * The DBIDs we are defined for
+ */
+ ModifiableDBIDs dbids;
+
+ /**
+ * Constructor
+ *
+ * @param name The long name (for pretty printing)
+ * @param shortname the short name (for filenames etc.)
+ */
+ public ClusterOrderResult(String name, String shortname) {
+ super(name, shortname);
+ clusterOrder = new ArrayList<ClusterOrderEntry<D>>();
+ map = new HashMap<DBID, ClusterOrderEntry<D>>();
+ dbids = DBIDUtil.newHashSet();
+
+ addChildResult(new ClusterOrderAdapter(clusterOrder));
+ addChildResult(new ReachabilityDistanceAdapter(map, dbids));
+ addChildResult(new PredecessorAdapter(map, dbids));
+ }
+
+ /**
+ * Retrieve the complete cluster order.
+ *
+ * @return cluster order
+ */
+ public List<ClusterOrderEntry<D>> getClusterOrder() {
+ return clusterOrder;
+ }
+
+ /**
+ * The cluster order is iterable
+ */
+ @Override
+ public Iterator<ClusterOrderEntry<D>> iterator() {
+ return clusterOrder.iterator();
+ }
+
+ /**
+ * Add an object to the cluster order.
+ *
+ * @param id Object ID
+ * @param predecessor Predecessor ID
+ * @param reachability Reachability distance
+ */
+ public void add(DBID id, DBID predecessor, D reachability) {
+ add(new GenericClusterOrderEntry<D>(id, predecessor, reachability));
+ dbids.add(id);
+ }
+
+ /**
+ * Add an object to the cluster order.
+ *
+ * @param ce Entry
+ */
+ public void add(ClusterOrderEntry<D> ce) {
+ clusterOrder.add(ce);
+ map.put(ce.getID(), ce);
+ dbids.add(ce.getID());
+ }
+
+ /**
+ * Get the distance class
+ *
+ * @return distance class. Can be {@code null} for an all-undefined result!
+ */
+ public Class<?> getDistanceClass() {
+ for(ClusterOrderEntry<D> ce : clusterOrder) {
+ D dist = ce.getReachability();
+ if(dist != null) {
+ return dist.getClass();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Ordering part of the result.
+ *
+ * @author Erich Schubert
+ */
+ class ClusterOrderAdapter implements OrderingResult, ResultAdapter {
+ /**
+ * Access reference.
+ */
+ private ArrayList<ClusterOrderEntry<D>> clusterOrder;
+
+ /**
+ * Constructor.
+ *
+ * @param clusterOrder order to return
+ */
+ public ClusterOrderAdapter(final ArrayList<ClusterOrderEntry<D>> clusterOrder) {
+ super();
+ this.clusterOrder = clusterOrder;
+ }
+
+ @Override
+ public DBIDs getDBIDs() {
+ return dbids;
+ }
+
+ /**
+ * Use the cluster order to sort the given collection ids.
+ *
+ * Implementation of the {@link OrderingResult} interface.
+ */
+ @Override
+ public IterableIterator<DBID> iter(DBIDs ids) {
+ ArrayModifiableDBIDs res = DBIDUtil.newArray(ids.size());
+ for(ClusterOrderEntry<D> e : clusterOrder) {
+ if(ids.contains(e.getID())) {
+ res.add(e.getID());
+ }
+ }
+
+ // TODO: elements in ids that are not in clusterOrder are lost!
+ return new IterableIteratorAdapter<DBID>(res);
+ }
+
+ @Override
+ public String getLongName() {
+ return "Derived Object Order";
+ }
+
+ @Override
+ public String getShortName() {
+ return "clusterobjectorder";
+ }
+ }
+
+ /**
+ * Result containing the reachability distances.
+ *
+ * @author Erich Schubert
+ */
+ class ReachabilityDistanceAdapter implements Relation<D>, ResultAdapter {
+ /**
+ * Access reference.
+ */
+ private HashMap<DBID, ClusterOrderEntry<D>> map;
+
+ /**
+ * DBIDs
+ */
+ private DBIDs dbids;
+
+ /**
+ * Constructor.
+ *
+ * @param map Map that stores the results.
+ * @param dbids DBIDs we are defined for.
+ */
+ public ReachabilityDistanceAdapter(HashMap<DBID, ClusterOrderEntry<D>> map, DBIDs dbids) {
+ super();
+ this.map = map;
+ this.dbids = dbids;
+ }
+
+ @Override
+ public D get(DBID objID) {
+ return map.get(objID).getReachability();
+ }
+
+ @Override
+ public String getLongName() {
+ return "Reachability";
+ }
+
+ @Override
+ public String getShortName() {
+ return "reachability";
+ }
+
+ @Override
+ public DBIDs getDBIDs() {
+ return DBIDUtil.makeUnmodifiable(dbids);
+ }
+
+ @Override
+ public IterableIterator<DBID> iterDBIDs() {
+ return IterableUtil.fromIterator(dbids.iterator());
+ }
+
+ @Override
+ public int size() {
+ return dbids.size();
+ }
+
+ @Override
+ public Database getDatabase() {
+ return null; // FIXME
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void set(DBID id, D val) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void delete(DBID id) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SimpleTypeInformation<D> getDataTypeInformation() {
+ return new SimpleTypeInformation<D>(Distance.class);
+ }
+
+ @Override
+ public ResultHierarchy getHierarchy() {
+ return ClusterOrderResult.this.getHierarchy();
+ }
+
+ @Override
+ public void setHierarchy(ResultHierarchy hierarchy) {
+ ClusterOrderResult.this.setHierarchy(hierarchy);
+ }
+ }
+
+ /**
+ * Result containing the predecessor ID.
+ *
+ * @author Erich Schubert
+ */
+ class PredecessorAdapter implements Relation<DBID>, ResultAdapter {
+ /**
+ * Access reference.
+ */
+ private HashMap<DBID, ClusterOrderEntry<D>> map;
+
+ /**
+ * Database IDs
+ */
+ private DBIDs dbids;
+
+ /**
+ * Constructor.
+ *
+ * @param map Map that stores the results.
+ * @param dbids DBIDs we are defined for
+ */
+ public PredecessorAdapter(HashMap<DBID, ClusterOrderEntry<D>> map, DBIDs dbids) {
+ super();
+ this.map = map;
+ this.dbids = dbids;
+ }
+
+ @Override
+ public DBID get(DBID objID) {
+ return map.get(objID).getPredecessorID();
+ }
+
+ @Override
+ public String getLongName() {
+ return "Predecessor";
+ }
+
+ @Override
+ public String getShortName() {
+ return "predecessor";
+ }
+
+ @Override
+ public DBIDs getDBIDs() {
+ return DBIDUtil.makeUnmodifiable(dbids);
+ }
+
+ @Override
+ public IterableIterator<DBID> iterDBIDs() {
+ return IterableUtil.fromIterator(dbids.iterator());
+ }
+
+ @Override
+ public int size() {
+ return dbids.size();
+ }
+
+ @Override
+ public Database getDatabase() {
+ return null; // FIXME
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void set(DBID id, DBID val) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public void delete(DBID id) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SimpleTypeInformation<DBID> getDataTypeInformation() {
+ return TypeUtil.DBID;
+ }
+
+ @Override
+ public ResultHierarchy getHierarchy() {
+ return ClusterOrderResult.this.getHierarchy();
+ }
+
+ @Override
+ public void setHierarchy(ResultHierarchy hierarchy) {
+ ClusterOrderResult.this.setHierarchy(hierarchy);
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/optics/DoubleDistanceClusterOrderEntry.java b/src/de/lmu/ifi/dbs/elki/result/optics/DoubleDistanceClusterOrderEntry.java
new file mode 100644
index 00000000..fe3c24bb
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/optics/DoubleDistanceClusterOrderEntry.java
@@ -0,0 +1,152 @@
+package de.lmu.ifi.dbs.elki.result.optics;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.DoubleDistance;
+
+/**
+ * Provides an entry in a cluster order.
+ *
+ * @author Elke Achtert
+ */
+public class DoubleDistanceClusterOrderEntry implements Comparable<ClusterOrderEntry<DoubleDistance>>, ClusterOrderEntry<DoubleDistance> {
+ /**
+ * The id of the entry.
+ */
+ private DBID objectID;
+
+ /**
+ * The id of the entry's predecessor.
+ */
+ private DBID predecessorID;
+
+ /**
+ * The reachability of the entry.
+ */
+ private double reachability;
+
+ /**
+ * Creates a new entry in a cluster order with the specified parameters.
+ *
+ * @param objectID the id of the entry
+ * @param predecessorID the id of the entry's predecessor
+ * @param reachability the reachability of the entry
+ */
+ public DoubleDistanceClusterOrderEntry(DBID objectID, DBID predecessorID, double reachability) {
+ this.objectID = objectID;
+ this.predecessorID = predecessorID;
+ this.reachability = reachability;
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ *
+ * NOTE: for the use in an UpdatableHeap, only the ID is compared!
+ *
+ * @param o the reference object with which to compare.
+ * @return <code>true</code> if this object has the same attribute values as
+ * the o argument; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if(this == o) {
+ return true;
+ }
+ if(o == null || !(o instanceof ClusterOrderEntry)) {
+ return false;
+ }
+
+ final ClusterOrderEntry<?> that = (ClusterOrderEntry<?>) o;
+ // Compare by ID only, for UpdatableHeap!
+ return objectID.equals(that.getID());
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return the object id if this entry
+ */
+ @Override
+ public int hashCode() {
+ return objectID.hashCode();
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return objectID + "(" + predecessorID + "," + reachability + ")";
+ }
+
+ /**
+ * Returns the object id of this entry.
+ *
+ * @return the object id of this entry
+ */
+ @Override
+ public DBID getID() {
+ return objectID;
+ }
+
+ /**
+ * Returns the id of the predecessor of this entry if this entry has a
+ * predecessor, null otherwise.
+ *
+ * @return the id of the predecessor of this entry
+ */
+ @Override
+ public DBID getPredecessorID() {
+ return predecessorID;
+ }
+
+ /**
+ * Returns the reachability distance of this entry
+ *
+ * @return the reachability distance of this entry
+ */
+ @Override
+ public DoubleDistance getReachability() {
+ return new DoubleDistance(reachability);
+ }
+
+ @Override
+ public int compareTo(ClusterOrderEntry<DoubleDistance> o) {
+ if(o instanceof DoubleDistanceClusterOrderEntry) {
+ int delta = Double.compare(this.reachability, ((DoubleDistanceClusterOrderEntry) o).reachability);
+ if(delta != 0) {
+ return delta;
+ }
+ }
+ else {
+ int delta = this.getReachability().compareTo(o.getReachability());
+ if(delta != 0) {
+ return delta;
+ }
+ }
+ return -getID().compareTo(o.getID());
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/optics/GenericClusterOrderEntry.java b/src/de/lmu/ifi/dbs/elki/result/optics/GenericClusterOrderEntry.java
new file mode 100644
index 00000000..d20ee35b
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/optics/GenericClusterOrderEntry.java
@@ -0,0 +1,145 @@
+package de.lmu.ifi.dbs.elki.result.optics;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+
+/**
+ * Provides an entry in a cluster order.
+ *
+ * @author Elke Achtert
+ * @param <D> the type of Distance used by the ClusterOrderEntry
+ */
+public class GenericClusterOrderEntry<D extends Distance<D>> implements Comparable<ClusterOrderEntry<D>>, ClusterOrderEntry<D> {
+ /**
+ * The id of the entry.
+ */
+ private DBID objectID;
+
+ /**
+ * The id of the entry's predecessor.
+ */
+ private DBID predecessorID;
+
+ /**
+ * The reachability of the entry.
+ */
+ private D reachability;
+
+ /**
+ * Creates a new entry in a cluster order with the specified parameters.
+ *
+ * @param objectID the id of the entry
+ * @param predecessorID the id of the entry's predecessor
+ * @param reachability the reachability of the entry
+ */
+ public GenericClusterOrderEntry(DBID objectID, DBID predecessorID, D reachability) {
+ this.objectID = objectID;
+ this.predecessorID = predecessorID;
+ this.reachability = reachability;
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ *
+ * NOTE: for the use in an UpdatableHeap, only the ID is compared!
+ *
+ * @param o the reference object with which to compare.
+ * @return <code>true</code> if this object has the same attribute values as
+ * the o argument; <code>false</code> otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if(this == o) {
+ return true;
+ }
+ if(o == null || !(o instanceof ClusterOrderEntry)) {
+ return false;
+ }
+
+ final ClusterOrderEntry<?> that = (ClusterOrderEntry<?>) o;
+ // Compare by ID only, for UpdatableHeap!
+ return objectID.equals(that.getID());
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return the object id if this entry
+ */
+ @Override
+ public int hashCode() {
+ return objectID.hashCode();
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ * @return a string representation of the object.
+ */
+ @Override
+ public String toString() {
+ return objectID + "(" + predecessorID + "," + reachability + ")";
+ }
+
+ /**
+ * Returns the object id of this entry.
+ *
+ * @return the object id of this entry
+ */
+ @Override
+ public DBID getID() {
+ return objectID;
+ }
+
+ /**
+ * Returns the id of the predecessor of this entry if this entry has a
+ * predecessor, null otherwise.
+ *
+ * @return the id of the predecessor of this entry
+ */
+ @Override
+ public DBID getPredecessorID() {
+ return predecessorID;
+ }
+
+ /**
+ * Returns the reachability distance of this entry
+ *
+ * @return the reachability distance of this entry
+ */
+ @Override
+ public D getReachability() {
+ return reachability;
+ }
+
+ @Override
+ public int compareTo(ClusterOrderEntry<D> o) {
+ int delta = this.getReachability().compareTo(o.getReachability());
+ if(delta != 0) {
+ return delta;
+ }
+ return -getID().compareTo(o.getID());
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/optics/package-info.java b/src/de/lmu/ifi/dbs/elki/result/optics/package-info.java
new file mode 100644
index 00000000..8cb59763
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/optics/package-info.java
@@ -0,0 +1,26 @@
+/**
+ * <p>Result classes for OPTICS.</p>
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result.optics; \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/BasicOutlierScoreMeta.java b/src/de/lmu/ifi/dbs/elki/result/outlier/BasicOutlierScoreMeta.java
new file mode 100644
index 00000000..0ea4e921
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/BasicOutlierScoreMeta.java
@@ -0,0 +1,178 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.logging.Logging;
+
+/**
+ * Basic outlier score. Straightforward implementation of the
+ * {@link OutlierScoreMeta} interface.
+ *
+ * @author Erich Schubert
+ */
+public class BasicOutlierScoreMeta implements OutlierScoreMeta {
+ /**
+ * Store the actual minimum
+ */
+ double actualMinimum = Double.NaN;
+
+ /**
+ * Store the actual maximum
+ */
+ double actualMaximum = Double.NaN;
+
+ /**
+ * Store the theoretical minimum
+ */
+ double theoreticalMinimum = Double.NaN;
+
+ /**
+ * Store the theoretical maximum
+ */
+ double theoreticalMaximum = Double.NaN;
+
+ /**
+ * Store the theoretical baseline
+ */
+ double theoreticalBaseline = Double.NaN;
+
+ /**
+ * Constructor with actual values only.
+ *
+ * @param actualMinimum actual minimum
+ * @param actualMaximum actual maximum
+ */
+ public BasicOutlierScoreMeta(double actualMinimum, double actualMaximum) {
+ this(actualMinimum, actualMaximum, Double.NaN, Double.NaN, Double.NaN);
+ }
+
+ /**
+ * Constructor with all range values
+ *
+ * @param actualMinimum actual minimum
+ * @param actualMaximum actual maximum
+ * @param theoreticalMinimum theoretical minimum
+ * @param theoreticalMaximum theoretical maximum
+ */
+ public BasicOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum) {
+ this(actualMinimum, actualMaximum, theoreticalMinimum, theoreticalMaximum, Double.NaN);
+ }
+
+ /**
+ * Full constructor - all values.
+ *
+ * @param actualMinimum actual minimum
+ * @param actualMaximum actual maximum
+ * @param theoreticalMinimum theoretical minimum
+ * @param theoreticalMaximum theoretical maximum
+ * @param theoreticalBaseline theoretical baseline
+ */
+ public BasicOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum, double theoreticalBaseline) {
+ super();
+ if(Double.isNaN(actualMinimum) || Double.isNaN(actualMaximum)) {
+ Logging.getLogger(this.getClass()).warning("Warning: Outlier Score meta initalized with NaN values: " + actualMinimum + " - " + actualMaximum);
+ }
+ this.actualMinimum = actualMinimum;
+ this.actualMaximum = actualMaximum;
+ this.theoreticalMinimum = theoreticalMinimum;
+ this.theoreticalMaximum = theoreticalMaximum;
+ this.theoreticalBaseline = theoreticalBaseline;
+ }
+
+ @Override
+ public double getActualMaximum() {
+ return actualMaximum;
+ }
+
+ @Override
+ public double getActualMinimum() {
+ return actualMinimum;
+ }
+
+ @Override
+ public double getTheoreticalBaseline() {
+ return theoreticalBaseline;
+ }
+
+ @Override
+ public double getTheoreticalMaximum() {
+ return theoreticalMaximum;
+ }
+
+ @Override
+ public double getTheoreticalMinimum() {
+ return theoreticalMinimum;
+ }
+
+ @Override
+ public double normalizeScore(double value) {
+ double center = 0.0;
+ if(!Double.isNaN(theoreticalBaseline) && !Double.isInfinite(theoreticalBaseline)) {
+ center = theoreticalBaseline;
+ }
+ else if(!Double.isNaN(theoreticalMinimum) && !Double.isInfinite(theoreticalMinimum)) {
+ center = theoreticalMinimum;
+ }
+ else if(!Double.isNaN(actualMinimum) && !Double.isInfinite(actualMinimum)) {
+ center = actualMinimum;
+ }
+ if(value < center) {
+ return 0.0;
+ }
+ double max = Double.NaN;
+ if(!Double.isNaN(theoreticalMaximum) && !Double.isInfinite(theoreticalMaximum)) {
+ max = theoreticalMaximum;
+ }
+ else if(!Double.isNaN(actualMaximum) && !Double.isInfinite(actualMaximum)) {
+ max = actualMaximum;
+ }
+ if(!Double.isNaN(max) && !Double.isInfinite(max) && max >= center) {
+ return (value - center) / (max - center);
+ }
+ return value - center;
+ }
+
+ /**
+ * @param actualMinimum the actualMinimum to set
+ */
+ public void setActualMinimum(double actualMinimum) {
+ this.actualMinimum = actualMinimum;
+ }
+
+ /**
+ * @param actualMaximum the actualMaximum to set
+ */
+ public void setActualMaximum(double actualMaximum) {
+ this.actualMaximum = actualMaximum;
+ }
+
+ @Override
+ public String getLongName() {
+ return "Outlier Score Metadata";
+ }
+
+ @Override
+ public String getShortName() {
+ return "outlier-score-meta";
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/InvertedOutlierScoreMeta.java b/src/de/lmu/ifi/dbs/elki/result/outlier/InvertedOutlierScoreMeta.java
new file mode 100644
index 00000000..6fd66bb8
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/InvertedOutlierScoreMeta.java
@@ -0,0 +1,91 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Class to signal a value-inverted outlier score, i.e. low values are outliers.
+ *
+ * @author Erich Schubert
+ */
+public class InvertedOutlierScoreMeta extends BasicOutlierScoreMeta {
+ /**
+ * Constructor with all values.
+ *
+ * @param actualMinimum Actual minimum in data
+ * @param actualMaximum Actual maximum in data
+ * @param theoreticalMinimum Theoretical minimum of algorithm
+ * @param theoreticalMaximum Theoretical maximum of algorithm
+ * @param theoreticalBaseline Theoretical Baseline
+ */
+ public InvertedOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum, double theoreticalBaseline) {
+ super(actualMinimum, actualMaximum, theoreticalMinimum, theoreticalMaximum, theoreticalBaseline);
+ }
+
+ /**
+ * Constructor with range values.
+ *
+ * @param actualMinimum Actual minimum in data
+ * @param actualMaximum Actual maximum in data
+ * @param theoreticalMinimum Theoretical minimum of algorithm
+ * @param theoreticalMaximum Theoretical maximum of algorithm
+ */
+ public InvertedOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum) {
+ super(actualMinimum, actualMaximum, theoreticalMinimum, theoreticalMaximum);
+ }
+
+ /**
+ * Constructor with actual range only.
+ *
+ * @param actualMinimum Actual minimum in data
+ * @param actualMaximum Actual maximum in data
+ */
+ public InvertedOutlierScoreMeta(double actualMinimum, double actualMaximum) {
+ super(actualMinimum, actualMaximum);
+ }
+
+ @Override
+ public double normalizeScore(double value) {
+ double center = 0.0;
+ if (!Double.isNaN(theoreticalBaseline) && !Double.isInfinite(theoreticalBaseline)) {
+ center = theoreticalBaseline;
+ } else if (!Double.isNaN(theoreticalMaximum) && !Double.isInfinite(theoreticalMaximum)) {
+ center = theoreticalMaximum;
+ } else if (!Double.isNaN(actualMaximum) && !Double.isInfinite(actualMaximum)) {
+ center = actualMaximum;
+ }
+ if (value > center) {
+ return 0.0;
+ }
+ double min = Double.NaN;
+ if (!Double.isNaN(theoreticalMinimum) && !Double.isInfinite(theoreticalMinimum)) {
+ min = theoreticalMinimum;
+ }
+ else if (!Double.isNaN(actualMinimum) && !Double.isInfinite(actualMinimum)) {
+ min = actualMinimum;
+ }
+ if (!Double.isNaN(min) && !Double.isInfinite(min) && min != center) {
+ return (center - value) / (center - min);
+ }
+ return center - value;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/OrderingFromRelation.java b/src/de/lmu/ifi/dbs/elki/result/outlier/OrderingFromRelation.java
new file mode 100644
index 00000000..d4aa0cf7
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/OrderingFromRelation.java
@@ -0,0 +1,115 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.Collections;
+import java.util.Comparator;
+
+import de.lmu.ifi.dbs.elki.database.ids.ArrayModifiableDBIDs;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDUtil;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.result.OrderingResult;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIterator;
+import de.lmu.ifi.dbs.elki.utilities.iterator.IterableIteratorAdapter;
+
+/**
+ * Ordering obtained from an outlier score.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses Relation
+ */
+public class OrderingFromRelation implements OrderingResult {
+ /**
+ * Outlier scores.
+ */
+ protected Relation<Double> scores;
+
+ /**
+ * Factor for ascending (+1) and descending (-1) ordering.
+ */
+ protected int ascending = +1;
+
+ /**
+ * Constructor for outlier orderings
+ *
+ * @param scores outlier score result
+ * @param ascending Ascending when {@code true}, descending otherwise
+ */
+ public OrderingFromRelation(Relation<Double> scores, boolean ascending) {
+ super();
+ this.scores = scores;
+ this.ascending = ascending ? +1 : -1;
+ }
+
+ /**
+ * Ascending constructor.
+ *
+ * @param scores
+ */
+ public OrderingFromRelation(Relation<Double> scores) {
+ this(scores, true);
+ }
+
+ @Override
+ public DBIDs getDBIDs() {
+ return scores.getDBIDs();
+ }
+
+ @Override
+ public IterableIterator<DBID> iter(DBIDs ids) {
+ ArrayModifiableDBIDs sorted = DBIDUtil.newArray(ids);
+ Collections.sort(sorted, new ImpliedComparator());
+ return new IterableIteratorAdapter<DBID>(sorted);
+ }
+
+ @Override
+ public String getLongName() {
+ return scores.getLongName()+" Order";
+ }
+
+ @Override
+ public String getShortName() {
+ return scores.getShortName()+"_order";
+ }
+
+ /**
+ * Internal comparator, accessing the map to sort objects
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.exclude
+ */
+ protected final class ImpliedComparator implements Comparator<DBID> {
+ @Override
+ public int compare(DBID id1, DBID id2) {
+ Double k1 = scores.get(id1);
+ Double k2 = scores.get(id2);
+ assert (k1 != null);
+ assert (k2 != null);
+ return ascending * k2.compareTo(k1);
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierResult.java b/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierResult.java
new file mode 100644
index 00000000..ba8239ec
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierResult.java
@@ -0,0 +1,97 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.result.BasicResult;
+import de.lmu.ifi.dbs.elki.result.OrderingResult;
+
+/**
+ * Wrap a typical Outlier result, keeping direct references to the main result
+ * parts.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.composedOf OutlierScoreMeta
+ * @apiviz.composedOf Relation oneway - - contains
+ * @apiviz.composedOf OrderingFromRelation
+ */
+public class OutlierResult extends BasicResult {
+ /**
+ * Outlier score meta information
+ */
+ private OutlierScoreMeta meta;
+
+ /**
+ * Outlier scores.
+ */
+ private Relation<Double> scores;
+
+ /**
+ * Outlier ordering.
+ */
+ private OrderingResult ordering;
+
+ /**
+ * Constructor.
+ *
+ * @param meta Outlier score metadata.
+ * @param scores Scores result.
+ */
+ public OutlierResult(OutlierScoreMeta meta, Relation<Double> scores) {
+ super(scores.getLongName(), scores.getShortName());
+ this.meta = meta;
+ this.scores = scores;
+ this.ordering = new OrderingFromRelation(scores, !(meta instanceof InvertedOutlierScoreMeta));
+ this.addChildResult(scores);
+ this.addChildResult(ordering);
+ this.addChildResult(meta);
+ }
+
+ /**
+ * Get the outlier score meta data
+ *
+ * @return the outlier meta information
+ */
+ public OutlierScoreMeta getOutlierMeta() {
+ return meta;
+ }
+
+ /**
+ * Get the outlier scores association.
+ *
+ * @return the scores
+ */
+ public Relation<Double> getScores() {
+ return scores;
+ }
+
+ /**
+ * Get the outlier ordering
+ *
+ * @return the ordering
+ */
+ public OrderingResult getOrdering() {
+ return ordering;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierScoreMeta.java b/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierScoreMeta.java
new file mode 100644
index 00000000..da65f254
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/OutlierScoreMeta.java
@@ -0,0 +1,83 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.result.Result;
+
+/**
+ * Generic meta information about the value range of an outlier score.
+ *
+ * All values can be {@link Double#NaN} if not specified by the algorithm or not
+ * computed (actual values). For theoretical values, it is explicitly allowed to
+ * return {@link Double#NEGATIVE_INFINITY} or {@link Double#POSITIVE_INFINITY}.
+ *
+ * @author Erich Schubert
+ */
+public interface OutlierScoreMeta extends Result {
+ /**
+ * Get the actual minimum of the value range.
+ *
+ * @return minimum or {@link Double#NaN}
+ */
+ public double getActualMinimum();
+
+ /**
+ * Get the actual maximum of the value range.
+ *
+ * @return maximum or {@link Double#NaN}
+ */
+ public double getActualMaximum();
+
+ /**
+ * Get the theoretical minimum of the value range.
+ *
+ * @return theoretical minimum or {@link Double#NaN}
+ */
+ public double getTheoreticalMinimum();
+
+ /**
+ * Get the theoretical maximum of the value range.
+ *
+ * This value may be {@link Double#NEGATIVE_INFINITY} or {@link Double#NaN}.
+ *
+ * @return theoretical maximum or {@link Double#NaN}
+ */
+ public double getTheoreticalMaximum();
+
+ /**
+ * Get the theoretical baseline of the value range.
+ *
+ * It will be common to see {@link Double#POSITIVE_INFINITY} here.
+ *
+ * @return theoretical baseline or {@link Double#NaN}
+ */
+ public double getTheoreticalBaseline();
+
+ /**
+ * Return a normalized value of the outlier score.
+ *
+ * @param value outlier score
+ * @return Normalized value (in 0.0-1.0)
+ */
+ public double normalizeScore(double value);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/ProbabilisticOutlierScore.java b/src/de/lmu/ifi/dbs/elki/result/outlier/ProbabilisticOutlierScore.java
new file mode 100644
index 00000000..0f078a54
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/ProbabilisticOutlierScore.java
@@ -0,0 +1,125 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Outlier score that is a probability value in the range 0.0 - 1.0
+ *
+ * But the baseline may be different from 0.0!
+ *
+ * @author Erich Schubert
+ */
+public class ProbabilisticOutlierScore implements OutlierScoreMeta {
+ /**
+ * Actual minimum seen, if given by the algorithm.
+ */
+ private double actualMinimum = Double.NaN;
+ /**
+ * Actual maximum seen, if given by the algorithm.
+ */
+ private double actualMaximum = Double.NaN;
+ /**
+ * Theoretical baseline specified by the algorithm. Defaults to 0.0 in short constructor.
+ */
+ private double theoreticalBaseline = Double.NaN;
+
+ /**
+ * Default constructor. No actual values, Baseline 0.0
+ */
+ public ProbabilisticOutlierScore() {
+ this(Double.NaN, Double.NaN, 0.0);
+ }
+
+ /**
+ * Constructor with baseline only.
+ *
+ * @param theoreticalBaseline Baseline
+ */
+ public ProbabilisticOutlierScore(double theoreticalBaseline) {
+ this(Double.NaN, Double.NaN, theoreticalBaseline);
+ }
+
+ /**
+ * Constructor with actual values, and a baseline of 0.0
+ *
+ * @param actualMinimum actual minimum seen
+ * @param actualMaximum actual maximum seen
+ */
+ public ProbabilisticOutlierScore(double actualMinimum, double actualMaximum) {
+ this(actualMinimum, actualMaximum, 0.0);
+ }
+
+ /**
+ * Full constructor.
+ *
+ * @param actualMinimum actual minimum seen
+ * @param actualMaximum actual maximum seen
+ * @param theoreticalBaseline theoretical baseline
+ */
+ public ProbabilisticOutlierScore(double actualMinimum, double actualMaximum, double theoreticalBaseline) {
+ super();
+ this.actualMinimum = actualMinimum;
+ this.actualMaximum = actualMaximum;
+ this.theoreticalBaseline = theoreticalBaseline;
+ }
+
+ @Override
+ public double getActualMinimum() {
+ return actualMinimum;
+ }
+
+ @Override
+ public double getActualMaximum() {
+ return actualMaximum;
+ }
+
+ @Override
+ public double getTheoreticalBaseline() {
+ return theoreticalBaseline;
+ }
+
+ @Override
+ public double getTheoreticalMaximum() {
+ return 1.0;
+ }
+
+ @Override
+ public double getTheoreticalMinimum() {
+ return 0.0;
+ }
+
+ @Override
+ public double normalizeScore(double value) {
+ return value;
+ }
+
+ @Override
+ public String getLongName() {
+ return "Outlier Score Metadata";
+ }
+
+ @Override
+ public String getShortName() {
+ return "outlier-score-meta";
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/QuotientOutlierScoreMeta.java b/src/de/lmu/ifi/dbs/elki/result/outlier/QuotientOutlierScoreMeta.java
new file mode 100644
index 00000000..ef477b34
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/QuotientOutlierScoreMeta.java
@@ -0,0 +1,71 @@
+package de.lmu.ifi.dbs.elki.result.outlier;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/**
+ * Score for outlier values generated by a quotient.
+ *
+ * @author Erich Schubert
+ *
+ */
+public class QuotientOutlierScoreMeta extends BasicOutlierScoreMeta {
+ /**
+ * Constructor with actual minimum and maximum values.
+ *
+ * If possible, please also specify a baseline!
+ *
+ * @param actualMinimum Actual minimum
+ * @param actualMaximum Actual maximum
+ */
+ public QuotientOutlierScoreMeta(double actualMinimum, double actualMaximum) {
+ super(actualMinimum, actualMaximum);
+ }
+
+ /**
+ * Constructor with all range values.
+ *
+ * If possible, please also specify a baseline!
+ *
+ * @param actualMinimum Actual minimum
+ * @param actualMaximum Actual maximum
+ * @param theoreticalMinimum Theoretical minimum
+ * @param theoreticalMaximum Theoretical maximum
+ */
+ public QuotientOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum) {
+ super(actualMinimum, actualMaximum, theoreticalMinimum, theoreticalMaximum);
+ }
+
+ /**
+ * Full constructor with all values.
+ *
+ * @param actualMinimum Actual minimum.
+ * @param actualMaximum Actual maximum.
+ * @param theoreticalMinimum Theoretical minimum.
+ * @param theoreticalMaximum Theoretical maximum.
+ * @param theoreticalBaseline Theoretical baseline.
+ */
+ public QuotientOutlierScoreMeta(double actualMinimum, double actualMaximum, double theoreticalMinimum, double theoreticalMaximum, double theoreticalBaseline) {
+ super(actualMinimum, actualMaximum, theoreticalMinimum, theoreticalMaximum, theoreticalBaseline);
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/outlier/package-info.java b/src/de/lmu/ifi/dbs/elki/result/outlier/package-info.java
new file mode 100644
index 00000000..c6b61bfe
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/outlier/package-info.java
@@ -0,0 +1,27 @@
+/**
+ * <p>Outlier result classes</p>
+ *
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result.outlier; \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/package-info.java b/src/de/lmu/ifi/dbs/elki/result/package-info.java
new file mode 100644
index 00000000..aba3dec6
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/package-info.java
@@ -0,0 +1,33 @@
+/**
+ * <p>Result types, representation and handling</p>
+ *
+ * @apiviz.exclude java.util.*
+ * @apiviz.exclude de.lmu.ifi.dbs.elki.algorithm.*
+ * @apiviz.exclude de.lmu.ifi.dbs.elki.database.*
+ * @apiviz.exclude de.lmu.ifi.dbs.elki.evaluation.*
+ * @apiviz.exclude de.lmu.ifi.dbs.elki.visualization.*
+ * @apiviz.exclude de.lmu.ifi.dbs.elki.workflow.*
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result; \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/MultipleFilesOutput.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/MultipleFilesOutput.java
new file mode 100644
index 00000000..91bdc959
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/MultipleFilesOutput.java
@@ -0,0 +1,187 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.zip.GZIPOutputStream;
+
+import de.lmu.ifi.dbs.elki.logging.Logging;
+
+/**
+ * Manage output to multiple files.
+ *
+ * @author Erich Schubert
+ */
+public class MultipleFilesOutput implements StreamFactory {
+ /**
+ * File name extension.
+ */
+ private final static String EXTENSION = ".txt";
+
+ /**
+ * GZip extra file extension
+ */
+ private final static String GZIP_EXTENSION = ".gz";
+
+ /**
+ * Default stream to write to when no name is supplied.
+ */
+ private PrintStream defaultStream = null;
+
+ /**
+ * Base file name.
+ */
+ private File basename;
+
+ /**
+ * HashMap of open print streams.
+ */
+ private HashMap<String, PrintStream> map = new HashMap<String, PrintStream>();
+
+ /**
+ * Control gzip compression of output.
+ */
+ private boolean usegzip = false;
+
+ /**
+ * Logger for debugging.
+ */
+ private final static Logging logger = Logging.getLogger(MultipleFilesOutput.class);
+
+ /**
+ * Constructor
+ *
+ * @param base Base file name (folder name)
+ */
+ public MultipleFilesOutput(File base) {
+ this(base, false);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param base Base file name (folder name)
+ * @param gzip Use gzip compression.
+ */
+ public MultipleFilesOutput(File base, boolean gzip) {
+ this.basename = base;
+ this.usegzip = gzip;
+ }
+
+ /**
+ * Retrieve/open the default output stream.
+ *
+ * @return default output stream
+ * @throws IOException
+ */
+ private PrintStream getDefaultStream() throws IOException {
+ if(defaultStream != null) {
+ return defaultStream;
+ }
+ defaultStream = newStream("default");
+ return defaultStream;
+ }
+
+ /**
+ * Open a new stream of the given name
+ *
+ * @param name file name (which will be appended to the base name)
+ * @return stream object for the given name
+ * @throws IOException
+ */
+ private PrintStream newStream(String name) throws IOException {
+ if (logger.isDebuggingFiner()) {
+ logger.debugFiner("Requested stream: "+name);
+ }
+ PrintStream res = map.get(name);
+ if(res != null) {
+ return res;
+ }
+ // Ensure the directory exists:
+ if(!basename.exists()) {
+ basename.mkdirs();
+ }
+ String fn = basename.getAbsolutePath() + File.separator + name + EXTENSION;
+ if (usegzip) {
+ fn = fn + GZIP_EXTENSION;
+ }
+ File n = new File(fn);
+ OutputStream os = new FileOutputStream(n);
+ if (usegzip) {
+ // wrap into gzip stream.
+ os = new GZIPOutputStream(os);
+ }
+ res = new PrintStream(os);
+ if (logger.isDebuggingFiner()) {
+ logger.debugFiner("Opened new output stream:"+fn);
+ }
+ // cache.
+ map.put(name, res);
+ return res;
+ }
+
+ /**
+ * Retrieve the output stream for the given file name.
+ */
+ @Override
+ public PrintStream openStream(String filename) throws IOException {
+ if(filename == null) {
+ return getDefaultStream();
+ }
+ return newStream(filename);
+ }
+
+ /**
+ * Get GZIP compression flag.
+ *
+ * @return if GZIP compression is enabled
+ */
+ protected boolean isGzipCompression() {
+ return usegzip;
+ }
+
+ /**
+ * Set GZIP compression flag.
+ *
+ * @param usegzip use GZIP compression
+ */
+ protected void setGzipCompression(boolean usegzip) {
+ this.usegzip = usegzip;
+ }
+
+ /**
+ * Close (and forget) all output streams.
+ */
+ @Override
+ public synchronized void closeAllStreams() {
+ for (PrintStream s : map.values()) {
+ s.close();
+ }
+ map.clear();
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/SingleStreamOutput.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/SingleStreamOutput.java
new file mode 100644
index 00000000..1ec93091
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/SingleStreamOutput.java
@@ -0,0 +1,148 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Class to output all result data to a single stream (e.g. Stdout, single file)
+ *
+ * @author Erich Schubert
+ *
+ */
+public class SingleStreamOutput implements StreamFactory {
+ /**
+ * Output stream
+ */
+ private PrintStream stream;
+
+ /**
+ * Constructor using stdout.
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput() throws IOException {
+ this(FileDescriptor.out);
+ }
+
+ /**
+ * Constructor using stdout
+ *
+ * @param gzip Use gzip compression
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(boolean gzip) throws IOException {
+ this(FileDescriptor.out, gzip);
+ }
+
+ /**
+ * Constructor with given file name.
+ *
+ * @param out filename
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(File out) throws IOException {
+ this(new FileOutputStream(out));
+ }
+
+ /**
+ * Constructor with given file name.
+ *
+ * @param out filename
+ * @param gzip Use gzip compression
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(File out, boolean gzip) throws IOException {
+ this(new FileOutputStream(out), gzip);
+ }
+
+ /**
+ * Constructor with given FileDescriptor
+ *
+ * @param out file descriptor
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(FileDescriptor out) throws IOException {
+ this(new FileOutputStream(out));
+ }
+
+ /**
+ * Constructor with given FileDescriptor
+ *
+ * @param out file descriptor
+ * @param gzip Use gzip compression
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(FileDescriptor out, boolean gzip) throws IOException {
+ this(new FileOutputStream(out), gzip);
+ }
+
+ /**
+ * Constructor with given FileOutputStream.
+ *
+ * @param out File output stream
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(FileOutputStream out) throws IOException {
+ this(out, false);
+ }
+
+ /**
+ * Constructor with given FileOutputStream.
+ *
+ * @param out File output stream
+ * @param gzip Use gzip compression
+ * @throws IOException on IO error
+ */
+ public SingleStreamOutput(FileOutputStream out, boolean gzip) throws IOException {
+ OutputStream os = out;
+ if (gzip) {
+ // wrap into gzip stream.
+ os = new GZIPOutputStream(os);
+ }
+ this.stream = new PrintStream(os);
+ }
+
+ /**
+ * Return the objects shared print stream.
+ *
+ * @param filename ignored filename for SingleStreamOutput, as the name suggests
+ */
+ @Override
+ public PrintStream openStream(String filename) {
+ return stream;
+ }
+
+ /**
+ * Close output stream.
+ */
+ @Override
+ public void closeAllStreams() {
+ stream.close();
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/StreamFactory.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/StreamFactory.java
new file mode 100644
index 00000000..a769f6c8
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/StreamFactory.java
@@ -0,0 +1,49 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * Interface for output handling (single file, multiple files, ...)
+ *
+ * @author Erich Schubert
+ */
+public interface StreamFactory {
+ /**
+ * Retrieve a print stream for output using the given label.
+ * Note that multiple labels MAY result in the same PrintStream, so you
+ * should be printing to only one stream at a time to avoid mixing outputs.
+ *
+ * @param label Output label.
+ * @return stream object for the given label
+ * @throws IOException on IO error
+ */
+ public PrintStream openStream(String label) throws IOException;
+
+ /**
+ * Close (and forget) all streams the factory has opened.
+ */
+ public void closeAllStreams();
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriteable.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriteable.java
new file mode 100644
index 00000000..84d18c40
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriteable.java
@@ -0,0 +1,39 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Interface for objects providing a text serialization suitable for
+ * human reading and storage in CSV files.
+ *
+ * @author Erich Schubert
+ */
+//TODO: split TextWriteable interface into data writing and metadata writing?
+public interface TextWriteable {
+ /**
+ * Write self to the given {@link TextWriterStream}
+ * @param out Output writer
+ * @param label Label
+ */
+ public void writeToText(TextWriterStream out, String label);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriter.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriter.java
new file mode 100644
index 00000000..e6fac1f8
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriter.java
@@ -0,0 +1,431 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import de.lmu.ifi.dbs.elki.data.Cluster;
+import de.lmu.ifi.dbs.elki.data.Clustering;
+import de.lmu.ifi.dbs.elki.data.FeatureVector;
+import de.lmu.ifi.dbs.elki.data.HierarchicalClassLabel;
+import de.lmu.ifi.dbs.elki.data.LabelList;
+import de.lmu.ifi.dbs.elki.data.SimpleClassLabel;
+import de.lmu.ifi.dbs.elki.data.model.Model;
+import de.lmu.ifi.dbs.elki.data.type.TypeUtil;
+import de.lmu.ifi.dbs.elki.database.Database;
+import de.lmu.ifi.dbs.elki.database.ids.DBID;
+import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
+import de.lmu.ifi.dbs.elki.database.relation.Relation;
+import de.lmu.ifi.dbs.elki.datasource.bundle.SingleObjectBundle;
+import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
+import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+import de.lmu.ifi.dbs.elki.result.CollectionResult;
+import de.lmu.ifi.dbs.elki.result.HierarchicalResult;
+import de.lmu.ifi.dbs.elki.result.IterableResult;
+import de.lmu.ifi.dbs.elki.result.OrderingResult;
+import de.lmu.ifi.dbs.elki.result.Result;
+import de.lmu.ifi.dbs.elki.result.ResultUtil;
+import de.lmu.ifi.dbs.elki.result.SettingsResult;
+import de.lmu.ifi.dbs.elki.result.textwriter.naming.NamingScheme;
+import de.lmu.ifi.dbs.elki.result.textwriter.naming.SimpleEnumeratingScheme;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterDoubleDoublePair;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterObjectArray;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterObjectComment;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterObjectInline;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterPair;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterTextWriteable;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterTriple;
+import de.lmu.ifi.dbs.elki.result.textwriter.writers.TextWriterVector;
+import de.lmu.ifi.dbs.elki.utilities.HandlerList;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.SerializedParameterization;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ClassParameter;
+import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter;
+import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
+import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
+import de.lmu.ifi.dbs.elki.utilities.pairs.Triple;
+
+/**
+ * Class to write a result to human-readable text output
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.landmark
+ * @apiviz.uses TextWriterStream oneway - - writesTo
+ * @apiviz.composedOf TextWriterWriterInterface
+ * @apiviz.has NamingScheme
+ */
+public class TextWriter {
+ /**
+ * Extension for txt-files.
+ */
+ public static final String FILE_EXTENSION = ".txt";
+
+ /**
+ * Hash map for supported classes in writer.
+ */
+ public final static HandlerList<TextWriterWriterInterface<?>> writers = new HandlerList<TextWriterWriterInterface<?>>();
+
+ /**
+ * Add some default handlers
+ */
+ static {
+ TextWriterObjectInline trivialwriter = new TextWriterObjectInline();
+ writers.insertHandler(Object.class, new TextWriterObjectComment());
+ writers.insertHandler(Pair.class, new TextWriterPair());
+ writers.insertHandler(DoubleDoublePair.class, new TextWriterDoubleDoublePair());
+ writers.insertHandler(Triple.class, new TextWriterTriple());
+ writers.insertHandler(FeatureVector.class, trivialwriter);
+ // these object can be serialized inline with toString()
+ writers.insertHandler(String.class, trivialwriter);
+ writers.insertHandler(Double.class, trivialwriter);
+ writers.insertHandler(Integer.class, trivialwriter);
+ writers.insertHandler(String[].class, new TextWriterObjectArray<String>());
+ writers.insertHandler(Double[].class, new TextWriterObjectArray<Double>());
+ writers.insertHandler(Integer[].class, new TextWriterObjectArray<Integer>());
+ writers.insertHandler(BitSet.class, trivialwriter);
+ writers.insertHandler(Vector.class, new TextWriterVector());
+ writers.insertHandler(Distance.class, trivialwriter);
+ writers.insertHandler(SimpleClassLabel.class, trivialwriter);
+ writers.insertHandler(HierarchicalClassLabel.class, trivialwriter);
+ writers.insertHandler(LabelList.class, trivialwriter);
+ writers.insertHandler(DBID.class, trivialwriter);
+ // Objects that have an own writeToText method.
+ writers.insertHandler(TextWriteable.class, new TextWriterTextWriteable());
+ }
+
+ /**
+ * For producing unique filenames.
+ */
+ protected Map<String, Object> filenames = new HashMap<String, Object>();
+
+ /**
+ * Try to find a unique file name.
+ *
+ * @param result Result we print
+ * @param filenamepre File name prefix to use
+ * @return unique filename
+ */
+ protected String getFilename(Object result, String filenamepre) {
+ if(filenamepre == null || filenamepre.length() == 0) {
+ filenamepre = "result";
+ }
+ int i = 0;
+ while(true) {
+ String filename;
+ if(i > 0) {
+ filename = filenamepre + "-" + i;
+ }
+ else {
+ filename = filenamepre;
+ }
+ Object existing = filenames.get(filename);
+ if(existing == null || existing == result) {
+ filenames.put(filename, result);
+ return filename;
+ }
+ i++;
+ }
+ }
+
+ /**
+ * Writes a header providing information concerning the underlying database
+ * and the specified parameter-settings.
+ *
+ * @param out the print stream where to write
+ * @param sr the settings to be written into the header
+ */
+ protected void printSettings(TextWriterStream out, List<SettingsResult> sr) {
+ out.commentPrintSeparator();
+ out.commentPrintLn("Settings:");
+
+ if(sr != null) {
+ for(SettingsResult settings : sr) {
+ Object last = null;
+ for(Pair<Object, Parameter<?, ?>> setting : settings.getSettings()) {
+ if(setting.first != last && setting.first != null) {
+ if(last != null) {
+ out.commentPrintLn("");
+ }
+ String name;
+ try {
+ if(setting.first instanceof Class) {
+ name = ((Class<?>) setting.first).getName();
+ }
+ else {
+ name = setting.first.getClass().getName();
+ }
+ if(ClassParameter.class.isInstance(setting.first)) {
+ name = ((ClassParameter<?>) setting.first).getValue().getName();
+ }
+ }
+ catch(NullPointerException e) {
+ name = "[null]";
+ }
+ out.commentPrintLn(name);
+ last = setting.first;
+ }
+ String name = setting.second.getOptionID().getName();
+ String value = "[unset]";
+ try {
+ if(setting.second.isDefined()) {
+ value = setting.second.getValueAsString();
+ }
+ }
+ catch(NullPointerException e) {
+ value = "[null]";
+ }
+ out.commentPrintLn(SerializedParameterization.OPTION_PREFIX + name + " " + value);
+ }
+ }
+ }
+
+ out.commentPrintSeparator();
+ out.flush();
+ }
+
+ /**
+ * Stream output.
+ *
+ * @param db Database object
+ * @param r Result class
+ * @param streamOpener output stream manager
+ * @throws UnableToComplyException when no usable results were found
+ * @throws IOException on IO error
+ */
+ public void output(Database db, Result r, StreamFactory streamOpener) throws UnableToComplyException, IOException {
+ List<Relation<?>> ra = null;
+ List<OrderingResult> ro = null;
+ List<Clustering<? extends Model>> rc = null;
+ List<IterableResult<?>> ri = null;
+ List<SettingsResult> rs = null;
+ HashSet<Result> otherres = null;
+
+ ra = ResultUtil.getRelations(r);
+ ro = ResultUtil.getOrderingResults(r);
+ rc = ResultUtil.getClusteringResults(r);
+ ri = ResultUtil.getIterableResults(r);
+ rs = ResultUtil.getSettingsResults(r);
+ // collect other results
+ if(r instanceof HierarchicalResult) {
+ final List<Result> resultList = ResultUtil.filterResults((HierarchicalResult) r, Result.class);
+ otherres = new HashSet<Result>(resultList);
+ otherres.removeAll(ra);
+ otherres.removeAll(ro);
+ otherres.removeAll(rc);
+ otherres.removeAll(ri);
+ otherres.removeAll(rs);
+ otherres.remove(db);
+ Iterator<Result> it = otherres.iterator();
+ while(it.hasNext()) {
+ if(it.next() instanceof HierarchicalResult) {
+ it.remove();
+ }
+ }
+ }
+
+ if(ra == null && ro == null && rc == null && ri == null) {
+ throw new UnableToComplyException("No printable result found.");
+ }
+
+ if(ri != null && ri.size() > 0) {
+ // TODO: associations are not passed to ri results.
+ for(IterableResult<?> rii : ri) {
+ writeIterableResult(streamOpener, rii, rs);
+ }
+ }
+ if(rc != null && rc.size() > 0) {
+ for(Clustering<?> c : rc) {
+ NamingScheme naming = new SimpleEnumeratingScheme(c);
+ for(Cluster<?> clus : c.getAllClusters()) {
+ writeClusterResult(db, streamOpener, clus, ra, naming, rs);
+ }
+ }
+ }
+ if(ro != null && ro.size() > 0) {
+ for(OrderingResult ror : ro) {
+ writeOrderingResult(db, streamOpener, ror, ra, rs);
+ }
+ }
+ if(otherres != null && otherres.size() > 0) {
+ for(Result otherr : otherres) {
+ writeOtherResult(streamOpener, otherr, rs);
+ }
+ }
+ }
+
+ private void printObject(TextWriterStream out, Database db, final DBID objID, List<Relation<?>> ra) throws UnableToComplyException, IOException {
+ SingleObjectBundle bundle = db.getBundle(objID);
+ // Write database element itself.
+ for(int i = 0; i < bundle.metaLength(); i++) {
+ Object obj = bundle.data(i);
+ TextWriterWriterInterface<?> owriter = out.getWriterFor(obj);
+ if(owriter == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + obj.getClass().getSimpleName());
+ }
+ String lbl = null;
+ // TODO: ugly compatibility hack...
+ if(TypeUtil.DBID.isAssignableFromType(bundle.meta(i))) {
+ lbl = "ID";
+ }
+ owriter.writeObject(out, lbl, obj);
+ }
+
+ // print the annotations
+ if(ra != null) {
+ for(Relation<?> a : ra) {
+ String label = a.getShortName();
+ Object value = a.get(objID);
+ if(value == null) {
+ continue;
+ }
+ TextWriterWriterInterface<?> writer = out.getWriterFor(value);
+ if(writer == null) {
+ // Ignore
+ continue;
+ }
+ writer.writeObject(out, label, value);
+ }
+ }
+ out.flush();
+ out.flush();
+ }
+
+ private void writeOtherResult(StreamFactory streamOpener, Result r, List<SettingsResult> rs) throws UnableToComplyException, IOException {
+ PrintStream outStream = streamOpener.openStream(getFilename(r, r.getShortName()));
+ TextWriterStream out = new TextWriterStream(outStream, writers);
+ TextWriterWriterInterface<?> owriter = out.getWriterFor(r);
+ if(owriter == null) {
+ throw new UnableToComplyException("No handler for result class: " + r.getClass().getSimpleName());
+ }
+ // Write settings preamble
+ printSettings(out, rs);
+ // Write data
+ owriter.writeObject(out, null, r);
+ out.flush();
+ }
+
+ private void writeClusterResult(Database db, StreamFactory streamOpener, Cluster<?> clus, List<Relation<?>> ra, NamingScheme naming, List<SettingsResult> sr) throws FileNotFoundException, UnableToComplyException, IOException {
+ String filename = null;
+ if(naming != null) {
+ filename = filenameFromLabel(naming.getNameFor(clus));
+ }
+ else {
+ filename = "cluster";
+ }
+
+ PrintStream outStream = streamOpener.openStream(getFilename(clus, filename));
+ TextWriterStream out = new TextWriterStream(outStream, writers);
+ printSettings(out, sr);
+
+ // Write cluster information
+ out.commentPrintLn("Cluster: " + naming.getNameFor(clus));
+ if(clus.getParents().size() > 0) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("Parents:");
+ for(Cluster<?> parent : clus.getParents()) {
+ buf.append(" ").append(naming.getNameFor(parent));
+ }
+ out.commentPrintLn(buf.toString());
+ }
+ if(clus.getChildren().size() > 0) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("Children:");
+ for(Cluster<?> child : clus.getChildren()) {
+ buf.append(" ").append(naming.getNameFor(child));
+ }
+ out.commentPrintLn(buf.toString());
+ }
+ out.flush();
+
+ // print ids.
+ DBIDs ids = clus.getIDs();
+ Iterator<DBID> iter = ids.iterator();
+
+ while(iter.hasNext()) {
+ printObject(out, db, iter.next(), ra);
+ }
+ out.commentPrintSeparator();
+ out.flush();
+ }
+
+ private void writeIterableResult(StreamFactory streamOpener, IterableResult<?> ri, List<SettingsResult> sr) throws UnableToComplyException, IOException {
+ PrintStream outStream = streamOpener.openStream(getFilename(ri, ri.getShortName()));
+ TextWriterStream out = new TextWriterStream(outStream, writers);
+ printSettings(out, sr);
+
+ // hack to print collectionResult header information
+ if(ri instanceof CollectionResult<?>) {
+ final Collection<String> hdr = ((CollectionResult<?>) ri).getHeader();
+ if(hdr != null) {
+ for(String header : hdr) {
+ out.commentPrintLn(header);
+ }
+ out.flush();
+ }
+ }
+ Iterator<?> i = ri.iterator();
+ while(i.hasNext()) {
+ Object o = i.next();
+ TextWriterWriterInterface<?> writer = out.getWriterFor(o);
+ if(writer != null) {
+ writer.writeObject(out, null, o);
+ }
+ out.flush();
+ }
+ out.commentPrintSeparator();
+ out.flush();
+ }
+
+ private void writeOrderingResult(Database db, StreamFactory streamOpener, OrderingResult or, List<Relation<?>> ra, List<SettingsResult> sr) throws IOException, UnableToComplyException {
+ PrintStream outStream = streamOpener.openStream(getFilename(or, or.getShortName()));
+ TextWriterStream out = new TextWriterStream(outStream, writers);
+ printSettings(out, sr);
+
+ Iterator<DBID> i = or.iter(or.getDBIDs());
+ while(i.hasNext()) {
+ printObject(out, db, i.next(), ra);
+ }
+ out.commentPrintSeparator();
+ out.flush();
+ }
+
+ /**
+ * Derive a file name from the cluster label.
+ *
+ * @param label cluster label
+ * @return cleaned label suitable for file names.
+ */
+ private String filenameFromLabel(String label) {
+ return label.toLowerCase().replaceAll("[^a-zA-Z0-9_.\\[\\]-]", "_");
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterStream.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterStream.java
new file mode 100644
index 00000000..9d50311f
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterStream.java
@@ -0,0 +1,255 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.PrintStream;
+
+import de.lmu.ifi.dbs.elki.utilities.HandlerList;
+
+/**
+ * Representation of an output stream to a text file.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses de.lmu.ifi.dbs.elki.result.textwriter.StreamFactory oneway - - wraps
+ */
+public class TextWriterStream {
+ /**
+ * Actual stream to write to.
+ */
+ private PrintStream outStream;
+
+ /**
+ * Buffer for inline data to output.
+ */
+ private StringBuffer inline;
+
+ /**
+ * Buffer for comment data to output.
+ */
+ private StringBuffer comment;
+
+ /**
+ * Handlers for various object types.
+ */
+ private HandlerList<TextWriterWriterInterface<?>> writers;
+
+ /**
+ * String to separate different entries while printing.
+ */
+ public static final String SEPARATOR = " ";
+
+ /**
+ * String to separate different entries while printing.
+ */
+ public static final String QUOTE = "# ";
+
+ /**
+ * Comment separator line.
+ * Since this will be printed without {@link #QUOTE} infront, it should be quoted string itself.
+ */
+ public static final String COMMENTSEP = "###############################################################";
+
+ /**
+ * System newline character(s)
+ */
+ private final static String NEWLINE = System.getProperty("line.separator");
+
+ /**
+ * Marker used in text serialization (and re-parsing)
+ */
+ public final static String SER_MARKER = "Serialization class:";
+
+ /**
+ * Force incomments flag
+ */
+ // TODO: solve this more gracefully
+ private boolean forceincomments = false;
+
+ /**
+ * Constructor.
+ *
+ * @param out Actual stream to write to
+ * @param writers Handlers for various data types
+ */
+ public TextWriterStream(PrintStream out, HandlerList<TextWriterWriterInterface<?>> writers) {
+ this.outStream = out;
+ this.writers = writers;
+ inline = new StringBuffer();
+ comment = new StringBuffer();
+ }
+
+ /**
+ * Print an object into the comments section
+ *
+ * @param line object to print into commments
+ */
+ public void commentPrint(Object line) {
+ comment.append(line);
+ }
+
+ /**
+ * Print an object into the comments section with trailing newline.
+ *
+ * @param line object to print into comments
+ */
+ public void commentPrintLn(Object line) {
+ comment.append(line);
+ comment.append(NEWLINE);
+ }
+
+ /**
+ * Print a newline into the comments section.
+ */
+ public void commentPrintLn() {
+ comment.append(NEWLINE);
+ }
+
+ /**
+ * Print a separator line in the comments section.
+ */
+ public void commentPrintSeparator() {
+ comment.append(COMMENTSEP + NEWLINE);
+ }
+
+ /**
+ * Print data into the inline part of the file.
+ * Data is sanitized: newlines are replaced with spaces, and text
+ * containing separators is put in quotes. Quotes and escape characters
+ * are escaped.
+ *
+ * @param o object to print
+ */
+ public void inlinePrint(Object o) {
+ if (forceincomments) {
+ commentPrint(o);
+ return;
+ }
+ if (inline.length() > 0) {
+ inline.append(SEPARATOR);
+ }
+ // remove newlines
+ String str = o.toString().replace(NEWLINE," ");
+ // escaping
+ str = str.replace("\\","\\\\").replace("\"","\\\"");
+ // when needed, add quotes.
+ if (str.contains(SEPARATOR)) {
+ str = "\""+str+"\"";
+ }
+ inline.append(str);
+ }
+
+ /**
+ * Print data into the inline part of the file WITHOUT checking for
+ * separators (and thus quoting).
+ *
+ * @param o object to print.
+ */
+ public void inlinePrintNoQuotes(Object o) {
+ if (forceincomments) {
+ commentPrint(o);
+ return;
+ }
+ if (inline.length() > 0) {
+ inline.append(SEPARATOR);
+ }
+ // remove newlines
+ String str = o.toString().replace(NEWLINE," ");
+ // escaping
+ str = str.replace("\\","\\\\").replace("\"","\\\"");
+ inline.append(str);
+ }
+
+ /**
+ * Flush output:
+ * write inline data, then write comment section. Reset streams.
+ */
+ public void flush() {
+ if (inline.length() > 0) {
+ outStream.println(inline);
+ }
+ inline.setLength(0);
+ if (comment.length() > 0) {
+ quotePrintln(outStream, comment.toString());
+ }
+ comment.setLength(0);
+ }
+
+ /**
+ * Quoted println. All lines written will be prefixed with {@link #QUOTE}
+ *
+ * @param outStream output stream to write to
+ * @param data data to print
+ */
+ private void quotePrintln(PrintStream outStream, String data) {
+ String[] lines = data.split("\r\n|\r|\n");
+ for (String line : lines) {
+ if (line.equals(COMMENTSEP)) {
+ outStream.println(COMMENTSEP);
+ } else {
+ outStream.println(QUOTE + line);
+ }
+ }
+ }
+
+ /**
+ * Retrieve an appropriate writer from the handler list.
+ *
+ * @param o query object
+ * @return appropriate write, if available
+ */
+ public TextWriterWriterInterface<?> getWriterFor(Object o) {
+ return writers.getHandler(o);
+ }
+
+ /**
+ * Restore a vector undoing any normalization that was applied.
+ * (This class does not support normalization, it is only provided
+ * by derived classes, which will then have to use generics.)
+ *
+ * @param <O> Object class
+ * @param v vector to restore
+ * @return restored value.
+ */
+ public <O> O normalizationRestore(O v) {
+ return v;
+ }
+
+ /**
+ * Test force-in-comments flag.
+ *
+ * @return flag value
+ */
+ protected boolean isForceincomments() {
+ return forceincomments;
+ }
+
+ /**
+ * Set the force-in-comments flag.
+ *
+ * @param forceincomments the new flag value
+ */
+ protected void setForceincomments(boolean forceincomments) {
+ this.forceincomments = forceincomments;
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterWriterInterface.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterWriterInterface.java
new file mode 100644
index 00000000..71f8cf12
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/TextWriterWriterInterface.java
@@ -0,0 +1,61 @@
+package de.lmu.ifi.dbs.elki.result.textwriter;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.IOException;
+
+import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
+
+/**
+ * Base class for object writers.
+ *
+ * @author Erich Schubert
+ *
+ * @param <O> Object type (usually the class itself)
+ */
+public abstract class TextWriterWriterInterface<O> {
+ /**
+ * Write a given object to the output stream.
+ *
+ * @param out Output stream
+ * @param label Label to prefix
+ * @param object object to output
+ * @throws UnableToComplyException on errors
+ * @throws IOException on IO errors
+ */
+ public abstract void write(TextWriterStream out, String label, O object) throws UnableToComplyException, IOException;
+
+ /**
+ * Non-type-checking version.
+ *
+ * @param out Output stream
+ * @param label Label to prefix
+ * @param object object to output
+ * @throws UnableToComplyException on errors
+ * @throws IOException on IO errors
+ */
+ @SuppressWarnings("unchecked")
+ public final void writeObject(TextWriterStream out, String label, Object object) throws UnableToComplyException, IOException {
+ write(out, label, (O) object);
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/NamingScheme.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/NamingScheme.java
new file mode 100644
index 00000000..5d885b35
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/NamingScheme.java
@@ -0,0 +1,42 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.naming;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.data.Cluster;
+
+/**
+ * Naming scheme implementation for clusterings.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses Cluster
+ */
+public interface NamingScheme {
+ /**
+ * Retrieve a name for the given cluster.
+ *
+ * @param cluster cluster to get a name for
+ * @return cluster name
+ */
+ public String getNameFor(Cluster<?> cluster);
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/SimpleEnumeratingScheme.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/SimpleEnumeratingScheme.java
new file mode 100644
index 00000000..102a017f
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/SimpleEnumeratingScheme.java
@@ -0,0 +1,109 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.naming;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.lmu.ifi.dbs.elki.data.Cluster;
+import de.lmu.ifi.dbs.elki.data.Clustering;
+
+/**
+ * Simple enumerating naming scheme. Cluster names are generated as follows: -
+ * if the cluster has a name assigned, use it - otherwise use getNameAutomatic()
+ * as name, and add an enumeration postfix
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses Clustering
+ */
+public class SimpleEnumeratingScheme implements NamingScheme {
+ /**
+ * Clustering this scheme is applied to.
+ */
+ private Clustering<?> clustering;
+
+ /**
+ * count how often each name occurred so far.
+ */
+ private Map<String, Integer> namecount = new HashMap<String, Integer>();
+
+ /**
+ * Assigned cluster names.
+ */
+ private Map<Cluster<?>, String> names = new HashMap<Cluster<?>, String>();
+
+ /**
+ * This is the postfix added to the first cluster, which will be removed when
+ * there is only one cluster of this name.
+ */
+ private final static String nullpostfix = " " + Integer.toString(0);
+
+ /**
+ * Constructor.
+ *
+ * @param clustering Clustering result to name.
+ */
+ public SimpleEnumeratingScheme(Clustering<?> clustering) {
+ super();
+ this.clustering = clustering;
+ updateNames();
+ }
+
+ /**
+ * Assign names to each cluster (which doesn't have a name yet)
+ */
+ private void updateNames() {
+ for(Cluster<?> cluster : clustering.getAllClusters()) {
+ if(names.get(cluster) == null) {
+ String sugname = cluster.getNameAutomatic();
+ Integer count = namecount.get(sugname);
+ if(count == null) {
+ count = new Integer(0);
+ }
+ names.put(cluster, sugname + " " + count.toString());
+ count++;
+ namecount.put(sugname, count);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the cluster name. When a name has not yet been assigned, call
+ * {@link #updateNames}
+ */
+ @Override
+ public String getNameFor(Cluster<?> cluster) {
+ String nam = names.get(cluster);
+ if(nam == null) {
+ updateNames();
+ nam = names.get(cluster);
+ }
+ if(nam.endsWith(nullpostfix)) {
+ if(namecount.get(nam.substring(0, nam.length() - nullpostfix.length())) == 1) {
+ nam = nam.substring(0, nam.length() - nullpostfix.length());
+ }
+ }
+ return nam;
+ }
+} \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/package-info.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/package-info.java
new file mode 100644
index 00000000..8d10da45
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/naming/package-info.java
@@ -0,0 +1,26 @@
+/**
+ * <p>Naming schemes for clusters (for output when an algorithm doesn't generate cluster names).</p>
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result.textwriter.naming; \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/package-info.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/package-info.java
new file mode 100644
index 00000000..b2abeb5b
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/package-info.java
@@ -0,0 +1,27 @@
+/**
+ * <p>Text serialization (CSV, Gnuplot, Console, ...)</p>
+ *
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result.textwriter; \ No newline at end of file
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterDoubleDoublePair.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterDoubleDoublePair.java
new file mode 100644
index 00000000..b8f25b6a
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterDoubleDoublePair.java
@@ -0,0 +1,53 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+
+/*
+ This file is part of ELKI:
+ Environment for Developing KDD-Applications Supported by Index-Structures
+
+ Copyright (C) 2011
+ Ludwig-Maximilians-Universität München
+ Lehr- und Forschungseinheit für Datenbanksysteme
+ ELKI Development Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+import de.lmu.ifi.dbs.elki.utilities.pairs.DoubleDoublePair;
+
+/**
+ * Write a pair
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterDoubleDoublePair extends TextWriterWriterInterface<DoubleDoublePair> {
+ /**
+ * Serialize a pair, component-wise
+ */
+ @Override
+ public void write(TextWriterStream out, String label, DoubleDoublePair object) {
+ if(object != null) {
+ String res;
+ if(label != null) {
+ res = label + "=" + object.first + "," + object.second;
+ }
+ else {
+ res = object.first + " " + object.second;
+ }
+ out.inlinePrintNoQuotes(res);
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectArray.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectArray.java
new file mode 100644
index 00000000..2ab3428c
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectArray.java
@@ -0,0 +1,51 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+
+/**
+ * Write an object into the inline section, using the objects toString method.
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterObjectArray<T> extends TextWriterWriterInterface<T[]> {
+ /**
+ * Serialize an object into the inline section.
+ */
+ @Override
+ public void write(TextWriterStream out, String label, T[] v) {
+ StringBuffer buf = new StringBuffer();
+ if(label != null) {
+ buf.append(label).append("=");
+ }
+ if(v != null) {
+ for (T o : v) {
+ buf.append(o.toString());
+ }
+ }
+ out.inlinePrintNoQuotes(buf.toString());
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectComment.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectComment.java
new file mode 100644
index 00000000..05b6db0d
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectComment.java
@@ -0,0 +1,50 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+
+/**
+ * Write an object into the comments section, using the objects toString()
+ * method.
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterObjectComment extends TextWriterWriterInterface<Object> {
+ /**
+ * Put an object into the comment section
+ */
+ @Override
+ public void write(TextWriterStream out, String label, Object object) {
+ String res = "";
+ if(label != null) {
+ res = res + label + "=";
+ }
+ if(object != null) {
+ res = res + object.toString();
+ }
+ out.commentPrintLn(res);
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectInline.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectInline.java
new file mode 100644
index 00000000..e327cc8f
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterObjectInline.java
@@ -0,0 +1,54 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+
+/**
+ * Write an object into the inline section, using the objects toString method.
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterObjectInline extends TextWriterWriterInterface<Object> {
+ /**
+ * Serialize an object into the inline section.
+ */
+ @Override
+ public void write(TextWriterStream out, String label, Object object) {
+ String res = "";
+ if(label != null) {
+ res = res + label + "=";
+ }
+ if(object != null) {
+ if(label != null) {
+ res = res + object.toString().replace(" ", "");
+ }
+ else {
+ res = res + object.toString();
+ }
+ }
+ out.inlinePrintNoQuotes(res);
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterPair.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterPair.java
new file mode 100644
index 00000000..3566f94a
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterPair.java
@@ -0,0 +1,64 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.IOException;
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
+import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
+
+/**
+ * Write a pair
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterPair extends TextWriterWriterInterface<Pair<?,?>> {
+ /**
+ * Serialize a pair, component-wise
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void write(TextWriterStream out, String label, Pair<?,?> object) throws UnableToComplyException, IOException {
+ if (object != null) {
+ Object first = object.getFirst();
+ if (first != null) {
+ TextWriterWriterInterface<Object> tw = (TextWriterWriterInterface<Object>) out.getWriterFor(first);
+ if (tw == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + first.getClass().getSimpleName());
+ }
+ tw.write(out, label, first);
+ }
+ Object second = object.getSecond();
+ if (second != null) {
+ TextWriterWriterInterface<Object> tw = (TextWriterWriterInterface<Object>) out.getWriterFor(second);
+ if (tw == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + second.getClass().getSimpleName());
+ }
+ tw.write(out, label, second);
+ }
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTextWriteable.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTextWriteable.java
new file mode 100644
index 00000000..29a5abb2
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTextWriteable.java
@@ -0,0 +1,44 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriteable;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+
+/**
+ * Write an object, using the objects own {@link TextWriteable} interface.
+ *
+ * @author Erich Schubert
+ *
+ * @apiviz.uses TextWriteable
+ */
+public class TextWriterTextWriteable extends TextWriterWriterInterface<TextWriteable> {
+ /**
+ * Use the objects own text serialization.
+ */
+ @Override
+ public void write(TextWriterStream out, String label, TextWriteable obj) {
+ obj.writeToText(out, label);
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTriple.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTriple.java
new file mode 100644
index 00000000..1fb1025e
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterTriple.java
@@ -0,0 +1,72 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import java.io.IOException;
+
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+import de.lmu.ifi.dbs.elki.utilities.exceptions.UnableToComplyException;
+import de.lmu.ifi.dbs.elki.utilities.pairs.Triple;
+
+/**
+ * Write a triple
+ *
+ * @author Erich Schubert
+ *
+ */
+public class TextWriterTriple extends TextWriterWriterInterface<Triple<?,?,?>> {
+ /**
+ * Serialize a triple, component-wise
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void write(TextWriterStream out, String label, Triple<?,?,?> object) throws UnableToComplyException, IOException {
+ if (object != null) {
+ Object first = object.getFirst();
+ if (first != null) {
+ TextWriterWriterInterface<Object> tw = (TextWriterWriterInterface<Object>) out.getWriterFor(first);
+ if (tw == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + first.getClass().getSimpleName());
+ }
+ tw.write(out, label, first);
+ }
+ Object second = object.getSecond();
+ if (second != null) {
+ TextWriterWriterInterface<Object> tw = (TextWriterWriterInterface<Object>) out.getWriterFor(second);
+ if (tw == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + second.getClass().getSimpleName());
+ }
+ tw.write(out, label, second);
+ }
+ Object third = object.getThird();
+ if (third != null) {
+ TextWriterWriterInterface<Object> tw = (TextWriterWriterInterface<Object>) out.getWriterFor(third);
+ if (tw == null) {
+ throw new UnableToComplyException("No handler for database object itself: " + third.getClass().getSimpleName());
+ }
+ tw.write(out, label, third);
+ }
+ }
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterVector.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterVector.java
new file mode 100644
index 00000000..ef708f8e
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/TextWriterVector.java
@@ -0,0 +1,49 @@
+package de.lmu.ifi.dbs.elki.result.textwriter.writers;
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import de.lmu.ifi.dbs.elki.math.linearalgebra.Vector;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterStream;
+import de.lmu.ifi.dbs.elki.result.textwriter.TextWriterWriterInterface;
+
+/**
+ * Write an object into the inline section, using the objects toString method.
+ *
+ * @author Erich Schubert
+ */
+public class TextWriterVector extends TextWriterWriterInterface<Vector> {
+ /**
+ * Serialize an object into the inline section.
+ */
+ @Override
+ public void write(TextWriterStream out, String label, Vector v) {
+ String res = "";
+ if(label != null) {
+ res = res + label + "=";
+ }
+ if(v != null) {
+ res = res + v.toStringNoWhitespace();
+ }
+ out.inlinePrintNoQuotes(res);
+ }
+}
diff --git a/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/package-info.java b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/package-info.java
new file mode 100644
index 00000000..66c5f706
--- /dev/null
+++ b/src/de/lmu/ifi/dbs/elki/result/textwriter/writers/package-info.java
@@ -0,0 +1,27 @@
+/**
+ * <p>Serialization handlers for individual data types.</p>
+ *
+ */
+/*
+This file is part of ELKI:
+Environment for Developing KDD-Applications Supported by Index-Structures
+
+Copyright (C) 2011
+Ludwig-Maximilians-Universität München
+Lehr- und Forschungseinheit für Datenbanksysteme
+ELKI Development Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+package de.lmu.ifi.dbs.elki.result.textwriter.writers; \ No newline at end of file