-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#32 Added filter to calculate enclosed area
- Loading branch information
1 parent
78368a9
commit 544c9b3
Showing
8 changed files
with
268 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
@@ -200,6 +200,7 @@ class FileGraph implements TabAPI { | |
if (newoutput.contains(".csv")) { | ||
//outputfile = newoutput; | ||
currentfile = newoutput; | ||
xData = -1; | ||
workerActive = true; | ||
WorkerThread loadingThread = new WorkerThread(); | ||
loadingThread.loadFile(); | ||
|
@@ -233,7 +234,7 @@ class FileGraph implements TabAPI { | |
for (int i = 0; i < dataTable.getColumnCount(); i++) { | ||
|
||
String columnTitle = dataTable.getColumnTitle(i); | ||
if (columnTitle.contains("x:")) { | ||
if (columnTitle.contains("x:") || columnTitle.contains("X:")) { | ||
xData = i; | ||
} else if (columnTitle.contains("l:")) { | ||
columnTitle = split(columnTitle, ':')[1]; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
@@ -29,6 +29,9 @@ | |
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
import java.text.DecimalFormat; | ||
|
||
|
||
class Filters { | ||
|
||
// Filter list to be shown in the sidebar menu. | ||
|
@@ -42,9 +45,12 @@ class Filters { | |
"Absolute Value: |x|", | ||
"Squared: x^2", | ||
"Derivative: Δx/dt", | ||
"Integral: Σxdt"}; | ||
"Integral: Σxdt", | ||
"h:Signal Analysis", | ||
"Fourier Transform", | ||
"Enclosed Area"}; | ||
|
||
String[] filterSlug = {"h", "avg", "tv", "lp", "hp", "h", "abs", "squ", "Δ/dt", "Σdt"}; | ||
String[] filterSlug = {"h", "avg", "tv", "lp", "hp", "h", "abs", "squ", "Δ/dt", "Σdt", "h", "fft", "ea"}; | ||
|
||
/** | ||
* Default Constructor | ||
|
@@ -150,6 +156,30 @@ class Filters { | |
} | ||
break; | ||
} | ||
|
||
// Fast Fourier Transform | ||
case 10: | ||
case 11: { | ||
alertMessage("Coming Soon\nThe FFT feature is still being developed!"); | ||
break; | ||
} | ||
|
||
// Enclosed Area Calculation | ||
case 12: { | ||
AreaCalculation areaCalc = new AreaCalculation(); | ||
double[] calculatedAreas = areaCalc.processCycles(xAxisData, signalData); | ||
|
||
if (calculatedAreas == null || calculatedAreas.length < 1) { | ||
alertMessage("Enclosed Area Calculation\nError: No cycles were found in the data\nTherefore there are no enclosed areas"); | ||
} else { | ||
double averageArea = 0; | ||
for (int i = 0; i < calculatedAreas.length; i++) averageArea += calculatedAreas[i]; | ||
averageArea /= calculatedAreas.length; | ||
DecimalFormat format = new DecimalFormat("0.########"); | ||
alertMessage("Enclosed Area Calculation\nCycles Detected: " + calculatedAreas.length + "\nAverage Area: " + format.format(averageArea)); | ||
} | ||
break; | ||
} | ||
} | ||
|
||
return outputData; | ||
|
@@ -388,7 +418,7 @@ class Filters { | |
* 1D Total Variation (TV) Noise Removal Algorithm | ||
* | ||
* @author Laurent Condat | ||
* @copyright CeCILL Licence (compatible with GNU GPL v2) | ||
* @copyright CeCILL Licence (compatible with GNU GPL v3) | ||
* @note The algorithm is based on the original C code by | ||
* Laurent Condat with minor adaptations to encapsulate | ||
* it in a class and make it work in Java | ||
|
@@ -528,4 +558,226 @@ class Filters { | |
} | ||
} | ||
|
||
|
||
/** | ||
* Fast Fourier Transform (FFT) to analyse frequency response of a signal | ||
* | ||
* @author Numerical Recipes chapter on FFTs | ||
* @copyright Apache Licence (compatible with GNU GPL v3) | ||
*/ | ||
public class FastFourierTransform { | ||
|
||
public double[] process(double[] signalData, double samplingFrequency, double[] amplitudeArray) | ||
{ | ||
// Figure out length of FFT (it must be a factor of 2) | ||
int signalLength = signalData.length; | ||
int pt = signalLength; | ||
pt--; | ||
pt |= pt >> 1; | ||
pt |= pt >> 2; | ||
pt |= pt >> 4; | ||
pt |= pt >> 8; | ||
pt |= pt >> 16; | ||
pt++; | ||
|
||
// Generate the complex data array | ||
double[] complexArray = new double[pt * 2]; | ||
for (int i = 0; i < pt * 2; i++) complexArray[i] = 0; | ||
for (int i = 0; i < signalLength; i++) complexArray[i * 2] = signalData[i]; | ||
|
||
// Calculate the FFT | ||
fft(complexArray, pt, 1); | ||
|
||
// Get the amplitude of the complex number | ||
amplitudeArray = new double[signalLength]; | ||
for (int i = 0; i < signalLength; i++) { | ||
amplitudeArray[i] = java.lang.Math.sqrt((complexArray[i * 2] * complexArray[i * 2]) + (complexArray[i*2 + 1] * complexArray[i*2 + 1])); | ||
} | ||
|
||
// Generate the frequency axis | ||
double[] frequencyAxis = new double[signalLength]; | ||
for (int i = 0; i < signalLength; i++) frequencyAxis[i] = i * samplingFrequency / pt; | ||
|
||
return amplitudeArray; | ||
} | ||
|
||
|
||
public void fft(double[] data, int noOfSamples, int direction) | ||
{ | ||
//variables for trigonometric recurrences | ||
int i, j, n, m, mmax, istep; | ||
double wr, wpr, wi, wpi, wtemp, tempr, tempi, theta; | ||
|
||
// The complex array is real+complex so the array has a | ||
// size n = 2* number of complex samples. The real part | ||
// is the data[index] and the complex part is the data[index+1] | ||
n = noOfSamples * 2; | ||
|
||
// Binary inversion (real = even-indexes, complex = odd-indexes) | ||
j = 0; | ||
|
||
for (i = 0; i < n / 2; i += 2) { | ||
if (j > i) { | ||
|
||
// Swap the real part | ||
tempr = data[j]; | ||
data[j] = data[i]; | ||
data[i] = tempr; | ||
|
||
// Swap the complex part | ||
tempr = data[j+1]; | ||
data[j+1] = data[i+1]; | ||
data[i+1] = tempr; | ||
|
||
// Checks if the changes occurs in the first half | ||
// and use the mirrored effect on the second half | ||
if ((j / 2) < (n / 4)) { | ||
// Swap the real part | ||
tempr = data[(n - (i + 2))]; | ||
data[(n - (i + 2))] = data[(n - (j + 2))]; | ||
data[(n - (j + 2))] = tempr; | ||
|
||
// Swap the complex part | ||
tempr = data[(n - (i + 2)) + 1]; | ||
data[(n - (i + 2)) + 1] = data[(n - (j + 2)) + 1]; | ||
data[(n - (j + 2)) + 1] = tempr; | ||
} | ||
} | ||
|
||
m = n / 2; | ||
|
||
while (m >= 2 && j >= m) { | ||
j -= m; | ||
m = m / 2; | ||
} | ||
j += m; | ||
|
||
print(i); print(" , "); println(j); | ||
} | ||
|
||
// Danielson-Lanzcos routine | ||
mmax = 2; | ||
|
||
// External loop | ||
while (n > mmax) { | ||
istep = mmax << 1; | ||
theta = direction * (2 * PI / mmax); | ||
wtemp = java.lang.Math.sin(0.5 * theta); | ||
wpr = -2.0 * wtemp * wtemp; | ||
wpi = java.lang.Math.sin(theta); | ||
wr = 1.0; | ||
wi = 0.0; | ||
|
||
// Internal loops | ||
for (m = 1; m < mmax; m += 2) { | ||
for (i = m; i <= n; i += istep) { | ||
j = i + mmax; | ||
tempr = wr * data[j-1] - wi * data[j]; | ||
tempi = wr * data[j] + wi * data[j-1]; | ||
data[j-1] = data[i-1] - tempr; | ||
data[j] = data[i] - tempi; | ||
data[i-1] += tempr; | ||
data[i] += tempi; | ||
} | ||
wr = (wtemp = wr) * wpr - wi * wpi + wr; | ||
wi = wi * wpr + wtemp * wpi + wi; | ||
} | ||
mmax = istep; | ||
} | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Enclosed Area Calculation | ||
* | ||
* @author Simon Bluett | ||
* @copyright GNU GPL-v3 | ||
*/ | ||
public class AreaCalculation { | ||
|
||
/** | ||
* Calculate the area of enclosed cycles/loops in the data | ||
* @param[in] xData X-axis data | ||
* @param[in] yData Y-axis data | ||
*/ | ||
public double[] processCycles(double[] xData, double[] yData) { | ||
|
||
// Detect cycles in the data, which correspond to enclosed areas | ||
int[] cycleEnd = detectCycles(xData, yData); | ||
double[] calculatedArea = null; | ||
//println("CycleEnd.length = " + cycleEnd.length); | ||
|
||
if ((cycleEnd != null) && (cycleEnd.length > 1)) { | ||
calculatedArea = new double[0]; | ||
|
||
for (int i = 1; i < cycleEnd.length; i++) { | ||
|
||
double currentArea = 0; | ||
|
||
// Apply the discrete version of Green's Theorem (Pick's Theorem) | ||
// Based on formula in DOI: 10.1117/1.JEI.26.6.063022 | ||
for (int j = cycleEnd[i - 1]; j < cycleEnd[i] - 1; j++) { | ||
double areaSlice = ((xData[j] * yData[j+1]) - (yData[j] * xData[j + 1])) / 2.0; | ||
currentArea += areaSlice; | ||
//println(areaSlice); | ||
} | ||
|
||
// Ensure the cycle is closed | ||
currentArea += ((xData[cycleEnd[i]-1] * yData[cycleEnd[i - 1]]) - (yData[cycleEnd[i] - 1] * xData[cycleEnd[i - 1]])) / 2.0; | ||
currentArea = java.lang.Math.abs(currentArea); | ||
calculatedArea = (double[])append(calculatedArea, currentArea); | ||
//println(currentArea); | ||
} | ||
} | ||
|
||
return calculatedArea; | ||
} | ||
|
||
/** | ||
* Detect whether the data forms a closed loop | ||
* @param[in] xData X-axis data | ||
* @param[in] yData Y-axis data | ||
* @return An array of the indices where one cycle ends and a new one starts | ||
*/ | ||
public int[] detectCycles(double[] xData, double[] yData) { | ||
|
||
if (xData == null || yData == null || (xData.length != yData.length) || xData.length < 3) return null; | ||
|
||
int[] cycleEnd = { 0 }; | ||
double[] startPoint = { xData[0], yData[0] }; | ||
double avgStepDistance = distance(xData[0], yData[0], xData[1], yData[0]); | ||
|
||
for (int i = 2; i < xData.length; i++) { | ||
// Update the average distance between points | ||
avgStepDistance = ((avgStepDistance * i) + distance(xData[i-1], yData[i-1], xData[i], yData[i])) / (double)(i + 1); | ||
|
||
// If the current point is closer to the start point than half the | ||
// average distance, then a full loop has probably been completed | ||
if (distance(startPoint[0], startPoint[1], xData[i], yData[i]) < avgStepDistance * 0.5) { | ||
cycleEnd = append(cycleEnd, i); | ||
startPoint[0] = xData[i]; | ||
startPoint[1] = yData[i]; | ||
//println(i); | ||
|
||
// Increment twice to prevent the next point from triggering a loop detection | ||
i++; | ||
} | ||
} | ||
|
||
return cycleEnd; | ||
} | ||
|
||
/** | ||
* Calculate the distance between two coordinates | ||
* @param[in] x1 X-value of point A | ||
* @param[in] y1 Y-value of point A | ||
* @param[in] x2 X-value of point B | ||
* @param[in] y2 Y-value of point B | ||
* @return The euclidean distance between two points | ||
*/ | ||
private double distance(double x1, double y1, double x2, double y2) { | ||
return java.lang.Math.sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,12 @@ | |
* @website https://wired.chillibasket.com/processing-grapher/ | ||
* | ||
* @copyright GNU General Public License v3 | ||
* @date 21st December 2021 | ||
* @version 1.3.5 | ||
* @date 11th June 2022 | ||
* @version 1.4.0 | ||
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
@@ -31,7 +31,7 @@ | |
* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
final String versionNumber = "1.3.5"; | ||
final String versionNumber = "1.4.0"; | ||
|
||
// Swing for input popups | ||
import static javax.swing.JOptionPane.*; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
* * * * * * * * * * * * * * * * * * * * * * */ | ||
|
||
/* | ||
* Copyright (C) 2021 - Simon Bluett <[email protected]> | ||
* Copyright (C) 2022 - Simon Bluett <[email protected]> | ||
* | ||
* This file is part of ProcessingGrapher | ||
* <https://github.com/chillibasket/processing-grapher> | ||
|