Index: /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataprocessing/MORPH.java
===================================================================
--- /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataprocessing/MORPH.java	(revision 119)
+++ /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataprocessing/MORPH.java	(revision 120)
@@ -97,16 +97,27 @@
      * @param data data to which the processor is applied
      */
-    private void applyMORPH(Instances data) {
+    public void applyMORPH(Instances data) {
         for (int i=0; i<data.numInstances(); i++ ) {
-            Instance instance = data.instance(i);
-            Instance nearestUnlikeNeighbor = getNearestUnlikeNeighbor(instance, data);
-            if( nearestUnlikeNeighbor==null ) {
-                throw new RuntimeException("could not find nearest unlike neighbor within the data: " + data.relationName());
-            }
-            for( int j=0; j<data.numAttributes() ; j++ ) {
-                if( data.attribute(j)!=data.classAttribute() && data.attribute(j).isNumeric()) {
-                    double randVal = rand.nextDouble()*(beta-alpha)+alpha;
-                    instance.setValue(j, instance.value(j) + randVal*(instance.value(j)-nearestUnlikeNeighbor.value(j)) );
-                }
+            morphInstance(data.get(i), data);
+        }
+    }
+    
+    /**
+     * <p>
+     * Applies MORPH to a single instance
+     * </p>
+     *
+     * @param instance instance that is morphed
+     * @param data data based on which the instance is morphed
+     */
+    public void morphInstance(Instance instance, Instances data) {
+        Instance nearestUnlikeNeighbor = getNearestUnlikeNeighbor(instance, data);
+        if( nearestUnlikeNeighbor==null ) {
+            throw new RuntimeException("could not find nearest unlike neighbor within the data: " + data.relationName());
+        }
+        for( int j=0; j<data.numAttributes() ; j++ ) {
+            if( data.attribute(j)!=data.classAttribute() && data.attribute(j).isNumeric()) {
+                double randVal = rand.nextDouble()*(beta-alpha)+alpha;
+                instance.setValue(j, instance.value(j) + randVal*(instance.value(j)-nearestUnlikeNeighbor.value(j)) );
             }
         }
@@ -122,5 +133,5 @@
      * @return nearest unlike instance
      */
-    protected Instance getNearestUnlikeNeighbor(Instance instance, Instances data) {
+    public Instance getNearestUnlikeNeighbor(Instance instance, Instances data) {
         Instance nearestUnlikeNeighbor = null;
         
Index: /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/CLIFF.java
===================================================================
--- /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/CLIFF.java	(revision 119)
+++ /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/CLIFF.java	(revision 120)
@@ -65,5 +65,5 @@
     }
 
-    private Instances applyCLIFF(Instances data) {
+    protected Instances applyCLIFF(Instances data) {
         final double[][] powerAttributes = new double[data.size()][data.numAttributes()];
         final double[] powerEntity = new double[data.size()];
Index: /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/LACE2.java
===================================================================
--- /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/LACE2.java	(revision 120)
+++ /trunk/CrossPare/src/de/ugoe/cs/cpdp/dataselection/LACE2.java	(revision 120)
@@ -0,0 +1,106 @@
+// Copyright 2016 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+
+package de.ugoe.cs.cpdp.dataselection;
+
+import java.util.Collections;
+import java.util.LinkedList;
+
+import org.apache.commons.collections4.list.SetUniqueList;
+import org.apache.commons.math3.stat.descriptive.rank.Median;
+import org.apache.commons.math3.util.MathArrays;
+
+import de.ugoe.cs.cpdp.dataprocessing.MORPH;
+import de.ugoe.cs.cpdp.util.WekaUtils;
+import weka.core.Instance;
+import weka.core.Instances;
+import weka.filters.Filter;
+import weka.filters.supervised.instance.Resample;
+
+/**
+ * <p>
+ * Implements LACE2 data privacy filter after Peters et al.
+ * </p>
+ * 
+ * @author Steffen Herbold
+ */
+public class LACE2 implements ISetWiseDataselectionStrategy {
+
+    private double percentage = 0.10;
+    
+    @Override
+    public void setParameter(String parameters) {
+        if( parameters!=null && !parameters.isEmpty()) {
+            percentage = Double.parseDouble(parameters);
+        }
+    }
+
+    @Override
+    public void apply(Instances testdata, SetUniqueList<Instances> traindataSet) {
+        Instances selectedData = new Instances(testdata);
+        selectedData.clear();
+        
+        LinkedList<Instances> traindataCopy = new LinkedList<>(traindataSet);
+        Collections.shuffle(traindataCopy);
+        
+        CLIFF cliff = new CLIFF();
+        cliff.setParameter(Double.toString(percentage));
+        MORPH morph = new MORPH();
+        Median median = new Median();
+        double minDist = Double.MIN_VALUE;
+        
+        for( Instances traindata : traindataCopy ) {
+            Instances cliffedData = cliff.applyCLIFF(traindata);
+            if( minDist==Double.MIN_VALUE ) {
+                // determine distance for leader-follower algorithm
+                Instances sample;
+                if( traindata.size()>100 ) {
+                    Resample resample = new Resample();
+                    resample.setSampleSizePercent(100.0/traindata.size()*100.0);
+                    resample.setBiasToUniformClass(0.0);
+                    resample.setNoReplacement(true);
+                    try {
+                        resample.setInputFormat(traindata);
+                        sample = Filter.useFilter(traindata, resample);
+                    }
+                    catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    sample = new Instances(traindata);
+                }
+                double[] distances = new double[sample.size()];
+                for( int i=0; i<sample.size(); i++ ) {
+                    Instance unlikeNeighbor = morph.getNearestUnlikeNeighbor(sample.get(i), sample);
+                    distances[i] = MathArrays.distance(WekaUtils.instanceValues(sample.get(i)), WekaUtils.instanceValues(unlikeNeighbor));
+                }
+                minDist = median.evaluate(distances);
+            }
+            for( int i=0; i<cliffedData.size(); i++ ) {
+                Instance unlikeNeighbor = morph.getNearestUnlikeNeighbor(cliffedData.get(i), selectedData);
+                if( unlikeNeighbor==null ) {
+                    selectedData.add(cliffedData.get(i));
+                } else {
+                    double distance = MathArrays.distance(WekaUtils.instanceValues(cliffedData.get(i)), WekaUtils.instanceValues(unlikeNeighbor));
+                    if( distance>minDist ) {
+                        morph.morphInstance(cliffedData.get(i), cliffedData);
+                        selectedData.add(cliffedData.get(i));
+                    }
+                }
+            }
+        }
+    }
+    
+}
