// This file is part of PleoCommand:
// Interactively control Pleo with psychobiological parameters
//
// Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Boston, USA.
package pleocmd.pipe.cvt;
import java.util.List;
import pleocmd.RunnableWithArgument;
import pleocmd.cfg.ConfigBoolean;
import pleocmd.cfg.ConfigDouble;
import pleocmd.exc.ConverterException;
import pleocmd.itfc.gui.dgr.DiagramDataSet;
import pleocmd.pipe.data.Data;
import pleocmd.pipe.data.SingleBoolData;
import pleocmd.pipe.data.SingleFloatData;
public final class ThresholdConverter extends Converter { // NO_UCD
private final ConfigDouble cfgThreshold;
private final ConfigDouble cfgMarginalArea;
private final ConfigDouble cfgValueBelow;
private final ConfigDouble cfgValueAbove;
private final ConfigBoolean cfgReturnBool;
private enum Hit {
Undef, Lower, Upper
}
private enum Val {
Undef, Below, Above
}
private double upper;
private double lower;
private Hit lastHit;
private Val value;
private double lastVal;
public ThresholdConverter() {
addConfig(cfgThreshold = new ConfigDouble("Threshold", 10, 0,
Double.MAX_VALUE));
addConfig(cfgMarginalArea = new ConfigDouble("Marginal Area", 3, 0,
Double.MAX_VALUE));
addConfig(cfgValueBelow = new ConfigDouble("Value For Below", 0, 0,
Double.MAX_VALUE));
addConfig(cfgValueAbove = new ConfigDouble(
"Value For Above (0 == current)", 0, 0, Double.MAX_VALUE));
addConfig(cfgReturnBool = new ConfigBoolean("Return Boolean As Value",
false));
cfgThreshold.setChangingContent(new RunnableWithArgument() {
@Override
public Object run(final Object... args) {
final double thres = (Double) args[0];
double margin = getCfgMarginalArea().getContentGUI();
final double below = getCfgValueBelow().getContentGUI();
final double above = getCfgValueAbove().getContentGUI();
if (2 * margin > thres) {
getCfgMarginalArea().setContentGUI(thres / 2);
margin = thres / 2;
}
if (below > thres - margin / 2)
getCfgValueBelow().setContentGUI(
Math.max(0, thres - margin / 2));
if (above != 0 && above < thres + margin / 2)
getCfgValueAbove().setContentGUI(thres + margin / 2);
return null;
}
});
cfgMarginalArea.setChangingContent(new RunnableWithArgument() {
@Override
public Object run(final Object... args) {
double thres = getCfgThreshold().getContentGUI();
final double margin = (Double) args[0];
final double below = getCfgValueBelow().getContentGUI();
final double above = getCfgValueAbove().getContentGUI();
if (2 * margin > thres) {
getCfgThreshold().setContentGUI(margin * 2);
thres = margin * 2;
}
if (below > thres - margin / 2)
getCfgValueBelow().setContentGUI(
Math.max(0, thres - margin / 2));
if (above != 0 && above < thres + margin / 2)
getCfgValueAbove().setContentGUI(thres + margin / 2);
return null;
}
});
cfgValueBelow.setChangingContent(new RunnableWithArgument() {
@Override
public Object run(final Object... args) {
double thres = getCfgThreshold().getContentGUI();
double margin = getCfgMarginalArea().getContentGUI();
final double below = (Double) args[0];
final double above = getCfgValueAbove().getContentGUI();
if (below > thres - margin / 2) {
thres = below + margin / 2;
getCfgThreshold().setContentGUI(thres);
if (2 * margin > thres) {
getCfgMarginalArea().setContentGUI(thres / 2);
margin = thres / 2;
}
}
if (above != 0
&& (above <= below || above < thres + margin / 2))
getCfgValueAbove().setContentGUI(thres + margin / 2);
return null;
}
});
cfgValueAbove.setChangingContent(new RunnableWithArgument() {
@Override
public Object run(final Object... args) {
double thres = getCfgThreshold().getContentGUI();
double margin = getCfgMarginalArea().getContentGUI();
final double below = getCfgValueBelow().getContentGUI();
final double above = (Double) args[0];
if (above != 0 && above < thres + margin / 2) {
thres = Math.max(0, above - margin / 2);
getCfgThreshold().setContentGUI(thres);
if (2 * margin > thres) {
getCfgMarginalArea().setContentGUI(thres / 2);
margin = thres / 2;
}
}
if (above != 0 && above <= below || below > thres - margin / 2)
getCfgValueBelow().setContentGUI(
Math.max(0, thres - margin / 2));
return null;
}
});
cfgReturnBool.setChangingContent(new RunnableWithArgument() {
@Override
public Object run(final Object... args) {
final boolean retBool = (Boolean) args[0];
getCfgValueBelow().setGUIEnabled(!retBool);
getCfgValueAbove().setGUIEnabled(!retBool);
return null;
}
});
constructed();
}
@Override
protected void init0() {
upper = cfgThreshold.getContent() + cfgMarginalArea.getContent() / 2;
lower = cfgThreshold.getContent() - cfgMarginalArea.getContent() / 2;
lastHit = Hit.Undef;
value = Val.Undef;
lastVal = .0;
}
@Override
protected void initVisualize0() {
DiagramDataSet ds = getVisualizeDataSet(0);
if (ds != null) ds.setLabel("Resulting Data");
ds = getVisualizeDataSet(1);
if (ds != null) ds.setLabel("Lower Margin");
ds = getVisualizeDataSet(2);
if (ds != null) ds.setLabel("Upper Margin");
ds = getVisualizeDataSet(3);
if (ds != null) ds.setLabel("Threshold");
}
@Override
public String getInputDescription() {
return SingleFloatData.IDENT;
}
@Override
public String getOutputDescription() {
return cfgReturnBool.getContent() ? SingleBoolData.IDENT
: SingleFloatData.IDENT;
}
@Override
protected String getShortConfigDescr0() {
return String
.format("|>%s±%s| => %s : %s",
cfgThreshold.asString(),
String.valueOf(cfgMarginalArea.getContent() / 2),
cfgReturnBool.getContent() ? "false" : cfgValueBelow
.asString(),
cfgReturnBool.getContent() ? "true" : cfgValueAbove
.getContent() > 0 ? cfgValueAbove.asString()
: "<cur>");
}
@Override
protected List<Data> convert0(final Data data) throws ConverterException {
if (!SingleFloatData.isSingleFloatData(data)) return null;
double val = SingleFloatData.getValue(data);
if (val < 0) val = -val; // consider values as absolute
// check if we just broke through the marginal area's bounds
if (lastVal < lower && val >= lower) lastHit = Hit.Lower;
if (lastVal > upper && val <= upper) lastHit = Hit.Upper;
// check if we just hit the threshold
final double thrs = cfgThreshold.getContent();
if (lastVal < thrs && val >= thrs || lastVal > thrs && val <= thrs)
switch (lastHit) {
case Lower:
value = Val.Above;
break;
case Upper:
value = Val.Below;
break;
default:
break;
}
// check if we just exceeded the bounds
if (lastVal > lower && val <= lower) value = Val.Below;
if (lastVal < upper && val >= upper) value = Val.Above;
lastVal = val;
// set correct output depending on our current state
if (cfgReturnBool.getContent()) {
final boolean out = value == Val.Above;
if (isVisualize()) {
plot(0, out ? 1 : 0);
plot(1, lower);
plot(2, upper);
plot(3, thrs);
}
return asList(SingleBoolData.create(out, data));
}
final double out;
switch (value) {
case Below:
out = cfgValueBelow.getContent();
break;
case Above:
out = cfgValueAbove.getContent() > 0 ? cfgValueAbove.getContent()
: val;
break;
default:
out = .0;
break;
}
if (isVisualize()) {
plot(0, out);
plot(1, lower);
plot(2, upper);
plot(3, thrs);
}
return asList(SingleFloatData.create(out, data));
}
public static String help(final HelpKind kind) {
switch (kind) {
case Name:
return "Threshold Converter";
case Description:
return "Sends one of two different values depending on whether the "
+ "currently received value is below or above a specified "
+ "threshold, while a passing of the threshold-value is "
+ "ignored unless a specifiable margin has been exceeded.";
case Config1:
return "The threshold-value (is treated as an absolute value)";
case Config2:
return "The size of the marginal area around the threshold-value";
case Config3:
return "The value which will be send if the current one is "
+ "below the threshold";
case Config4:
return "The value which will be send if the current one is "
+ "above the threshold - if this is 0, the current "
+ "one itself will be sent";
case Config5:
return "If true, a boolean value (true for above, false for below) "
+ "will be returned instead of the float from the fields above";
default:
return null;
}
}
@Override
public String isConfigurationSane() {
final double thres = cfgThreshold.getContent();
final double margin = cfgMarginalArea.getContent();
final double below = cfgValueBelow.getContent();
final double above = cfgValueAbove.getContent();
if (2 * margin > thres)
return "Marginal Area must be at most half of threshold";
if (below > thres - margin / 2)
return "Value for Below must not be larger than the "
+ "lower marginal area bound";
if (above != 0) {
if (above < thres + margin / 2)
return "Value for Above must not be smaller than the "
+ "higher marginal area bound";
if (above <= below)
return "Value for Above must be larger than Value for Below";
}
return null;
}
@Override
protected int getVisualizeDataSetCount() {
return 4;
}
protected ConfigDouble getCfgThreshold() {
return cfgThreshold;
}
protected ConfigDouble getCfgMarginalArea() {
return cfgMarginalArea;
}
protected ConfigDouble getCfgValueBelow() {
return cfgValueBelow;
}
protected ConfigDouble getCfgValueAbove() {
return cfgValueAbove;
}
public ConfigBoolean getCfgReturnBool() {
return cfgReturnBool;
}
}