Skip to content

Commit 3710ef6

Browse files
authored
Files updated for 4v1 release
1 parent c1b5893 commit 3710ef6

File tree

5 files changed

+702
-99
lines changed

5 files changed

+702
-99
lines changed

Diff for: UDataPlotWindow.cpp

+99-51
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
// Loosely based on an original public domain version by Frank Heinrich, mail@frank-heinrich.de
55
//
66
// Originally created using Borland Builder c++ V5
7-
// This version compiled with Embarcadero® C++Builder 11.3
7+
// This version compiled with Embarcadero® C++Builder 12.1
88
// It should be easy to move to a more recent version of C++Builder, but equally it would not be very hard to revert to earlier versions if necessary.
9-
// This is compiled for (and this is the only version that has been tested) Windows 32 bits.
10-
// It runs on Windows 10 64 bits. In theory it should run on windows versions from Vista onwards and 32bit versions of Windows but this is untested.
9+
//
1110
//
1211
// Version Date : changes from previous version
1312
// 1v0 1/1/2021 : 1st version released on github (was 16v6).
@@ -153,6 +152,19 @@
153152
// 11n - added tiff and wdp file save
154153
// 11o - csvgraph/manual path can now be unicode - removed AnsiOf() & UnicodeOf() as not needed anymore. Most _WIN64 #if's also removed as no longer required.
155154
// >> renamed 4v0 for release
155+
// 4v1 24/9/2024 1st version using C++ Builder 12.1
156+
// 1b - for x values allowed >= for "monotonic". Previoulsy required > which caused issues when we run out of resolution in a float (e.g. file csvfunbig.csv)
157+
// 1c - better "fixup" for equal x values
158+
// 1d - min abs/rel error fit improved (some calculations were done in double but should have been in float)
159+
// - GMR line fit could generate a line with the wrong slope! - fixed
160+
// 1e - let user select if equal x values are optimised or not. Only ask once if multiple traces added in a block.
161+
// 1f - central moving average & cumulative average filters added
162+
// 1g - central moving average filter - full (efficient) code in place
163+
// 1h - start of new median filter code
164+
// 1i - new median code fully working. Uses time to swap from exact to approximate algorithms.
165+
// 1j - new median code optimised, and errors of approximate algorithms bounded
166+
// 1k - cumulate average changed to Kalman filter as that's more useful.
167+
// 1L - improved Kalman filter implementation
156168
//
157169
// TO DO:
158170
//
@@ -234,9 +246,9 @@
234246
extern TForm1 *Form1;
235247
extern const char * Prog_Name;
236248
#ifdef _WIN64
237-
const char * Prog_Name="CSVgraph (Github) 4v0 (64 bit)"; // needs to be global as used in about box as well.
249+
const char * Prog_Name="CSVgraph (Github) 4v1 (64 bit)"; // needs to be global as used in about box as well.
238250
#else
239-
const char * Prog_Name="CSVgraph (Github) 4v0 (32 bit)"; // needs to be global as used in about box as well.
251+
const char * Prog_Name="CSVgraph (Github) 4v1 (32 bit)"; // needs to be global as used in about box as well.
240252
#endif
241253
#if 1 /* if 1 then use fast_strtof() rather than atof() for floating point conversion. Note in this application this is only slightly faster (1-5%) */
242254
extern "C" float fast_strtof(const char *s,char **endptr); // if endptr != NULL returns 1st character thats not in the number
@@ -1532,6 +1544,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
15321544
bool compress;
15331545
bool showsortmessage=true; // only show "sort message" once per press of the "add traces" button
15341546
bool showerrmessage=true; // ditto for message to warn users of errors in format of csv file
1547+
int dupXmessage=0; // only ask if user wants to optimise duplicate X values once
15351548
float previousxvalue=0,previousyvalue=0,last_actual_x_value=0;
15361549
double median_ahead_t; // >0 for median filtering to be used
15371550
int poly_order=2;
@@ -2195,7 +2208,7 @@ try{
21952208
}
21962209

