tag:blogger.com,1999:blog-16498795232764604182018-08-28T21:35:38.103+02:00Delphi BitsTips and tricks for Delphi/RadStudio developers. Hans Lohningernoreply@blogger.comBlogger11125tag:blogger.com,1999:blog-1649879523276460418.post-60266165713936500342013-10-21T10:41:00.000+02:002013-10-21T10:43:10.448+02:00Broken help fileA few weeks ago I received a mail of a customer who complained of the help file of the <a href="http://www.lohninger.com/sdlindex.html">SDL Suite</a> which did not correctly display on his new computer (while the same help file on his old computer worked as expected). Several mails and a few Google searches later it turned out that this is due to a "security feature" of newer versions of Windows. <p>Microsoft discovered in about 2005 a vulnerability of the help engine which allowed to execute remote code within a corrupted chm file (compiled help file). The security hole was "fixed" by an update which simply blocks the display of html pages in the help viewer and displays the misleading message "Navigation to the Website was canceled". <div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-VcmeL55B-sQ/UmTkvrSyoqI/AAAAAAAAAOA/sL71C319Y0I/s1600/chm_blocked.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-VcmeL55B-sQ/UmTkvrSyoqI/AAAAAAAAAOA/sL71C319Y0I/s320/chm_blocked.png" /></a></div> Well, thank you Microsoft, this message is really of big help. Thousands (if not millions) of Windows user will eventually bump into this problem without having a clue of the true nature of the problem.... <p>Those who face the same problem maybe want to have a look at the <a href="http://www.sdlsuite.com/technotes/tn0041.html">SDL TechNotes</a> where I published a simple solution to it.Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com2tag:blogger.com,1999:blog-1649879523276460418.post-84105525351893559762013-09-03T09:41:00.000+02:002013-09-03T09:41:51.717+02:00Smoothing DataYesterday, someone contacted me and asked me how to quickly smooth a bunch of data measured over time. While I did this many times in various projects before, I discovered just now, that I never published a sample program which shows how to perform smoothing of data using the <a href="http://www.lohninger.com/sdlindex.html">SDL Component Suite</a>. So here we are... <p>This small program allows to create a time series simulating the measured data. The data may be "poisoned" by arbitrary levels of (Gaussian) noise and up to 10 spikes. The resulting data series is typical for measured data, which usually contains some amount of noise, and occasionally one or two spikes (e.g. when a refrigerator in the vicinity of the measurement device switches off). <p>Experimenting with this little program quickly shows that normally distributed noise can be easily reduced by most of the smoothing algorithms, provided that the window width of the smoothing algorithm is large enough to cover a sufficient number of random fluctuations, and, of course, small enough not to interfere with low-frequency signal components. <p>A big problem to all integrating filters are spikes. We know from system theory that the response of a filter to a dirac impulse resembles its own transfer function. Thus a <a href="http://www.lohninger.com/helpcsuite/movingaveragesmooth_math2.htm">moving average filter</a> will generate rectangular shapes, a <a href="http://www.lohninger.com/helpcsuite/polynomialsmooth.htm">polynomial filter</a> will generate parabolic artefacts. A very good solution for getting rid of the spikes (at least if they have a width of only a few data points) is to use a <a href="http://www.lohninger.com/helpcsuite/movingmediansmooth_math2.htm">moving median filter</a>. <p><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-RchrUL66aQw/UiTX9sVQuoI/AAAAAAAAANg/o7OuPaNhi9c/s1600/smooothing.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-RchrUL66aQw/UiTX9sVQuoI/AAAAAAAAANg/o7OuPaNhi9c/s320/smooothing.png" /></a></div><p>The images above show the effect of smoothing a noisy signal containing a a spike. <p>If you want to experiment with these kinds of filters yourself, you can download the program <a href="http://www.sdlsuite.com/download/smoothing.zip">here</a>. The archive contains the executable as well as the Delphi XE4 sources, so you can immediately try out the various options. For adjusting the code to your own needs you have to install the SDL Component Suite (the <a href="http://www.lohninger.com/sdlcsuite_le.html">Light Edition</a> is free of charge). <p>Last but not least two remarks: you may be interested in experimenting with <a href="http://www.lohninger.com/helpcsuite/penalizedcubicspline_math2.htm">penalized splines</a>, as well. They are basically not intended to automatically filter data, however, the resulting smoothed curves are free of noise and thus nice to look at. A sample program to experiment with penalized splines may be downloaded from the <a href="http://www.vias.org/simulations/simusoft_spline.html">VIAS Web site</a>. Another way (and a quite efficient one) to perform smoothing is to use fast Fourier transform to remove unwanted parts of the signal. Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com1tag:blogger.com,1999:blog-1649879523276460418.post-66493393715222855222013-06-10T11:29:00.000+02:002013-06-10T11:34:15.225+02:00Mahalanobis DistanceMany of us (especially those who do a lot of calculations involving statistical data) have to calculate distances in arbitrary spaces. While this is quite common in everyday life (think, for example, of the calculation of a room diagonal) it may become quite complicated when doing data analysis. <p>In data analysis we have to consider two special aspects: (1) the space in which a distance is to be calculated is normally not two- or three-dimensional, but in many cases of much higher dimensionality; (2) the Euclidean distance which we are used to use in everyday life is exceedingly inappropriate when the variables of the space are correlated (which is almost always the case in practical applications). <p>For short, the higher the correlation of the variables describing a p-dimensional space, the more misleading a calculated Euclidean distance will be. As a consequence using Euclidean distances in data analysis may and will lead to wrong results if variables are correlated. <p>Fortunately, there is a simple solution to this problem: the "Mahalanobis Distance" (MD). The MD allows for the correlation among variables and returns a distance which is undistorted even for strongly correlated variables. While the mathematics behind the calculation of the MD is quite demanding, the application is simple if you take the newly implemented function <a href="http://www.lohninger.com/helpcsuite/mahalanobisdistance_math2.htm">MahalanobisDistance</a> of <a href="http://www.lohninger.com/math2.html">MathPack </a> (a package which is part of the <a href="http://www.lohninger.com/sdlindex.html">SDL Component Suite</a>). <p><H4>Example</h4>Let's assume we want to calculate the MD between two points of a q-dimensional data space. The positions of the two points for which the MD has to be calculated are defined by two q-dimensional vectors p1 and p2. In order to specify the dependencies between the variables the user has to supply the inverse of the covariance matrix of the data set (the inverse can be easily obtained by calling the function <a href="http://www.lohninger.com/helpcsuite/invert_matrix.htm">Invert</a>). <p>So, all in all, the MD can be calculated by the following statements (assuming that the data are stored in the <a href="http://www.lohninger.com/helpcsuite/using_datatbl.htm">data table</a> DataMat): <pre><br /><br />uses<br /> SDL_Vector, SDL_Matrix, SDL_math2;<br /><br />...<br /><br />var<br /> p1, p2 : TVector;<br /> CovMat : TMatrix;<br /> d : double;<br /><br />...<br /><br />DataMat.CalcCovar (CovMat,1,DataMat.NrOfColumns, <br /> 1, DataMat.NrOfRows, 1);<br />if not CovMat.Invert then<br /> begin<br /> CovMat.Fill(0);<br /> CovMat[1,1] := 1;<br /> end;<br />d := MahalanobisDistance (p1,p2,CovMat);<br /></pre> Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com1tag:blogger.com,1999:blog-1649879523276460418.post-90526932411598962392013-03-13T05:43:00.000+01:002013-03-13T07:12:35.585+01:00Covariance of Samples and PopulationsRecently I was asked by a customer why the covariance values obtained by the function <a href="http://www.lohninger.com/helpcsuite/calccovar.htm">CalcCovar</a> of the <a href="http://www.lohninger.com/sdlindex.html">SDL Suite</a> deviate from the covariance values calculated by the function COVAR of Microsofts Excel<sup>(tm)</sup>. <p>The explanation for the difference is quite simple: Excel assumes that you are dealing with <a href="http://www.statistics4u.com/fundstat_eng/cc_population.html">populations</a>, while the SDL Suite assumes that you are working with <a href="http://www.statistics4u.com/fundstat_eng/cc_population.html">samples </a>(which is much more realistic). <p>In order to convert the covariance of a population to the covariance of a sample you have to multiply the covariance by a factor of n/(n-1), with n being the number of values used for the calculation. <p>Let me add a personal remark: I never use the statistics package of Excel as it has many built-in flaws and peculiarities which guide the inexperienced or unwary user into the wrong direction. <p>To be specific: in many cases Excel assumes that a set of, for example, five measurements describes a population and not a sample - an assumption which is rather daring and will mislead millions of world-wide users of this package. However, Microsoft is working to improve on these issues (see the <a href="http://blogs.office.com/b/microsoft-excel/archive/2009/09/10/function-improvements-in-excel-2010.aspx">Office blog</a>).... Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com0tag:blogger.com,1999:blog-1649879523276460418.post-86026954412283437192013-03-02T07:30:00.000+01:002013-03-02T07:30:09.279+01:00Fast Calculation of QuantilesDid you ever face the problem to calculate a particular <a href="http://en.wikipedia.org/wiki/Quantile">quantile</a> of a large collection of numbers (about 1 million values) in a minimum amount of time? I came across this problem when adjusting an image processing system to use a better color assignment in false color images. These images were hampered by a few extreme values which caused an unwanted shift of the color scale. <p>So the idea to improve this situation was quite simple: change the color assignment from a min/max-based algorithm to a quantile-based algorithm, using the 0.01-quantile and the 0.99 quantile as the reference points for the color scale. This would allow for a maximum number of 1% of outliers on either side of the data distribution. <p>The only problem was that the calculation of the quantiles from a sorted list of values was too slow. Although the sorting was done by a fast sorting algorithm it took about 200 milli-seconds (ms) to obtain a quantile (the images contained about 800000 data points). <p>So I looked for a faster algorithm, and decided to implement the <a href="http://en.wikipedia.org/wiki/Selection_algorithm">QuickSelect algorithm</a> in Delphi. In order to get an idea about the speed improvement I performed a few measurements which are summarized in the table below. The calculation times for 0.01-, 0.5-, and 0.99-quantiles have been measured for various sizes of data sets (between 10000 and 5 million elements) and compared to the calculation time based on fully sorting the data sets (using <a href="http://www.lohninger.com/helpcsuite/sortarray.htm">CompSort</a>, a modified bubble sort which is comparable in speed to the well-known quick sort algorithm). Further, the increase of time was measured when passing the data on the stack and not by reference (in order to prevent changing the data array): <pre><br /> Full 0.01 Qu.<br /> 0.01 Qu. 0.5 Qu. 0.99 Qu. Sort (Stack)<br /> NData [ms] [ms] [ms] [ms] [ms]<br />-------------------------------------------------------<br /> 10000 0.12 0.20 0.14 1.78 0.16<br /> 20000 0.28 0.42 0.26 3.47 0.30<br /> 50000 0.65 1.03 0.64 10.5 0.77<br /> 100000 1.39 2.04 1.25 20.8 1.55<br /> 200000 2.51 4.08 2.57 43.0 3.01<br /> 500000 6.3 10.7 6.41 110 8.00<br />1000000 12.0 22.9 13.3 226 16.2<br />2000000 25.4 40.9 26.1 470 33.0<br />5000000 63.0 107.0 68.9 1167 82.0<br /></pre> So, all in all, the QuickSelect algorithm dramatically speeds up the calculation time, its properties can be summarized as follows: <ul><li>The QuickSelect algorithm is about a factor of 18.5(!) faster than the calculation based on sorting the data <li>The calculation time scales linearly with the size of the data set <li>The calculation time depends on the quantile: 0.01-quantiles and 0.99-quantiles are equally fast, 0.50-quantiles (= medians) take about 70% longer to calculate <li>Changing the passing of the data array from call by reference to copying it on the stack increases calculation time by 30%. However, while this approach has the advantage of not changing the data array, it eventually may (and will) lead to a stack overflow when the size of the data set grows and cannot be restricted. </ul> .... needless to say that the QuickSelect algorithm will become part of the next release of <a href="http://www.lohninger.com/mathpack.html">MathPack</a> (which is a package of the <a href="http://www.lohninger.com/sdlindex.html">SDL Component Suite</a>). Following is the <a href="http://www.embarcadero.com/products/delphi">Delphi</a> code of the QuickSelect algorithm: <pre><br />(********************************************************)<br />function QuickSelect (var data: array of double; <br /> k, First, Last: integer): double;<br />(********************************************************<br /> AUTHOR: Hans Lohninger (www.lohninger.com)<br /> <br /> ENTRY: data .......... data array<br /> k ............. k-th order statistic <br /> First, Last ... range of data to be processed<br /><br /> EXIT: function returns the value of the k-th smallest<br /> element of the data array<br /><br /> REMARK: you are allowed to use this code for free in your <br /> own programs provided that do not change the <br /> reference to the author (including the Web URL)<br /> ********************************************************)<br /><br /><br />function Partition (First, Last, ixPiv: integer): integer;<br />{--------------------------------------------------------}<br /><br />var<br /> i : integer;<br /> iy : integer;<br /> pivot : double;<br /> adouble: double;<br /><br />begin<br />pivot := data[ixPiv];<br />data[ixPiv] := data[First];<br />data[First] := pivot;<br />iy := First + 1;<br />while (iy < Last) and (data[iy] < pivot) do<br /> inc (iy);<br />for i:=iy+1 to Last do<br /> if (data[i] <= pivot) then<br /> begin<br /> adouble := data[i];<br /> data[i] := data[iy];<br /> data[iy] := adouble;<br /> inc(iy);<br /> end;<br />result := iy - 1;<br />data[First] := data[result];<br />data[result] := pivot;<br />result := result;<br />end;<br /><br /><br />var<br /> stop : boolean;<br /> ixSplit: integer;<br /> ixPiv : integer;<br /><br />begin<br />stop := false;<br />while not stop do<br /> begin<br /> ixPiv := (First+Last) div 2;<br /> ixSplit := partition(First, Last, ixPiv);<br /> if k < ixSplit<br /> then Last := ixSplit<br /> else<br /> if k > ixSplit<br /> then First := ixSplit + 1<br /> else begin<br /> stop := true;<br /> result := data[k];<br /> end;<br /> end;<br />end;<br /></pre> Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com0tag:blogger.com,1999:blog-1649879523276460418.post-18488981557077596522013-02-21T10:58:00.000+01:002013-02-21T10:58:04.240+01:00Rotatable 3D surface plotYesterday a customer asked me how to create a quick and dirty solution for displaying a rotatable three dimensional surface (using <a href="http://www.lohninger.com/chartpack.html">ChartPack</a>). Here's the solution to it (for sake of clarity I use predefined data stored in the 12 by 14 array "Data"): <ol><li> Put the TPlot3D component on a form <li> In the OnShow event add the following code: <pre><br /> // suppress any redraw during the<br /> // construction of the plot<br />P3d.SuppressPaint := true; <br /> // adjust the data grid of the component <br />P3d.GridMat.Resize(14,12); <br /> // fill the data grid<br />for i:=1 to 14 do<br /> for j:=1 to 12 do<br /> P3d.GridMat[i,j] := Data[i,j]; <br /> // set the scales of the axes<br />P3D.SetRange(1,14,1,12,0,35); <br /> // this triggers a repaint<br />P3d.SuppressPaint := false; <br /></pre> <li> In the object inspector set the color model ("ColorCodingMode") to "cmThreeColors", the range of colors to a range of 0 to 35 (properties "ColorScaleLow" and "ColorScaleHigh"), and of course, the pivot colors ("ColorLow", "ColorMid", and "ColorHigh") according to your requirements. </ol> As a small refinement I added a scrollbar which allows to adjust the property "ColorScaleHigh". Following is a screen shot of the mini demo: <p><a href="http://3.bp.blogspot.com/-RnOznW1xLZM/USXtQ6-mUDI/AAAAAAAAAME/E5BcGvzt8YM/s1600/screenshot.png" imageanchor="1" ><img border="0" src="http://3.bp.blogspot.com/-RnOznW1xLZM/USXtQ6-mUDI/AAAAAAAAAME/E5BcGvzt8YM/s320/screenshot.png" /></a><p>You can download the sample program written for Delphi XE3 from <a href="http://www.sdlsuite.com/delphibits/download/x3p1004_plot3d.zip">here</a>. The ZIP file contains the sources and the corresponding executable for Windows. In order to rotate the 3D plot simply click into the chart and drag the mouse holding the left mouse button down. Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com2tag:blogger.com,1999:blog-1649879523276460418.post-16102883807619099812013-01-14T07:55:00.000+01:002013-01-14T08:02:41.537+01:00Reentrancy of eventsNot very long ago, I encountered a weird situation which took lots of time to resolve (yes, call me stupid): a measurement device delivered data to my program at irregular intervals by triggering an event which took the new value, added it to a FIFO structure and redrew the corresponding graph. <p>This worked nice, however, every now and then parts of the drawing disappeared (I used <a href="http://www.lohninger.com/rchart.html">RChart </a> for displaying the graph). The problem was, of course, that the glitch occurred only infrequently, so debugging was in fact almost impossible (how can you the detect the condition of not drawing parts of a graph?). <p>In such situations, I did what I call "debugging by meditation" (;-): I slept over it, hoping that my brain finds a solution while being asleep..... <p>And the solution was simple enough: the event to redraw the graph was called a second time before the previous call had been finished (re-entrant call), clearing parts of the graph. So I simply created the following program structure to block re-entrant calls of the drawing routine: <blockquote><pre><br /><br />var<br /> IsProcessing: boolean; // (private variable of the form)<br /><br />...<br />...<br />...<br /><br />procedure OnDataTrigger (Sender: TObject; newdat: double);<br /><br />begin<br />.... // collect the data (not shown here)<br /><br />if not Processing then<br /> begin<br /> IsProcessing := true;<br /><br /> .... // display the data<br /><br /> IsProcessing := false;<br /> end;<br />end;<br /></pre></blockquote> ... and the glitches were gone! <p>A final remark: in my case it was a little bit more complicated, because of additional restrictions of the measurement device. So I used the event to collect the data in an array and called the drawing routine only when enough data were available... <p>Yes, and the very last remark: do not forget to initialize the "IsProcessing" variable to FALSE. Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com0tag:blogger.com,1999:blog-1649879523276460418.post-69062050984817450372013-01-10T12:15:00.000+01:002013-01-10T12:15:39.423+01:00Lifetime of Older Delphi VersionsDid you ever wonder how long it takes that most of the users of an older Delphi version switch to the newer one? <p>Well, this question could be answered simply by looking at the sales figures of <a href="http://www.embarcadero.com">Embarcadero</a>; however I doubt that Embarcadero will make the sales figures available to the public. An idea to get some insight to this question would be to exploit the search statistics of Google. These numbers are publicly available (at least the relative numbers) and certainly provide an indication of how many people are (still) working with a particular version of Delphi. <p>Here are the results of this approach (the data points are weekly averages scaled to a maximum of 100%): <div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-QDswsFgZSBc/UO6bd3aMwNI/AAAAAAAAALg/4_fhLTcAAqc/s1600/delphi.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="191" width="320" src="http://4.bp.blogspot.com/-QDswsFgZSBc/UO6bd3aMwNI/AAAAAAAAALg/4_fhLTcAAqc/s320/delphi.png" /></a></div> So what I did, was to visit <a href="http://www.google.com/trends/">Google Trends</a>, enter the following keywords, and download the data provided by Google. Search keywords: "Delphi 2009", "Delphi 2010", "Delphi XE", "Delphi XE2", and "Delphi XE3". <p>The results clearly show that the decay of a particular search keyword immediately starts after the launch of a new release. The interest in the old release drops within a few months to half of the initial interest. Only Delphi 2010 needed almost a year to drop to 50% - which may be an indication that the move to XE was judged excessively cautious by the users.Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com2tag:blogger.com,1999:blog-1649879523276460418.post-85849564584360709492013-01-09T06:36:00.001+01:002013-01-09T06:36:36.761+01:00Regular Expressions in DelphiIf you ever have a need for regular expressions in your Delphi programs, here's a Web site (not to say <b>the</b> WebSite) which provides all necessary explanations (including code to download): <p align=center><a href="http://www.regular-expressions.info/delphi.html">regular-expressions.info</a><p>Delphi/RadStudio XE or higher supports PCRE. PCRE has been developed by Philip Hazel, University of Cambridge, and is a library of regex functions whose syntax and semantics are close to those of the Perl 5 language. Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com0tag:blogger.com,1999:blog-1649879523276460418.post-80216269326800555532013-01-08T08:09:00.002+01:002013-01-08T08:17:28.683+01:00When was Easter Sunday in 1953?Did you ever try to implement a calendar? If so, you already know that this is one of the most intricate tasks a programmer can come upon (;-))). More on the background of that can be found, for example, in <a href="http://en.wikipedia.org/wiki/Computus">Wikipedia</a>.<br /><br />To make things short, here's the Delphi code of the formula developed by Carl Gauss, including the correction of Lichtenberg (the code snippet is taken from the <a href="http://www.lohninger.com/minical.html">calendar component</a> of the <a href="http://www.lohninger.com/sdlindex.html">SDL Component Suite</a>):<br /> <pre><blockquote><br />procedure CalcEasterSunday<br /> (Year: integer; var Day, Mon: integer);<br />(****************************************************<br /> ENTRY: Year ....... year to be calculated<br /> EXIT: Day, Mon ... day and month of Easter Sunday<br /> ****************************************************)<br /><br />var<br /> kx, mk, sk : integer;<br /> ax : integer;<br /> dam : integer;<br /> rda : integer;<br /> sz : integer;<br /> og,oe : integer;<br /><br />begin<br />kx := Year div 100;<br />mk := 15 + (3*kx + 3) div 4 - (8*kx + 13) div 25;<br />sk := 2 - (3*kx + 3) div 4;<br />ax := Year mod 19;<br />dam := (19*ax + mk) mod 30;<br />rda := (dam + ax div 11) div 29;<br />og := 21 + dam - rda;<br />sz := 7 - (Year + Year div 4 + sk) mod 7;<br />oe := 7 - (og - sz) mod 7;<br />Mon := 3;<br />Day := og + oe;<br />if Day > 31 then<br /> begin<br /> Mon := 4;<br /> Day := Day-31;<br /> end;<br />end;<br /></blockquote><br /></pre>The date is calculated for the Gregorian calendar (which is the most widely used one in the Western hemisphere). As you can easily calculate, Easter Sunday in 1953 was the 5th of April.... Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com0tag:blogger.com,1999:blog-1649879523276460418.post-4345178893276274762013-01-06T07:14:00.000+01:002013-01-06T10:09:14.765+01:00Selected Items of a TTreeView ComponentDid you ever try to programmatically select a particular node in a TTreeView component? I thought this should be simple enough. Assuming that the particular node to be selected is given by <b>Items[i]</b> we could simply write the following statement: <blockquote>TreeView1.Items[i].Selected := true;</blockquote> Well that works; if we check whether the node is selected we get back a TRUE value. However the selection is not indicated visually, the display of the TTreeView component clears all previous selections, and that's all.... no indication of the new selection. <p>After hours of tedious experiments I finally found the solution: in order to make the component indicate the selection, you have to set the focus to the component (even if it already had the focus before selecting the node). So the following two-liner will do the job: <blockquote>TreeView1.Items[i].Selected := true;<br>TreeView1.SetFocus; </blockquote> Hans Lohningerhttps://plus.google.com/100224874000172303542noreply@blogger.com2