/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003 - 2013
* University of Konstanz, Germany and
* KNIME GmbH, Konstanz, Germany
* Website: http://www.knime.org; Email: contact@knime.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, Version 3, as
* published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
* Hence, KNIME and ECLIPSE are both independent programs and are not
* derived from each other. Should, however, the interpretation of the
* GNU GPL Version 3 ("License") under any applicable laws result in
* KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
* you the additional permission to use and propagate KNIME together with
* ECLIPSE with only the license terms in place for ECLIPSE applying to
* ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
* license terms of ECLIPSE themselves allow for the respective use and
* propagation of ECLIPSE together with KNIME.
*
* Additional permission relating to nodes for KNIME that extend the Node
* Extension (and in particular that are based on subclasses of NodeModel,
* NodeDialog, and NodeView) and that only interoperate with KNIME through
* standard APIs ("Nodes"):
* Nodes are deemed to be separate and independent programs and to not be
* covered works. Notwithstanding anything to the contrary in the
* License, the License does not apply to Nodes, you are not required to
* license Nodes under the License, and you are granted a license to
* prepare and propagate Nodes, in each case even if such Nodes are
* propagated with or for interoperation with KNIME. The owner of a Node
* may freely choose the license terms applicable to such Node, including
* when such Node is propagated with or for interoperation with KNIME.
* ---------------------------------------------------------------------
*
* Created on 17.11.2013 by Daniel Seebacher
*/
package org.knime.knip.base.nodes.proc.clahe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* This class provides the function needed by the CLAHE algorithm to make a linear interpolation in n-dimensions
* (linear, bilinear, trilinear, ...)
*
* @author Daniel Seebacher
*/
public class ClaheInterpolation {
/**
* @param currentPoint the current point (somewhere in between the neighbors)
* @param neighbors a list containing all neighbors
* @param oldValue the old value at the position of the current point
* @param histValues the values of the histograms at the positions of the neighbors
* @return the new value for this position
*/
public final static double interpolate(final ClahePoint currentPoint, final List<ClahePoint> neighbors,
final double oldValue, final double[] histValues) {
// create a list containg all points and their value and sort them using the dimension comparator
List<InterpolationPoint> ips = new ArrayList<ClaheInterpolation.InterpolationPoint>();
for (int i = 0; i < neighbors.size(); i++) {
ips.add(new InterpolationPoint(neighbors.get(i).getCoordinates(), histValues[i]));
}
Collections.sort(ips, getDimensionComparator());
return interpolate(currentPoint, ips, 0);
}
/**
* This method makes a linear interpolation in every dimension.
*
* @param currentPoint the point for which a new value should be calculated
* @param ips the points used for the interpolation
* @param dim the dimension in which the interpolation is made
* @return
*/
private static double interpolate(final ClahePoint currentPoint, final List<InterpolationPoint> ips, final int dim) {
// with only one point no interpolation has to be made
if (ips.size() == 1) {
return ips.get(0).getValue();
}
// if all values in a given dimension are the same an interpolation is also unnecessary.
if (checkDimension(ips, dim) && dim + 1 < currentPoint.numDim()) {
return interpolate(currentPoint, ips, dim + 1);
}
// because the points were sorted beforehand the values needed for the interpolation are always at i and i+1
final List<InterpolationPoint> newIPS = new ArrayList<ClaheInterpolation.InterpolationPoint>();
for (int i = 0; i < ips.size(); i += 2) {
// calculate the distances in the dimension i
final double distanceOne = Math.abs(ips.get(i).dim(dim) - currentPoint.dim(dim));
final double distanceTwo = Math.abs(ips.get(i + 1).dim(dim) - currentPoint.dim(dim));
final double completeDistance = distanceOne + distanceTwo;
// calculate the weights
final double weightOne = 1 - distanceOne / completeDistance;
final double weightTwo = 1 - distanceTwo / completeDistance;
// calculate the new value
final double val = (ips.get(i).getValue() * weightOne) + (ips.get(i + 1).getValue() * weightTwo);
// check if it is a number (could happen if there are no values in a dimension)
if (Double.isNaN(val)) {
newIPS.add(new InterpolationPoint(ips.get(i).getCoordinates(), 0));
} else {
newIPS.add(new InterpolationPoint(ips.get(i).getCoordinates(), val));
}
}
return interpolate(currentPoint, newIPS, dim + 1);
}
/**
* Used to sort the array of interpolation points by their values in the different dimensions (think about a
* reversed lexicographic order). For example: {0, 1, 2} < {0, 10, 2} < {0, 1, 3}
*
* @return Comparator
*/
private static Comparator<InterpolationPoint> getDimensionComparator() {
return new Comparator<InterpolationPoint>() {
@Override
public int compare(final InterpolationPoint o1, final InterpolationPoint o2) {
int numDim = o1.numDim();
for (int i = numDim - 1; i >= 0; i--) {
if (o1.dim(i) < o2.dim(i)) {
return -1;
} else if (o1.dim(i) > o2.dim(i)) {
return 1;
}
}
return 0;
}
};
}
/**
* Checks if all points are equal in a given dimension.
*
* @param ips some points
* @param dim dimension which should be checked
* @return true if the value in a given dimension is the same for all points, otherwise false.
*/
private static boolean checkDimension(final List<InterpolationPoint> ips, final int dim) {
for (InterpolationPoint interpolationPoint : ips) {
if (interpolationPoint.dim(dim) != ips.get(0).dim(dim)) {
return false;
}
}
return true;
}
/**
* Private class used by the interpolation, stores coordinates and a value.
*
* @author Daniel Seebacher
*/
private static class InterpolationPoint {
private long[] m_coordinates;
private double m_value;
/**
* @param coordinates
* @param value
*/
public InterpolationPoint(final long[] coordinates, final double value) {
this.m_coordinates = coordinates;
this.m_value = value;
}
/**
* @return the coordinates in all dimensions
*/
public long[] getCoordinates() {
return this.m_coordinates;
}
/**
* @return the dimensionality of this point
*/
public int numDim() {
return m_coordinates.length;
}
/**
* @param i the dimension
* @return the coordinate value in the given dimension
*/
public long dim(final int i) {
return this.m_coordinates[i];
}
public double getValue() {
return this.m_value;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return Arrays.toString(m_coordinates) + "\t" + m_value;
}
}
}