21972210
// get x value
2198-
if(allvalidxvals && nos_traces_added>1 && xmonotonic && !compress ) // This optimisation disabled if lines with invalid xvals found (and skipped)
2211+
if(allvalidxvals && nos_traces_added>1 && xmonotonic && !compress && dupXmessage!=IDNO ) // This optimisation disabled if lines with invalid xvals found (and skipped)
21992212
{xval=(float)((double)(pScientificGraph->fnAddDataPoint_nextx(iGraph))-x_offset); // same value as previous graph loaded as this is faster than decoding it again , x_offset will be added later but is already in previous trace so must subtract here
22002213
if(!firstxvalue && xval<previousxvalue)
22012214
{if(++nos_errs<=MAX_ERRS)
@@ -2429,20 +2442,14 @@ try{
24292442
previousyvalue=yval;
24302443
}
24312444
else
2432-
{float prev_last_actual_x_value=last_actual_x_value;
2445+
{
24332446
last_actual_x_value=xval; // remember actual last x value
2434-
if((float)xval<=(float)previousxvalue && (float)xval>=(float)prev_last_actual_x_value)
2435-
{// try and fix up (starts with two x values being equal, but fixup could make this worse for a while)
2436-
// eg 1 2 2 2 3 -> 1 2 3 4 5
2437-
xval=nextafterf(previousxvalue,FLT_MAX); // keep incrementing value we wil use for x-axis
2438-
// rprintf("Fixup x value: xval was %.9g fixed up to %.9g, previousxvalue=%.9g prev_last_actual_x_value=%.9g\n",last_actual_x_value,xval,previousxvalue,prev_last_actual_x_value);
2439-
}
2440-
else if((float)xval<=(float)previousxvalue)
2441-
{ // x value is NOT monotonically increasing (could be =, but we leave that case for sorting as well as in general we cannot fix that here)
2447+
if((float)xval<(float)previousxvalue)
2448+
{ // x value is NOT monotonically increasing (equal values are OK)
24422449
xmonotonic=false;
24432450
// rprintf("xval(=%.15g)<previousxvalue(=%.15g)\n", xval,previousxvalue);
24442451
}
2445-
else {// x value is monotonically increasing
2452+
else {// x value is monotonically increasing (or =)
24462453

24472454
#if 1 /* this way of compressing only works if x values are monotonically increasing, we only know this correctly on the 2nd trace added onwards when we can trust xmonotonic */
24482455
/* There is a general solution for compression in function compress_y() but that requires reading all data into an array and then compressing it (and then freeing extra memory) so peak RAM is lower by doing it here if we can */
@@ -2509,18 +2516,21 @@ try{
25092516
ShowMessage(cap_str);
25102517
}
25112518
#if 1 /* check x values are monotonic if we think they are */
2519+
bool found_eq_xvals=false;
25122520
if(xmonotonic)
25132521
{float *xptr,*yptr;
25142522
size_t nos_points;
25152523
bool sorted=true;
25162524
// size_t fnGetxyarr(float **x_arr,float **y_arr,int iGraphNumberF = 0); // allow access to x and y arrays, returns nos points
25172525
nos_points=pScientificGraph->fnGetxyarr(&xptr,&yptr,iGraph);
25182526
for(size_t i=1;i<nos_points;++i)
2519-
{if(xptr[i]<=xptr[i-1])
2527+
{if(xptr[i]<xptr[i-1])
25202528
{sorted=false;
25212529
rprintf("checking if x values sorted: they are not at x[%zu]=%.9g and x[%zu]=%.9g\n",i,xptr[i],i,xptr[i-1]);
25222530
break; // we have failed - no need to test any more
25232531
}
2532+
if(xptr[i]==xptr[i-1])
2533+
found_eq_xvals=true;
25242534
}
25252535
if(!sorted)
25262536
{// rprintf("Error adding data xmonotic is true but sorted is false!");
@@ -2541,15 +2551,38 @@ try{
25412551
pScientificGraph->sortx(iGraph); // sort on x values
25422552
StatusText->Caption="X values sorted";
25432553
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
2554+
/* now see if there are any equal values */
2555+
float *xptr,*yptr;
2556+
size_t nos_points;
2557+
// size_t fnGetxyarr(float **x_arr,float **y_arr,int iGraphNumberF = 0); // allow access to x and y arrays, returns nos points
2558+
nos_points=pScientificGraph->fnGetxyarr(&xptr,&yptr,iGraph);
2559+
for(size_t i=1;i<nos_points;++i)
2560+
{
2561+
if(xptr[i]==xptr[i-1])
2562+
{found_eq_xvals=true;
2563+
break;
2564+
}
2565+
}
25442566
}
2567+
#if 1
2568+
if(found_eq_xvals)
2569+
{
2570+
if(dupXmessage==0) // IDYES!=0 and IDNO!=0, so we only ask user once for every set of traces added
2571+
dupXmessage=Application->MessageBox(L"Are duplicate X values expected?\n Duplicates were found, but this may be due to\n csvgraphs 7 significant digit resolution", L"Optimise duplicate X values", MB_YESNO);
2572+
if(dupXmessage==IDNO)
2573+
pScientificGraph->fix_dupx(iGraph); // "fix" any duplicate xvalues
2574+
}
2575+
#endif
25452576
#if 1 /* general purpose compression - works well but does require all data for the trace is read into ram , then excess is returned so peak ram is high */
25462577
if(compress && !(nos_traces_added>1 && xmonotonic) ) // && ! bit traps cases that can be done when reading the trace data in (above)
2547-
{StatusText->Caption="Compressing...";
2548-
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
2549-
pScientificGraph->compress_y(iGraph); // compress by deleting points with equal y values except for 1st and last in a row
2550-
StatusText->Caption="Compression completed";
2551-
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
2552-
}
2578+
{
2579+
StatusText->Caption="Compressing...";
2580+
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
2581+
pScientificGraph->compress_y(iGraph); // compress by deleting points with equal y values except for 1st and last in a row
2582+
StatusText->Caption="Compression completed";
2583+
Application->ProcessMessages(); /* allow windows to update (but not go idle) */
2584+
2585+
}
25532586
#endif
25542587
// now implement filter on data just read in if user requires this
25552588
switch(FilterType->ItemIndex)
@@ -2617,13 +2650,28 @@ try{
26172650
poly_order,median_ahead_t,sqrt(pow(2.0,1.0/(double)poly_order)-1.0)/(2.0*3.14159265358979*median_ahead_t));
26182651
}
26192652
break;
2620-
case 6:
2653+
case 6: // Central moving average filter
2654+
if(median_ahead_t>0.0)
2655+
{rprintf("Central moving average filter applied over current X value +/- %g\n",median_ahead_t);
2656+
StatusText->Caption=FString;
2657+
pScientificGraph->fnCentral_moving_average_filter(median_ahead_t,iGraph,filter_callback);
2658+
}
2659+
break;
2660+
case 7: // Kalman filter
2661+
if(median_ahead_t>0.0)
2662+
{rprintf("Trace set to Kalman filter of input Y values with measurement noise standard deviation of %g\n",median_ahead_t);
2663+
StatusText->Caption=FString;
2664+
pScientificGraph->fnKalman_filter(median_ahead_t,iGraph,filter_callback);
2665+
}
2666+
break;
2667+
2668+
case 8:
26212669

26222670
// lin regression y=mx
26232671
StatusText->Caption=FString;
26242672
pScientificGraph->fnLinreg_origin(iGraph,filter_callback);
26252673
break;
2626-
case 7:
2674+
case 9:
26272675
#if 0
26282676
{
26292677
// test code uses general linear least squares fitting , false arguments means y values are not changed
@@ -2656,100 +2704,100 @@ try{
26562704
StatusText->Caption=FString;
26572705
pScientificGraph->fnLinreg(LinLin,iGraph,filter_callback);
26582706
break;
2659-
case 8:
2707+
case 10:
26602708
// lin regression y=mx+c via GMR
26612709
StatusText->Caption=FString;
26622710
pScientificGraph->fnLinreg(LinLin_GMR,iGraph,filter_callback);
26632711
break;
2664-
case 9:
2712+
case 11:
26652713
// minimal max abs error: y=mx+c
26662714
StatusText->Caption=FString;
26672715
pScientificGraph->fnLinreg_abs(false,iGraph,filter_callback);
26682716
break;
2669-
case 10:
2717+
case 12:
26702718
// minimal max abs relative error: y=mx+c
26712719
StatusText->Caption=FString;
26722720
pScientificGraph->fnLinreg_abs(true,iGraph,filter_callback);
26732721
break;
2674-
case 11:
2722+
case 13:
26752723
// log regression
26762724
StatusText->Caption=FString;
26772725
pScientificGraph->fnLinreg(LogLin,iGraph,filter_callback);
26782726
break;
2679-
case 12:
2727+
case 14:
26802728
// exponentail regression
26812729
StatusText->Caption=FString;
26822730
pScientificGraph->fnLinreg(LinLog,iGraph,filter_callback);
26832731
break;
2684-
case 13:
2732+
case 15:
26852733
// powerregression
26862734
StatusText->Caption=FString;
26872735
pScientificGraph->fnLinreg(LogLog,iGraph,filter_callback);
26882736
break;
2689-
case 14:
2737+
case 16:
26902738
// recip regression
26912739
StatusText->Caption=FString;
26922740
pScientificGraph->fnLinreg(RecipLin,iGraph,filter_callback);
26932741
break;
2694-
case 15:
2742+
case 17:
26952743
// lin-recip regression
26962744
StatusText->Caption=FString;
26972745
pScientificGraph->fnLinreg(LinRecip,iGraph,filter_callback);
26982746
break;
2699-
case 16:
2747+
case 18:
27002748
// hyperbolic regression
27012749
StatusText->Caption=FString;
27022750
pScientificGraph->fnLinreg(RecipRecip,iGraph,filter_callback);
27032751
break;
2704-
case 17:
2752+
case 19:
27052753
// sqrt regression y=m*sqrt(x)+c
27062754
StatusText->Caption=FString;
27072755
pScientificGraph->fnLinreg(SqrtLin,iGraph,filter_callback);
27082756
break;
2709-
case 18:
2757+
case 20:
27102758
// nlog2(n) regression y=m*x*log2(x)+c
27112759
StatusText->Caption=FString;
27122760
pScientificGraph->fnLinreg(Nlog2nLin,iGraph,filter_callback);
27132761
break;
2714-
case 19:
2762+
case 21:
27152763
// y=a*x+b*sqrt(x)+c (least squares fit)
27162764
StatusText->Caption=FString;
27172765
pScientificGraph->fnLinreg_3(iGraph,filter_callback);
27182766
break;
27192767

2720-
case 20:
2768+
case 22:
27212769
// y=a+b*sqrt(x)+c*x+d*x^1.5
27222770
StatusText->Caption=FString;
27232771
gen_lin_reg(reg_sqrt,4,true,iGraph);
27242772
break;
2725-
case 21:
2773+
case 23:
27262774
// y=(a+bx)/(1+cx) (least squares fit)
27272775
StatusText->Caption=FString;
27282776
pScientificGraph->fnrat_3(iGraph,filter_callback);
27292777
break;
2730-
case 22:
2778+
case 24:
27312779
// N=5=> y=(a0+a1*x+a2*x^2)/(1+b1*x+b2*x^2)
27322780
StatusText->Caption=FString;
27332781
gen_lin_reg(reg_rat,5,true,iGraph);
27342782
break;
2735-
case 23: // general purpose polynomial fit (least squares using orthogonal polynomials)
2783+
case 25: // general purpose polynomial fit (least squares using orthogonal polynomials)
27362784
StatusText->Caption=FString;
27372785
if(!pScientificGraph->fnPolyreg((unsigned int)poly_order,iGraph,filter_callback))
27382786
{StatusText->Caption="Polynomial fit failed";
27392787
ShowMessage("Warning: Polynomial fit failed - adding original trace to graph");
27402788
}
27412789
break;
2742-
case 24:
2790+
case 26:
27432791
// general purpose polynomial fit in sqrt(x) with user defined order
27442792
StatusText->Caption=FString;
27452793
gen_lin_reg(reg_sqrt,poly_order+1,true,iGraph);
27462794
break;
2747-
case 25:
2795+
case 27:
27482796
// rational fit (poly1/poly2) with user defined order
27492797
StatusText->Caption=FString;
27502798
gen_lin_reg(reg_rat,poly_order+1,true,iGraph);
27512799
break;
2752-
case 26:
2800+
case 28:
27532801
// derivative
27542802
StatusText->Caption=FString;
27552803
#if 1
@@ -2758,41 +2806,41 @@ try{
27582806
deriv_trace(iGraph); // this was used till csvgraph 3v8
27592807
#endif
27602808
break;
2761-
case 27:
2809+
case 29:
27622810
// 2nd derivative
27632811
StatusText->Caption=FString;
27642812
pScientificGraph->deriv2_filter((unsigned int)poly_order,iGraph); // used fom 3v9 - uses Savitzky Golay filtered derivative of specfied order (1,2,4 are very efficient)
27652813
break;
2766-
case 28:
2814+
case 30:
27672815
// integral
27682816
StatusText->Caption=FString;
27692817
integral_trace(iGraph);
27702818
break;
2771-
case 29: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
2819+
case 31: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
27722820
// fft return ||
27732821
StatusText->Caption=FString;
27742822
if(!pScientificGraph->fnFFT(false,false,iGraph,filter_callback))
27752823
{StatusText->Caption="FFT failed";
27762824
ShowMessage("Warning: FFT failed - adding original trace to graph");
27772825
}
27782826
break;
2779-
case 30: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
2827+
case 32: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
27802828
// fft return dBV
27812829
StatusText->Caption=FString;
27822830
if(!pScientificGraph->fnFFT(true,false,iGraph,filter_callback))
27832831
{StatusText->Caption="FFT failed";
27842832
ShowMessage("Warning: FFT failed - adding original trace to graph");
27852833
}
27862834
break;
2787-
case 31: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
2835+
case 33: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
27882836
// fft with Hanning window, return ||
27892837
StatusText->Caption=FString;
27902838
if(!pScientificGraph->fnFFT(false,true,iGraph,filter_callback))
27912839
{StatusText->Caption="FFT failed";
27922840
ShowMessage("Warning: FFT failed - adding original trace to graph");
27932841
}
27942842
break;
2795-
case 32: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
2843+
case 34: // bool TScientificGraph::fnFFT(bool dBV_result,bool hanning,int iGraphNumberF, void (*callback)(unsigned int cnt,unsigned int maxcnt))
27962844
// fft with Hanning window return dBV
27972845
StatusText->Caption=FString;
27982846
if(!pScientificGraph->fnFFT(true,true,iGraph,filter_callback))
@@ -3187,7 +3235,7 @@ void proces_open_filename(char *fn) // open filename - just to peek at header ro
31873235
if(col_names!=NULL) free(col_names);
31883236
col_names=strdup(csv_line); // copy input line as we want to save column header strings
31893237
if(col_names==NULL)
3190-
{ShowMessage("Error no RAM): cannot read headers from file "+filename);
3238+
{ShowMessage("Error (no RAM): cannot read headers from file "+filename);
31913239
fclose(fin);
31923240
filename="";
31933241
Form1->pPlotWindow->StatusText->Caption="No filename set";

0 commit comments

Comments
 (0)