I have read every related post on stackoverflow but i just still cant seem to replicate the exact same RSI calculation that tradingview uses. I've spend over dozens of hours trying to get this right. At this point it is really sucking the joy out of my hobby project. Any help would be greatly appreciated.
If any other info or something is required please let me know and i will add it.
What the tradingview documentation says about how the calculation is done:
* For a practical example, the built-in Pine Script function rsi(), could be replicated in long form as follows.
* change = change(close)
* gain = change >= 0 ? change : 0.0
* loss = change < 0 ? (-1) * change : 0.0
* avgGain = rma(gain, 14)
* avgLoss = rma(loss, 14)
* rs = avgGain / avgLoss
* rsi = 100 - (100 / (1 + rs))
*
*
* where
* RMA = alpha * source + (1-alpha) * RMA[1]
* alpha = 1/length
I just cant seem to get the RMA part right, i just dont have a clue anymore on how to interperted it. Below is my class with at the bottom some sample data. If someone has managed to get a exact or near RSI calculation to to the tradingview could you please walk me through the steps.
Rsi_indicator.class
public class Rsi_indicator {
static Candlestick[] candles = getCandleSticks();
static double[] gain_array = new double[candles.length];
static double[] loss_array = new double[candles.length];
public static final int RSI_PERIOD = 14;
public static void main(String[] args) {
/**
* 1. Calculate change over closes
*
* */
for (int i = 0; i < candles.length; i++) {
double gain = 0;
double loss = 0;
if (i >= 1) {
double change = candles[i - 1].getClose() - candles[i].getClose();
gain_array[i] = change >= 0 ? change : 0.0d;
loss_array[i] = change < 0 ? (-1) * change : 0.0d;
}
}
/**
* 2. Calculate AVG gain
*
* According to trading view they use this --> avgGain = rma(gain, 14)
* Where
* RMA = alpha * source + (1-alpha) * RMA[1]
* alpha = 1/length
*/
double alpha = 1.0 / RSI_PERIOD;
RMA rma_gain = new RMA(alpha);
RMA rma_loss = new RMA(alpha);
/*
TODO HOW EXACTLY TO IMPLEMENT THE ABOVE RMA?
what is exactly meant by source ? and RMA[1] is this the previous value that was calculated?
*/
/**
* 4. Calculate relative strength
* */
double RS = avg_gain / avg_loss;
/**
* 5. Calculate RSI
* */
double rsi = 100 - (100 / (1 + RS));
/**
* The last 4 calculated rsi values should be:
* 7 nov 2021 --> 59.68
* 8 nov 2021 --> 67.65
* 9 nov 2021 --> 65.75
* 10 nov 2021 --> 59.33
*/
System.out.println("RSI: " + rsi);
}
static class RMA {
private double alpha;
private Double oldValue;
public RMA(double alpha) {
this.alpha = alpha;
}
public double average(double value) {
if (oldValue == null) {
oldValue = value;
return value;
}
double newValue = oldValue + alpha * (value - oldValue);
return newValue;
}}
/**
* This is just for you to have a working sample instantly
* The sample data i pulled from the binance api is the
* 1D BINANCE BTCUSDT pair from 2021-10-20T02:00 TO 2021-11-10T01:00.
*/
static Candlestick[] getCandleSticks() {
String[] data =
{"1634688000000,64280.59,67000.0,63481.4,66001.41,1634774399999",
"1634774400000,66001.4,66639.74,62000.0,62193.15,1634860799999",
"1634860800000,62193.15,63732.39,60000.0,60688.22,1634947199999",
"1634947200000,60688.23,61747.64,59562.15,61286.75,1635033599999",
"1635033600000,61286.75,61500.0,59510.63,60852.22,1635119999999",
"1635120000000,60852.22,63710.63,60650.0,63078.78,1635206399999",
"1635206400000,63078.78,63293.48,59817.55,60328.81,1635292799999",
"1635292800000,60328.81,61496.0,58000.0,58413.44,1635379199999",
"1635379200000,58413.44,62499.0,57820.0,60575.89,1635465599999",
"1635465600000,60575.9,62980.0,60174.81,62253.71,1635551999999",
"1635552000000,62253.7,62359.25,60673.0,61859.19,1635638399999",
"1635638400000,61859.19,62405.3,59945.36,61299.8,1635724799999",
"1635724800000,61299.81,62437.74,59405.0,60911.11,1635811199999",
"1635811200000,60911.12,64270.0,60624.68,63219.99,1635897599999",
"1635897600000,63220.57,63500.0,60382.76,62896.48,1635983999999",
"1635984000000,62896.49,63086.31,60677.01,61395.01,1636070399999",
"1636070400000,61395.01,62595.72,60721.0,60937.12,1636156799999",
"1636156800000,60940.18,61560.49,60050.0,61470.61,1636243199999",
"1636243200000,61470.62,63286.35,61322.78,63273.59,1636329599999",
"1636329600000,63273.58,67789.0,63273.58,67525.83,1636415999999",
"1636416000000,67525.82,68524.25,66222.4,66947.66,1636502399999",
"1636502400000,66947.67,69000.0,62822.9,64882.43,1636588799999",
};
List<Candlestick>list = new ArrayList<>();
for (String s: data) {
Candlestick c = new Candlestick();
String[] sArr = s.split(",");
c.setOpenTime(Long.valueOf(sArr[0]));
c.setOpen(sArr[1]);
c.setHigh(sArr[2]);
c.setLow(sArr[3]);
c.setClose(sArr[4]);
c.setCloseTime(Long.valueOf(sArr[5]));
list.add(c);
}
return list.stream().toArray(Candlestick[]::new);
}
}
Candlestick.class
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class Candlestick {
private Long openTime;
private double open;
private double high;
private double low;
private double close;
private double volume;
private Long closeTime;
public Long getOpenTime() {
return openTime;
}
public void setOpenTime(Long openTime) {
this.openTime = openTime;
}
public double getOpen() {
return open;
}
public void setOpen(String open) {
this.open = toDouble(open);
}
public double getHigh() {
return high;
}
public void setHigh(String high) {
this.high = toDouble(high);
}
public double getLow() {
return low;
}
public void setLow(String low) {
this.low = toDouble(low);
}
public double getClose() {
return close;
}
public void setClose(String close) {
this.close = toDouble(close);
}
public Long getCloseTime() {
return closeTime;
}
public void setCloseTime(Long closeTime) {
this.closeTime = closeTime;
}
public LocalDateTime getFormattedOpenTime() {
return Instant.ofEpochMilli(openTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public LocalDateTime getFormattedCloseTime() {
return Instant.ofEpochMilli(closeTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public double toDouble(String a) {
return Double.parseDouble(a);
}
}
Related
I'm trying to build an app for fall detection using the accelerometer of a smartphone as a school project (so I still have a lot of things to improve).
I was doing some research and I found this article with some calculations, so I'm trying to turn those into some code.
I asked a friend for some help and he explained me how to do those calculations. But considering it's been a few years since I finished high school and I'm not very good at math, I'm sure I got some stuff wrong.
So I was expecting someone could give me a hand.
Here's what I need and what I have already, in case someone finds any mistake.
return Math.abs(x) + Math.abs(y) + Math.abs(z);
double anX = this.accelerometerValues.get(size -2).get(AccelerometerAxis.X) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.X);
double anY = this.accelerometerValues.get(size -2).get(AccelerometerAxis.Y) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.Y);
double anZ = this.accelerometerValues.get(size -2).get(AccelerometerAxis.Z) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.Z);
double an = anX + anY + anZ;
double anX0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.X), 2);
double anY0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.Y), 2);
double anZ0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.Z), 2);
double an0 = Math.sqrt(anX0 + anY0 + anZ0);
double anX1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.X), 2);
double anY1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.Y), 2);
double anZ1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.Z), 2);
double an1 = Math.sqrt(anX1 + anY1 + anZ1);
double a = an / (an0 * an1);
return (Math.pow(Math.cos(a), -1)) * (180 / Math.PI);
double aX = this.accelerometerValues.get(0).get(AccelerometerAxis.X) * this.accelerometerValues.get(3).get(AccelerometerAxis.X);
double aY = this.accelerometerValues.get(0).get(AccelerometerAxis.Y) * this.accelerometerValues.get(3).get(AccelerometerAxis.Y);
double aZ = this.accelerometerValues.get(0).get(AccelerometerAxis.Z) * this.accelerometerValues.get(3).get(AccelerometerAxis.Z);
double a0 = aX + aY + aZ;
double a1 = (Math.sqrt(Math.pow(aX, 2)) + Math.sqrt(Math.pow(aY, 2)) + Math.sqrt(Math.pow(aZ, 2)));
return (Math.pow(Math.cos(a0 / a1), -1)) * (180 / Math.PI);
I'm getting the same return from the second and the third calculation and neither seems to be the expected result, but I can't find what I'm doing wrong.
Here's the code for the class, for more details
import android.app.IntentService;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import br.com.aimcol.fallalertapp.activity.FallNotificationActivity;
import br.com.aimcol.fallalertapp.model.Elderly;
import br.com.aimcol.fallalertapp.model.Person;
import br.com.aimcol.fallalertapp.model.User;
import br.com.aimcol.fallalertapp.util.AccelerometerAxis;
import br.com.aimcol.fallalertapp.util.RuntimeTypeAdapterFactory;
public class FallDetectionService extends IntentService implements SensorEventListener {
private static final int ACCELEROMETER_SAMPLING_PERIOD = 1000000;
private static final double CSV_THRESHOLD = 23;
private static final double CAV_THRESHOLD = 18;
private static final double CCA_THRESHOLD = 65.5;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private User user;
private Gson gson;
private Long lastSentInMillis;
private Long minTimeToNotifyAgain = 3000000L;
private List<Map<AccelerometerAxis, Double>> accelerometerValues = new ArrayList<>();
public FallDetectionService() {
super(".FallDetectionService");
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* #param name Used to name the worker thread, important only for debugging.
*/
public FallDetectionService(String name) {
super(name);
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public final void onAccuracyChanged(Sensor sensor,
int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event) {
// Axis of the rotation sample, not normalized yet.
double x = event.values[0];
double y = event.values[1];
double z = event.values[2];
if (this.isFallDetected(x, y, z)) {
if (this.isOkayToNotifyAgain()) {
this.lastSentInMillis = System.currentTimeMillis();
Toast.makeText(this, "Fall", Toast.LENGTH_LONG).show();
FallNotificationActivity.startFallNotificationActivity(this, this.gson.toJson(this.user));
}
}
}
private boolean isFallDetected(double x,
double y,
double z) {
double acceleration = this.calculateAcceleration(x, y, z);// - SensorManager.GRAVITY_EARTH;
this.addAccelerometerValuesToList(x, y, z, acceleration);
String msg = new StringBuilder("x: ").append(x).append(" y: ").append(y).append(" z: ").append(z).append(" acc: ").append(acceleration).toString();
Log.d("FDS-Acc-Values", msg);
if (acceleration > CSV_THRESHOLD) {
// double angleVariation = this.calculateAngleVariation();
// if (angleVariation > CAV_THRESHOLD) {
// double changeInAngle = this.calculateChangeInAngle();
// if (changeInAngle > CCA_THRESHOLD) {
Log.d("FDS-Fall-Happened", msg);
return true;
// }
// }
}
return false;
}
private void addAccelerometerValuesToList(double x,
double y,
double z,
double acceleration) {
if(this.accelerometerValues.size() >= 4) {
this.accelerometerValues.remove(0);
}
Map<AccelerometerAxis, Double> map = new HashMap<>();
map.put(AccelerometerAxis.X, x);
map.put(AccelerometerAxis.Y, y);
map.put(AccelerometerAxis.Z, z);
map.put(AccelerometerAxis.ACCELERATION, acceleration);
this.accelerometerValues.add(map);
}
private double calculateAcceleration(double x,
double y,
double z) {
return Math.abs(x) + Math.abs(y) + Math.abs(z);
}
private double calculateAngleVariation() {
int size = this.accelerometerValues.size();
if (size < 2){
return -1;
}
double anX = this.accelerometerValues.get(size -2).get(AccelerometerAxis.X) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.X);
double anY = this.accelerometerValues.get(size -2).get(AccelerometerAxis.Y) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.Y);
double anZ = this.accelerometerValues.get(size -2).get(AccelerometerAxis.Z) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.Z);
double an = anX + anY + anZ;
// double an = this.accelerometerValues.get(size -2).get(AccelerometerAxis.ACCELERATION) * this.accelerometerValues.get(size -1).get(AccelerometerAxis.ACCELERATION);
double anX0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.X), 2);
double anY0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.Y), 2);
double anZ0 = Math.pow(this.accelerometerValues.get(size -2).get(AccelerometerAxis.Z), 2);
double an0 = Math.sqrt(anX0 + anY0 + anZ0);
double anX1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.X), 2);
double anY1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.Y), 2);
double anZ1 = Math.pow(this.accelerometerValues.get(size -1).get(AccelerometerAxis.Z), 2);
double an1 = Math.sqrt(anX1 + anY1 + anZ1);
double a = an / (an0 * an1);
return (Math.pow(Math.cos(a), -1)) * (180 / Math.PI); //cosseno inverso? Ou cosseno ^-1?
}
private double calculateChangeInAngle() {
int size = this.accelerometerValues.size();
if (size < 4){
return -1;
}
double aX = this.accelerometerValues.get(0).get(AccelerometerAxis.X) * this.accelerometerValues.get(3).get(AccelerometerAxis.X);
double aY = this.accelerometerValues.get(0).get(AccelerometerAxis.Y) * this.accelerometerValues.get(3).get(AccelerometerAxis.Y);
double aZ = this.accelerometerValues.get(0).get(AccelerometerAxis.Z) * this.accelerometerValues.get(3).get(AccelerometerAxis.Z);
double a0 = aX + aY + aZ;
double a1 = (Math.sqrt(Math.pow(aX, 2)) + Math.sqrt(Math.pow(aY, 2)) + Math.sqrt(Math.pow(aZ, 2)));
return (Math.pow(Math.cos(a0 / a1), -1)) * (180 / Math.PI);
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
if (this.user == null) {
String userJson = intent.getStringExtra(User.USER_JSON);
this.user = this.gson.fromJson(userJson, User.class);
}
}
#Override
public int onStartCommand(Intent intent,
int flags,
int startId) {
if (this.gson == null) {
RuntimeTypeAdapterFactory<Person> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Person.class, "type")
.registerSubtype(Elderly.class, Elderly.class.getSimpleName());
this.gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create();
}
if (this.user == null) {
String userJson = intent.getStringExtra(User.USER_JSON);
this.user = this.gson.fromJson(userJson, User.class);
}
this.mSensorManager = (SensorManager) super.getSystemService(Context.SENSOR_SERVICE);
this.mAccelerometer = this.mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (this.mAccelerometer == null) {
throw new RuntimeException("Acelerometro não encontrado");
}
this.mSensorManager.registerListener(this, this.mAccelerometer, ACCELEROMETER_SAMPLING_PERIOD);
return Service.START_STICKY;
}
private boolean isOkayToNotifyAgain() {
return this.lastSentInMillis == null || (this.lastSentInMillis + this.minTimeToNotifyAgain) < System.currentTimeMillis();
}
public static void startFallDetectionService(String userJson,
Context context) {
Intent fallDetectionServiceIntent = new Intent(context, FallDetectionService.class);
fallDetectionServiceIntent.putExtra(User.USER_JSON, userJson);
context.startService(fallDetectionServiceIntent);
}
#VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
protected boolean testFallDetection(List<Map<AccelerometerAxis, Double>> values) {
for (Map<AccelerometerAxis, Double> value : values) {
if (this.isFallDetected(
value.get(AccelerometerAxis.X),
value.get(AccelerometerAxis.Y),
value.get(AccelerometerAxis.Z))) {
return true;
}
}
return false;
}
}
Ps: Sorry if I got something wrong. English is not my first language and I'm a little rusty.
Ps2: Sorry about my variable names.
Ps3: The code is on github, if you want to take a look on the rest of the code or if instead of posting a reply here you want to make a pull request or something, feel free.
This question might not be appropriate here. SO isn't intended as a code review site.
Here are a few comments to consider:
The first formula you list is not what you've expressed in code. There is no square root or sum of squares in the formula, but you put them into code for some reason. The article says SV should be the sum of the absolute value of the components of the linear acceleration vector. That's not what your code says.
The meaning of n and n+1 in the second formula isn't clear to me. Are those consecutive time points? The dot product of two vectors is easy to calculate - but for which two vectors?
The third formula calls for an average of acceleration vectors over a four second window. I don't see that being done anywhere. The number of vectors involved in the average would depend on your sampling rate.
I would suggest that you re-read the article several more times.
I'd also advise that you encapsulate these into functions and write some good unit tests for them. See if you can reproduce the results from the paper.
Facing issue with reading a cell in excel which is set with XIRR function.
I written my code in Java. Below is the code to set the formula. Please help on how can I read the value from the cell and not the formula.
cell.setCellFormula("XIRR(E2:E10, B2:B10");
CellStyle style = workbook.createCellStyle();
style.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
cell.setCellStyle(style);
Below is the error while evaluating the cell using FormulaEvaluator
org.apache.poi.ss.formula.eval.NotImplementedFunctionException: XIRR
at org.apache.poi.ss.formula.atp.AnalysisToolPak$NotImplemented.evaluate(AnalysisToolPak.java:59)
at org.apache.poi.ss.formula.UserDefinedFunction.evaluate(UserDefinedFunction.java:61)
at org.apache.poi.ss.formula.OperationEvaluatorFactory.evaluate(OperationEvaluatorFactory.java:129)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateFormula(WorkbookEvaluator.java:550)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateAny(WorkbookEvaluator.java:317)
... 18 more
Without patching apache poi with XIRR function directly calculating a result like Excel's XIRR function is possible using a User Defined Function in apache poi.
The following code provides exactly this.
It defines a class CalculateXIRR which then will be used as myXIRR function in apache poi. The CalculateXIRR uses either JXIRR - v1.0.0 (C) 2005 Gautam Satpathy or class Xirr derived from java program to calculate XIRR without using excel or any other library to calculate XIRR.
Also it provides code for test cases. At first the same test case as from the example in Excel's XIRR documentation. And then random test cases using random values and dates. Those test cases are written into an Excel workbook. Written are the result of the evaluation of the user defined myXIRR function as well as Excel's original XIRR function. So we can comparing the results.
My tests have shown that both XIRR calculation methods are pretty exact like Excel using reasonable values and dates. Only using values and dates which leads Excel's XIRR resulting in high negative percentages (lower than -60%) or very high percentages (greater than 1000%) both methods are different from Excel.
JXIRR - v1.0.0 from Gautam Satpathy is better suited to Excel as the class Xirr. The reason is pretty clear since the class Xirr will always fail if x in Math.pow((x + 1d), (dt0-dt) / 365d) is lower than -1d. If so, then the base of the Math.pow function is negative and since the exponent (dt0-dt) / 365d) is fractional, there is only a imaginary solution. This happens if Excel's XIRR is resulting in high negative percentages and the approximation tries to come from below -100%. JXIRR uses a goal seek method which seems to be more like the one of Excel itself.
Code:
import java.io.* ;
import org.apache.poi.ss.formula.functions.* ;
import org.apache.poi.ss.formula.udf.* ;
import org.apache.poi.ss.usermodel.* ;
import org.apache.poi.xssf.usermodel.* ;
import org.apache.poi.ss.formula.* ;
import org.apache.poi.ss.formula.eval.* ;
import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.Random;
/*
https://github.com/ept/jxirr
(C) 2005 Gautam Satpathy
*/
import in.satpathy.financial.*;
public class XIRREvaluator {
private Workbook workbook;
private Sheet sheet;
private Row row;
private Cell cell;
private CellStyle percentStyle;
private CellStyle dateStyle;
private FormulaEvaluator evaluator;
private String[] labels;
private char c1;
private char c2;
private String[] formulas;
private Double[] values;
private SimpleDateFormat sdf;
private Date[] dates;
public XIRREvaluator() {
this.workbook = new XSSFWorkbook();
String[] functionNames = { "myXIRR" } ;
FreeRefFunction[] functionImpls = { new CalculateXIRR() } ;
UDFFinder udfs = new DefaultUDFFinder( functionNames, functionImpls ) ;
UDFFinder udfToolpack = new AggregatingUDFFinder( udfs ) ;
workbook.addToolPack(udfToolpack);
this.percentStyle = workbook.createCellStyle();
percentStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
this.dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(workbook.createDataFormat().getFormat("yyyy-MM-dd"));
this.evaluator = workbook.getCreationHelper().createFormulaEvaluator();
this.sheet = workbook.createSheet("Sheet1");
this.labels = new String[]{"XIRR", "myXIRR", "diff"};
this.sdf = new SimpleDateFormat("yyyy-MM-dd");
}
public void save() {
try {
workbook.write(new FileOutputStream("ExcelWorkbookXIRR.xlsx"));
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void testCaseFromExcelDocu(int startCol, int startRow) {
/*
This provides a test case as from the example in Excel's XIRR documentation:
https://support.office.com/en-us/article/XIRR-function-de1242ec-6477-445b-b11b-a303ad9adc9d
*/
if (startCol > 24) return;
try {
c1 = (char)(65+startCol);
c2 = (char)(65+startCol+1);
formulas = new String[]{"XIRR("+c1+(startRow+4)+":"+c1+(startRow+8)+","+c2+(startRow+4)+":"+c2+(startRow+8)+")",
"myXIRR("+c1+(startRow+4)+":"+c1+(startRow+8)+","+c2+(startRow+4)+":"+c2+(startRow+8)+")",
""+c2+(startRow+1)+"-"+c2+(startRow+2)};
values = new Double[]{-10000d, 2750d, 4250d, 3250d, 2750d};
dates = new Date[]{sdf.parse("2008-01-01"), sdf.parse("2008-03-01"), sdf.parse("2008-10-30"), sdf.parse("2009-02-15"), sdf.parse("2009-04-01")};
for (int r = startRow; r < startRow+3; r++) {
row = (sheet.getRow(r)==null)?sheet.createRow(r):sheet.getRow(r);
cell = row.createCell(startCol);
cell.setCellValue(labels[r-startRow]);
}
for (int r = startRow+3; r < startRow+8; r++) {
row = (sheet.getRow(r)==null)?sheet.createRow(r):sheet.getRow(r);
cell = row.createCell(startCol);
cell.setCellValue(values[r-startRow-3]);
cell = row.createCell(startCol+1);
cell.setCellValue(dates[r-startRow-3]);
cell.setCellStyle(dateStyle);
}
for (int r = startRow; r < startRow+2; r++) {
cell = sheet.getRow(r).createCell(startCol+1);
cell.setCellFormula(formulas[r-startRow]);
cell.setCellStyle(percentStyle);
if (r == startRow+1) {
cell = evaluator.evaluateInCell(cell);
System.out.println(new DataFormatter().formatCellValue(cell));
}
}
cell = sheet.getRow(startRow+2).createCell(startCol+1);
cell.setCellFormula(formulas[2]);
sheet.autoSizeColumn(startCol);
sheet.autoSizeColumn(startCol+1);
} catch (Exception e) {
e.printStackTrace();
}
}
private void randomTestCases(int startCol, int startRow, int count) {
/*
This provides randon test cases
*/
try {
long day = 24L*60L*60L*1000L;
long startDate = sdf.parse("2010-01-01").getTime();
for (int test = startCol; test < startCol+3*count; test+=3) {
if (test > 24) return;
c1 = (char)(65+test);
c2 = (char)(65+test+1);
Random rnd = new Random();
int rows = 5+rnd.nextInt(5);
formulas = new String[]{"XIRR("+c1+(startRow+4)+":"+c1+(startRow+3+rows)+","+c2+(startRow+4)+":"+c2+(startRow+3+rows)+")",
"myXIRR("+c1+(startRow+4)+":"+c1+(startRow+3+rows)+", "+c2+(startRow+4)+":"+c2+(startRow+3+rows)+")",
""+c2+(startRow+1)+"-"+c2+(startRow+2)};
values = new Double[rows];
values[0] = -1d*(rows-1d)*(1000+rnd.nextInt(5000));
for (int i = 1; i < rows; i++) {
values[i] = 1d*(1000+rnd.nextInt(5000));
}
dates = new Date[rows];
for (int i = 0; i < rows; i++) {
dates[i] = sdf.parse(sdf.format(new Date(startDate+=day*(1L+rnd.nextInt(150)))));
}
for (int r = startRow; r < startRow+3; r++) {
row = (sheet.getRow(r)==null)?sheet.createRow(r):sheet.getRow(r);
cell = row.createCell(test);
cell.setCellValue(labels[r-startRow]);
}
for (int r = startRow+3; r < startRow+3+rows; r++) {
row = (sheet.getRow(r)==null)?sheet.createRow(r):sheet.getRow(r);
cell = row.createCell(test);
cell.setCellValue(values[r-startRow-3]);
cell = row.createCell(test+1);
cell.setCellValue(dates[r-startRow-3]);
cell.setCellStyle(dateStyle);
}
for (int r = startRow; r < startRow+2; r++) {
cell = sheet.getRow(r).createCell(test+1);
cell.setCellFormula(formulas[r-startRow]);
cell.setCellStyle(percentStyle);
if (r == startRow+1) {
evaluator.clearAllCachedResultValues();
cell = evaluator.evaluateInCell(cell);
System.out.println(new DataFormatter().formatCellValue(cell));
}
}
cell = sheet.getRow(startRow+2).createCell(test+1);
cell.setCellFormula(formulas[2]);
sheet.autoSizeColumn(test);
sheet.autoSizeColumn(test+1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main( String[] args ) {
XIRREvaluator xirrEvaluator = new XIRREvaluator();
//test case as from the example in Excel's XIRR documentation
//starting on column 0, row 0
xirrEvaluator.testCaseFromExcelDocu(0,0);
//9 random test cases
//starting on column 0, row 10
xirrEvaluator.randomTestCases(0,10,9);
//9 random test cases
//starting on column 0, row 25
xirrEvaluator.randomTestCases(0,25,9);
xirrEvaluator.save();
}
}
/*
Class for user defined function myXIRR
*/
class CalculateXIRR implements FreeRefFunction {
#Override
public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
if (args.length < 2 || args.length > 3) {
return ErrorEval.VALUE_INVALID;
}
double result;
try {
double[] values = ValueCollector.collectValues(args[0]);
double[] dates = ValueCollector.collectValues(args[1]);
double guess;
if(args.length == 3) {
ValueEval v = OperandResolver.getSingleValue(args[2], ec.getRowIndex(), ec.getColumnIndex()) ;
guess = OperandResolver.coerceValueToDouble(v);
} else {
guess = 0.1d;
}
result = calculateXIRR( values, dates, guess ) ;
checkValue(result);
} catch (EvaluationException e) {
//e.printStackTrace();
return e.getErrorEval();
}
return new NumberEval( result ) ;
}
public double calculateXIRR(double[] values, double[] dates, double guess ) {
double result;
/*
Either calculating XIRR using https://github.com/ept/jxirr (C) 2005 Gautam Satpathy
*/
XIRRData data = new XIRRData(values.length, guess, values, dates);
result = XIRR.xirr(data) - 1d;
/*
Or calculating XIRR Class Xirr
from https://stackoverflow.com/questions/36789967/java-program-to-calculate-xirr-without-using-excel-or-any-other-library
*/
//result = Xirr.Newtons_method(guess, values, dates);
return result;
}
static final void checkValue(double result) throws EvaluationException {
if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
}
static final class ValueCollector extends MultiOperandNumericFunction {
private static final ValueCollector instance = new ValueCollector();
public ValueCollector() {
super(false, false);
}
public static double[] collectValues(ValueEval...operands) throws EvaluationException {
return instance.getNumberArray(operands);
}
protected double evaluate(double[] values) {
throw new IllegalStateException("should not be called");
}
}
}
/*
Class Xirr from https://stackoverflow.com/questions/36789967/java-program-to-calculate-xirr-without-using-excel-or-any-other-library
*/
final class Xirr {
private static final double tol = 0.00000001;
private static double f_xirr(double p, double dt, double dt0, double x) {
double resf = p * Math.pow((x + 1d), (dt0-dt) / 365d);
return resf;
}
private static double df_xirr(double p, double dt, double dt0, double x) {
double resf = (1d / 365d) * (dt0-dt) * p * Math.pow((x + 1d), ((dt0-dt) / 365d) - 1d);
return resf;
}
private static double total_f_xirr(double[] payments, double[] days, double x) {
double resf = 0d;
for (int i = 0; i < payments.length; i++) {
resf = resf + f_xirr(payments[i], days[i], days[0], x);
}
return resf;
}
private static double total_df_xirr(double[] payments, double[] days, double x) {
double resf = 0d;
for (int i = 0; i < payments.length; i++) {
resf = resf + df_xirr(payments[i], days[i], days[0], x);
}
return resf;
}
public static double Newtons_method(double guess, double[] payments, double[] days) {
double x0 = guess;
double x1 = 0d;
double err = 1e+100;
while (err > tol) {
x1 = x0 - total_f_xirr(payments, days, x0) / total_df_xirr(payments, days, x0);
err = Math.abs(x1 - x0);
x0 = x1;
}
return x0;
}
}
I don't want to solve an equation and my question is not about Graphs and Trees Data Structures. I am trying to generate Data Points for graph from an equation given by user. I want efficient algorithm, easy to use and easy to maintain data structures. I have two solutions in mind
1: This is trivial and I have seen in many Applications.
String expr = "2*x+3*x";
Evaluator evaluator = new Evaluator();//I have this class
for (int i = start; i < end; i += step)
{
evaluator.setConstant("x", i);
double ans = evaluator.evaluate(expr);
}
This is very slow because each time every step is repeated like tokenzing, verifying, conversion to RPN, preparing stacks and queues and at last result calculation. The possible solution to this problem is somehow caching all stacks and queues but after that a comparison would be required between current expression and previous expression to use last stored state.
2: Currently I am developing second solution. The purpose of this is efficiency and would be used in Symbolic calculation in future.
So far my implementation
Variable.java
import java.text.DecimalFormat;
public class Variable
{
private final double pow;
private final double coefficient;
private final String symbol;
public Variable(String symbol)
{
this.symbol = symbol;
this.pow = 1.0;
this.coefficient = 1.0;
}
public Variable(String symbol, double coefficient, double pow)throws IllegalArgumentException
{
if (coefficient == 0.0)throw new IllegalArgumentException("trying to create variable with coefficient 0");
if (pow == 0.0)throw new IllegalArgumentException("trying to create variable with exponent 0");
this.symbol = symbol;
this.pow = pow;
this.coefficient = coefficient;
}
public final String getSymbol()
{
return this.symbol;
}
public final double getPow()
{
return this.pow;
}
public final double getCoefficient()
{
return this.coefficient;
}
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
DecimalFormat decimalFormat = new DecimalFormat("#.############");
if (coefficient != 1.0)builder.append(decimalFormat.format(this.coefficient));
builder.append(this.symbol);
if (this.pow != 1.0)builder.append("^").append(decimalFormat.format(this.pow));
return builder.toString();
}
/*
* Stub Method
* Generate some unique hash code
* such that chances of key collision
* become less and easy to identify
* variables with same power and same
* symbol*/
#Override
public int hashCode()
{
return 0;
}
}
Equation.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
public class Equation
{
private final ArrayList<Boolean> operations;
private final HashMap<String, Variable> variableHashMap;
private int typesOfVariables;
public Equation(Variable variable)
{
this.variableHashMap = new HashMap<>();
this.operations = new ArrayList<>();
this.typesOfVariables = 1;
this.variableHashMap.put(variable.getSymbol(), variable);
}
/*Stub Method*/
public void addVariable(Variable variable, boolean multiply)
{
/*
* Currently not covering many cases
* 1: Add two variables which have same name
* and same pow.
* 2: variable which are wrapped inside functions e.g sin(x)
* and many other.*/
if (multiply && variableHashMap.containsKey(variable.getSymbol()))
{
Variable var = variableHashMap.get(variable.getSymbol());
Variable newVar = new Variable(var.getSymbol(), var.getCoefficient() * variable.getCoefficient(), var.getPow() + variable.getPow());
/*
* Collision chances for variables with same name but
* with different powers*/
this.variableHashMap.replace(var.getSymbol(), newVar);
}
else
{
++this.typesOfVariables;
this.variableHashMap.put(variable.getSymbol(), variable);
}
this.operations.add(multiply);
}
/*Stub Method
*Value for every variable at any point will be different*/
public double solveFor(double x)
{
if (typesOfVariables > 1)throw new IllegalArgumentException("provide values for all variables");
Iterator<HashMap.Entry<String, Variable>> entryIterator = this.variableHashMap.entrySet().iterator();
Variable var;
double ans = 0.0;
if (entryIterator.hasNext())
{
var = entryIterator.next().getValue();
ans = var.getCoefficient() * Math.pow(x, var.getPow());
}
for (int i = 0; entryIterator.hasNext(); i++)
{
var = entryIterator.next().getValue();
if (this.operations.get(i))ans *= var.getCoefficient() * Math.pow(x, var.getPow());
else ans += var.getCoefficient() * Math.pow(x, var.getPow());
}
return ans;
}
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
Iterator<HashMap.Entry<String, Variable>> entryIterator = this.variableHashMap.entrySet().iterator();
if (entryIterator.hasNext())builder.append(entryIterator.next().getValue().toString());
Variable var;
for (int i = 0; entryIterator.hasNext(); i++)
{
var = entryIterator.next().getValue();
if (this.operations.get(i))builder.append("*").append(var.toString());
else builder.append(var.toString());
}
return builder.toString();
}
}
Main.java
class Main
{
public static void main(String[] args)
{
try
{
long t1 = System.nanoTime();
Variable variable = new Variable("x");
Variable variable1 = new Variable("x", -2.0, 1.0);
Variable variable2 = new Variable("x", 3.0, 4.0);
Equation equation = new Equation(variable);
equation.addVariable(variable1, true);//2x+x
equation.addVariable(variable2, true);
for (int i = 0; i < 1000000; i++)equation.solveFor(i);//Calculate Million Data Points
long t2 = System.nanoTime();
System.out.println((t2-t1)/1000/1000);
System.out.println(equation.toString());
}
catch (Exception e)
{
System.out.println("Error: " + e.getMessage());
}
}
}
Am I going in right direction?
Is there any commonly used Algorithm for this problem?
My main goal is efficiency, code cleanness and code maintainability.
Note: I am not native English speaker so please ignore any grammatical mistake.
Thanks.
I do not see any problem with your first code. Yes may be at every step your code "repeat like tokenzing, verifying, conversion to RPN, preparing stacks and queues and at last result calculation", but in the end all of this is just linear number of steps. So I fail to see how it can make it really slow.
One of the biggest screens I have seen was 2560x1440 pixels, which means that most of the time you would need less than 2500 points to draw your graph there.
If you point is code cleanness and code maintainability, then most probably a code consisting of 5 lines is better than the code consisting of 200.
I have to write a program to convert between linear units in, ft, mi, mm, cm, m, km. I know there are easier and better ways to do this. I think we'ere just trying to fully understand if else if statements. But this is what I have so far. I'm just trying to figure out if I am on the right track. I've tried to write out some pseudocode but it just seems like a lot going on so I find it a bit overwhelming. Next I'm going to add a method to convert form in or mm to whatever is selected by the user.
When I test the program i get this: UnitConversion#76c5a2f7 (EDIT: THIS ISSUE WAS FIXED)
Ok I made the suggested changes and that allowed the first part of the program to run properly. I have now added my second method to convert from in/mm to the other measurements.. I was having issues but I figured it out.
Here is my main method;
public class LinearConversion
{
public static void main(String[] args)
{
UnitConversion newConvert = new UnitConversion("km", "m", 100);
System.out.println(newConvert);
}
}
Any suggestions? What am I missing or not understanding about doing this sort of program?
public class UnitConversion
{
private String input;
private String output;
private double value;
private double temp;
private double in, ft, mi, mm, cm, m, km;
private final double inch_feet = 12;
private final double inch_miles = 63360;
private final double inch_millimeters = 25.4;
private final double inch_centimeters = 2.54;
private final double inch_meters = 0.0254;
private final double inch_kilometers = 0.0000254;
private final double millimeters_inch = 0.0393701;
private final double millimeters_feet = 0.00328084;
private final double millimeters_miles = 0.000000622;
private final double millimeter_centimeters = 10;
private final double millimeter_meters = 1000;
private final double millimeter_kilometers = 1000000;
public UnitConversion(String in, String out, double val)
{
input = in;
output = out;
value = val;
}
public String toString()
{
if (input.equals("mi"))
{
in = value * inch_miles;
input = "in";
}
else if (input.equals("ft"))
{
in = value * inch_feet;
input = "in";
}
else
{
in = value;
input = "in";
}
if (input.equals("km"))
{
mm = value * millimeter_kilometers;
input = "mm";
}
else if (input.equals("m"))
{
mm = value * millimeter_meters;
input = "mm";
}
else if (input.equals("cm"))
{
mm = value * millimeter_centimeters;
input = "mm";
}
else
{
mm = value;
input = "mm";
}
return value + input + " " + output;
}
public double getUnit()
{
if (input.equals("in"))
{
if (output.equals("ft"))
{
ft = in * inch_feet;
System.out.println(ft + "ft");
}
else if (output.equals("mi"))
{
mi = in * inch_miles;
System.out.println(mi + "mi");
}
else if (output.equals("mm"))
{
mm = in * inch_millimeters;
System.out.println(mm + "mm");
}
else if (output.equals("cm"))
{
cm = in * inch_centimeters;
System.out.println(cm + "cm");
}
else if (output.equals("m"))
{
m = in * inch_meters;
System.out.println(m + "m");
}
else if (output.equals("km"))
{
km = in * inch_kilometers;
System.out.println(km + "km");
}
else
{
System.out.println(in + "in");
}
}
else
{
if (output.equals("cm"))
{
cm = mm * millimeter_centimeters;
System.out.println(cm + "cm");
}
else if (output.equals("m"))
{
m = mm * millimeter_meters;
System.out.println(m + "m");
}
else if (output.equals("km"))
{
km = mm * millimeter_kilometers;
System.out.println(km + "km");
}
else if (output.equals("in"))
{
in = mm * millimeters_inch;
System.out.println(in + "in");
}
else if (output.equals("ft"))
{
ft = mm * millimeters_feet;
System.out.println(ft + "ft");
}
else if (output.equals("mi"))
{
mi = mm * millimeters_miles;
System.out.println(mi + "mi");
}
else
{
System.out.println(mm + "mm");
}
}
}
Basically, you need/want to give a String argument to System.out.println in order to display it.
Thus, when you use System.out.println with an Object (that is not a String) as the argument, Java actually outputs the result of the toString method on that object.
If you haven't overridden it, the Object class' implementation of toString is used: this is what gives you your current output: UnitConversion#76c5a2f7.
To learn more about how is this default toString implementation generating that String, you can refer to the javadoc entry for Object#toString.
Base on your output, and your provided code, yes! Rename String getInput() to String toString() and your current main() will work, or change your current main()
System.out.println(newConvert.getInput()); // <-- added .getInput()
I'm trying to write my own implementation of Watershed Segmentation for a project. I have a version that returns something resembling the correct segmentation given really trivial pictures. Unfortunately, it's super-slow/inefficient and it may or may not terminate in all cases.
I've been working from the description in "Digital Image Processing," by Woods and Gonzales, and from the Watershed Wikipedia page. The general algorithm is coded and included below, but I have a feeling I'm looping over a lot of things I do not need to be. I appreciate any and all help here, thanks in advance.
public static Set<Point> watershed(double[][] im) {
//Get the Gradient Magnitude to use as our "topographic map."
double t1 = System.currentTimeMillis();
double[][] edges = EdgeDetection.sobelMagnitude(im);
//Only iterate over values in the gradient magnitude to speed up.
double[] histogram = Image.getHistogram(edges);
Image.drawHistogram(histogram, Color.red);
int minPixelValue = 0;
for (int i = 0; i < histogram.length; i++) {
if (histogram[i] > 0) {
minPixelValue = i;
break;
}
}
int h = im.length;
int w = im[0].length;
//SE is a 3x3 structuring element for morphological dilation.
boolean[][] SE = {{true, true, true}, {true, true, true}, {true, true, true}};
//Keeping track of last iteration's components to see if two flooded together.
ArrayList<Set<Point>> lastComponents = connectedComponents(getSet(EdgeDetection.underThreshold(edges, minPixelValue + 1)));
ArrayList<Set<Point>> components;
Set<Point> boundary = new HashSet<Point>();
for (int i = minPixelValue + 1; i < 256; i++) {
if (histogram[i] != 0) {
System.out.println("BEHHH " + i);
t1 = System.currentTimeMillis();
ArrayList<Integer> damLocations = new ArrayList<Integer>();
HashMap<Integer, ArrayList<Integer>> correspondingSets = new HashMap<Integer, ArrayList<Integer>>();
//Figure out which of the old sets the new sets incorporated.
//Here is where we check if sets flooded into eachother.
//System.out.println("Checking for flooding");
components = connectedComponents(getSet(EdgeDetection.underThreshold(edges, i)));
for (int nc = 0; nc < components.size(); nc++) {
//System.out.println("Checking component " + nc);
Set<Point> newComponent = components.get(nc);
for (int oc = 0; oc < lastComponents.size(); oc++) {
//System.out.println(" Against component " + oc);
Set<Point> oldComponent = lastComponents.get(oc);
if (numberInCommon(newComponent, oldComponent) > 0) {
//System.out.println(" In there.");
ArrayList<Integer> oldSetsContained;
if (correspondingSets.containsKey(nc)) {
oldSetsContained = correspondingSets.get(nc);
damLocations.add(nc);
} else {
//System.out.println(" Nope.");
oldSetsContained = new ArrayList<Integer>();
}
oldSetsContained.add(oc);
correspondingSets.put(nc, oldSetsContained);
}
}
}
System.out.println("Calculating overlapping sets: " + (System.currentTimeMillis() - t1));
//System.out.println("Check done.");
for (int key : correspondingSets.keySet()) {
Integer[] cs = new Integer[correspondingSets.get(key).size()];
correspondingSets.get(key).toArray(cs);
if (cs.length == 1) {
//System.out.println("Set " + cs[0] + " has grown without flooding.");
} else {
//System.out.println("The following sets have flooded together: " + Arrays.toString(cs));
}
}
//Build Damns to prevent flooding
for (int c : damLocations) {
System.out.println("Building dam for component " + c);
Set<Point> bigComponent = components.get(c);
System.out.println("Total size: " + bigComponent.size());
ArrayList<Set<Point>> littleComponent = new ArrayList<Set<Point>>();
for (int lcindex : correspondingSets.get(c)) {
littleComponent.add(lastComponents.get(lcindex));
}
Set<Point> unionSet = new HashSet<Point>(boundary);
for (Set<Point> lc : littleComponent) {
unionSet = union(unionSet, lc);
}
System.out.println("Building union sets: " + (System.currentTimeMillis() - t1));
while (intersection(unionSet, bigComponent).size() < bigComponent.size()) {
for (int lIndex = 0; lIndex < littleComponent.size(); lIndex++) {
Set<Point> lc = littleComponent.get(lIndex);
Set<Point> lcBoundary = extractBoundaries(lc, SE, h, w);
Set<Point> toAdd = new HashSet<Point>();
Set<Point> otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
otherComponents = extractBoundaries(otherComponents, SE, h, w);
for (Point pt : lcBoundary) {
Set<Point> eightNbrs = get8Neighborhood(pt);
for (Point nbr : eightNbrs) {
if (bigComponent.contains(nbr) & !boundary.contains(nbr)) {
Set<Point> neighborNbr = get8Neighborhood(nbr);
if (intersection(neighborNbr, otherComponents).size() > 0) {
boundary.add(nbr);
edges[nbr.y][nbr.x] = 256;
break;
} else if (!lc.contains(nbr)) {
toAdd.add(nbr);
//if(i==65)System.out.println("Adding point " + nbr.y + " " + nbr.x);
} else {
//if(i==65)System.out.println("Already in here " + nbr.y + " " + nbr.x);
}
}
}
}
t1 = System.currentTimeMillis();
lc.addAll(toAdd);
toAdd.removeAll(toAdd);
littleComponent.set(lIndex, lc);
unionSet = new HashSet<Point>(boundary);
for (Set<Point> ltc : littleComponent) {
unionSet = union(unionSet, ltc);
}
System.out.println("This is a donk " + intersection(unionSet, bigComponent).size());
otherComponents = new HashSet<Point>(unionSet);
otherComponents.removeAll(lc);
otherComponents.removeAll(boundary);
}
}
}
}
}
boundary = close(boundary,h,w);
Image.drawSet(boundary, h, w);
return boundary;
}
The algorithm as it seems is at most O(n^2).
You have many many nested loops.. I was not able to find the Woods description.
This code by Christopher Mei implements the algorithm: it is a really simple implementation.
WatershedPixel.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* 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 plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.*;
/**
* The aim of WatershedPixel is to enable
* sorting the pixels of an Image according
* to their grayscale value.
*
* This is the first step of the Vincent
* and Soille Watershed algorithm (1991)
*
**/
public class WatershedPixel implements Comparable {
/** Value used to initialise the image */
final static int INIT = -1;
/** Value used to indicate the new pixels that
* are going to be processed (intial value
* at each level)
**/
final static int MASK = -2;
/** Value indicating that the pixel belongs
* to a watershed.
**/
final static int WSHED = 0;
/** Fictitious pixel **/
final static int FICTITIOUS = -3;
/** x coordinate of the pixel **/
private int x;
/** y coordinate of the pixel **/
private int y;
/** grayscale value of the pixel **/
private byte height;
/** Label used in the Watershed immersion algorithm **/
private int label;
/** Distance used for working on pixels */
private int dist;
/** Neighbours **/
private Vector neighbours;
public WatershedPixel(int x, int y, byte height) {
this.x = x;
this.y = y;
this.height = height;
label = INIT;
dist = 0;
neighbours = new Vector(8);
}
public WatershedPixel() {
label = FICTITIOUS;
}
public void addNeighbour(WatershedPixel neighbour) {
/*IJ.write("In Pixel, adding :");
IJ.write(""+neighbour);
IJ.write("Add done");
*/
neighbours.add(neighbour);
}
public Vector getNeighbours() {
return neighbours;
}
public String toString() {
return new String("("+x+","+y+"), height : "+getIntHeight()+", label : "+label+", distance : "+dist);
}
public final byte getHeight() {
return height;
}
public final int getIntHeight() {
return (int) height&0xff;
}
public final int getX() {
return x;
}
public final int getY() {
return y;
}
/** Method to be able to use the Collections.sort static method. **/
public int compareTo(Object o) {
if(!(o instanceof WatershedPixel))
throw new ClassCastException();
WatershedPixel obj = (WatershedPixel) o;
if( obj.getIntHeight() < getIntHeight() )
return 1;
if( obj.getIntHeight() > getIntHeight() )
return -1;
return 0;
}
public void setLabel(int label) {
this.label = label;
}
public void setLabelToINIT() {
label = INIT;
}
public void setLabelToMASK() {
label = MASK;
}
public void setLabelToWSHED() {
label = WSHED;
}
public boolean isLabelINIT() {
return label == INIT;
}
public boolean isLabelMASK() {
return label == MASK;
}
public boolean isLabelWSHED() {
return label == WSHED;
}
public int getLabel() {
return label;
}
public void setDistance(int distance) {
dist = distance;
}
public int getDistance() {
return dist;
}
public boolean isFICTITIOUS() {
return label == FICTITIOUS;
}
public boolean allNeighboursAreWSHED() {
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( !r.isLabelWSHED() )
return false;
}
return true;
}
}
WatershedFIFO.java
/*
* Watershed plugin
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* 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 plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.util.*;
import ij.*;
/** This class implements a FIFO queue that
* uses the same formalism as the Vincent
* and Soille algorithm (1991)
**/
public class WatershedFIFO {
private LinkedList watershedFIFO;
public WatershedFIFO() {
watershedFIFO = new LinkedList();
}
public void fifo_add(WatershedPixel p) {
watershedFIFO.addFirst(p);
}
public WatershedPixel fifo_remove() {
return (WatershedPixel) watershedFIFO.removeLast();
}
public boolean fifo_empty() {
return watershedFIFO.isEmpty();
}
public void fifo_add_FICTITIOUS() {
watershedFIFO.addFirst(new WatershedPixel());
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedFIFO.size(); i++) {
ret.append( ((WatershedPixel)watershedFIFO.get(i)).toString() );
ret.append( "\n" );
}
return ret.toString();
}
}
WatershedStructure.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* 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 plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import java.lang.*;
import java.util.*;
import ij.process.*;
import ij.*;
import java.awt.*;
/**
* WatershedStructure contains the pixels
* of the image ordered according to their
* grayscale value with a direct access to their
* neighbours.
*
**/
public class WatershedStructure {
private Vector watershedStructure;
public WatershedStructure(ImageProcessor ip) {
byte[] pixels = (byte[])ip.getPixels();
Rectangle r = ip.getRoi();
int width = ip.getWidth();
int offset, topOffset, bottomOffset, i;
watershedStructure = new Vector(r.width*r.height);
/** The structure is filled with the pixels of the image. **/
for (int y=r.y; y<(r.y+r.height); y++) {
offset = y*width;
IJ.showProgress(0.1+0.3*(y-r.y)/(r.height));
for (int x=r.x; x<(r.x+r.width); x++) {
i = offset + x;
int indiceY = y-r.y;
int indiceX = x-r.x;
watershedStructure.add(new WatershedPixel(indiceX, indiceY, pixels[i]));
}
}
/** The WatershedPixels are then filled with the reference to their neighbours. **/
for (int y=0; y<r.height; y++) {
offset = y*width;
topOffset = offset+width;
bottomOffset = offset-width;
IJ.showProgress(0.4+0.3*(y-r.y)/(r.height));
for (int x=0; x<r.width; x++) {
WatershedPixel currentPixel = (WatershedPixel)watershedStructure.get(x+offset);
if(x+1<r.width) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+1+topOffset));
}
if(x-1>=0) {
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+offset));
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x-1+topOffset));
}
if(y-1>=0)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+bottomOffset));
if(y+1<r.height)
currentPixel.addNeighbour((WatershedPixel)watershedStructure.get(x+topOffset));
}
}
Collections.sort(watershedStructure);
//IJ.showProgress(0.8);
}
public String toString() {
StringBuffer ret = new StringBuffer();
for(int i=0; i<watershedStructure.size() ; i++) {
ret.append( ((WatershedPixel) watershedStructure.get(i)).toString() );
ret.append( "\n" );
ret.append( "Neighbours :\n" );
Vector neighbours = ((WatershedPixel) watershedStructure.get(i)).getNeighbours();
for(int j=0 ; j<neighbours.size() ; j++) {
ret.append( ((WatershedPixel) neighbours.get(j)).toString() );
ret.append( "\n" );
}
ret.append( "\n" );
}
return ret.toString();
}
public int size() {
return watershedStructure.size();
}
public WatershedPixel get(int i) {
return (WatershedPixel) watershedStructure.get(i);
}
}
Watershed_Algorithm.java
/*
* Watershed algorithm
*
* Copyright (c) 2003 by Christopher Mei (christopher.mei#sophia.inria.fr)
*
* This plugin is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* 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 plugin; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
import ij.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import ij.gui.*;
import ij.plugin.frame.PlugInFrame;
import java.awt.*;
import java.util.*;
/**
* This algorithm is an implementation of the watershed immersion algorithm
* written by Vincent and Soille (1991).
*
* #Article{Vincent/Soille:1991,
* author = "Lee Vincent and Pierre Soille",
* year = "1991",
* keywords = "IMAGE-PROC SKELETON SEGMENTATION GIS",
* institution = "Harvard/Paris+Louvain",
* title = "Watersheds in digital spaces: An efficient algorithm
* based on immersion simulations",
* journal = "IEEE PAMI, 1991",
* volume = "13",
* number = "6",
* pages = "583--598",
* annote = "Watershed lines (e.g. the continental divide) mark the
* boundaries of catchment regions in a topographical map.
* The height of a point on this map can have a direct
* correlation to its pixel intensity. WIth this analogy,
* the morphological operations of closing (or opening)
* can be understood as smoothing the ridges (or filling
* in the valleys). Develops a new algorithm for obtaining
* the watershed lines in a graph, and then uses this in
* developing a new segmentation approach based on the
* {"}depth of immersion{"}.",
* }
*
* A review of Watershed algorithms can be found at :
* http://www.cs.rug.nl/~roe/publications/parwshed.pdf
*
* #Article{RoeMei00,
* author = "Roerdink and Meijster",
* title = "The Watershed Transform: Definitions, Algorithms and
* Parallelization Strategies",
* journal = "FUNDINF: Fundamenta Informatica",
* volume = "41",
* publisher = "IOS Press",
* year = "2000",
* }
**/
public class Watershed_Algorithm implements PlugInFilter {
private int threshold;
final static int HMIN = 0;
final static int HMAX = 256;
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about"))
{showAbout(); return DONE;}
return DOES_8G+DOES_STACKS+SUPPORTS_MASKING+NO_CHANGES;
}
public void run(ImageProcessor ip) {
boolean debug = false;
IJ.showStatus("Sorting pixels...");
IJ.showProgress(0.1);
/** First step : the pixels are sorted according to increasing grey values **/
WatershedStructure watershedStructure = new WatershedStructure(ip);
if(debug)
IJ.write(""+watershedStructure.toString());
IJ.showProgress(0.8);
IJ.showStatus("Start flooding...");
if(debug)
IJ.write("Starting algorithm...\n");
/** Start flooding **/
WatershedFIFO queue = new WatershedFIFO();
int curlab = 0;
int heightIndex1 = 0;
int heightIndex2 = 0;
for(int h=HMIN; h<HMAX; h++) /*Geodesic SKIZ of level h-1 inside level h */ {
for(int pixelIndex = heightIndex1 ; pixelIndex<watershedStructure.size() ; pixelIndex++) /*mask all pixels at level h*/ {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex1 = pixelIndex;
break;
}
p.setLabelToMASK();
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) {
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(q.getLabel()>=0) {/*Initialise queue with neighbours at level h of current basins or watersheds*/
p.setDistance(1);
queue.fifo_add(p);
break;
} // end if
} // end for
} // end for
int curdist = 1;
queue.fifo_add_FICTITIOUS();
while(true) /** extend basins **/{
WatershedPixel p = queue.fifo_remove();
if(p.isFICTITIOUS())
if(queue.fifo_empty())
break;
else {
queue.fifo_add_FICTITIOUS();
curdist++;
p = queue.fifo_remove();
}
if(debug) {
IJ.write("\nWorking on :");
IJ.write(""+p);
}
Vector neighbours = p.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* Labelling p by inspecting neighbours */{
WatershedPixel q = (WatershedPixel) neighbours.get(i);
if(debug)
IJ.write("Neighbour : "+q);
/* Original algorithm :
if( (q.getDistance() < curdist) &&
(q.getLabel()>0 || q.isLabelWSHED()) ) {*/
if( (q.getDistance() <= curdist) &&
(q.getLabel()>=0) ) {
/* q belongs to an existing basin or to a watershed */
if(q.getLabel() > 0) {
if( p.isLabelMASK() )
// Removed from original algorithm || p.isLabelWSHED() )
p.setLabel(q.getLabel());
else
if(p.getLabel() != q.getLabel())
p.setLabelToWSHED();
} // end if lab>0
else
if(p.isLabelMASK())
p.setLabelToWSHED();
}
else
if( q.isLabelMASK() &&
(q.getDistance() == 0) ) {
if(debug)
IJ.write("Adding value");
q.setDistance( curdist+1 );
queue.fifo_add( q );
}
} // end for, end processing neighbours
if(debug) {
IJ.write("End processing neighbours");
IJ.write("New val :\n"+p);
IJ.write("Queue :\n"+queue);
}
} // end while (loop)
/* Detect and process new minima at level h */
for(int pixelIndex = heightIndex2 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.getIntHeight() != h) {
/** This pixel is at level h+1 **/
heightIndex2 = pixelIndex;
break;
}
p.setDistance(0); /* Reset distance to zero */
if(p.isLabelMASK()) { /* the pixel is inside a new minimum */
curlab++;
p.setLabel(curlab);
queue.fifo_add(p);
while(!queue.fifo_empty()) {
WatershedPixel q = queue.fifo_remove();
Vector neighbours = q.getNeighbours();
for(int i=0 ; i<neighbours.size() ; i++) /* inspect neighbours of p2*/{
WatershedPixel r = (WatershedPixel) neighbours.get(i);
if( r.isLabelMASK() ) {
r.setLabel(curlab);
queue.fifo_add(r);
}
}
} // end while
} // end if
} // end for
} /** End of flooding **/
IJ.showProgress(0.9);
IJ.showStatus("Putting result in a new image...");
/** Put the result in a new image **/
int width = ip.getWidth();
ImageProcessor outputImage = new ByteProcessor(width, ip.getHeight());
byte[] newPixels = (byte[]) outputImage.getPixels();
for(int pixelIndex = 0 ; pixelIndex<watershedStructure.size() ; pixelIndex++) {
WatershedPixel p = watershedStructure.get(pixelIndex);
if(p.isLabelWSHED() && !p.allNeighboursAreWSHED())
newPixels[p.getX()+p.getY()*width] = (byte)255;
}
IJ.showProgress(1);
IJ.showStatus("Displaying result...");
new ImagePlus("Watershed", outputImage).show();
}
void showAbout() {
IJ.showMessage("About Watershed_Algorithm...",
"This plug-in filter calculates the watershed of a 8-bit images.\n" +
"It uses the immersion algorithm written by Vincent and Soille (1991)\n"
);
}
}