Posts

Showing posts from January, 2011

exponential filtering algorithm

Here I'll briefly describe the algorithm for filtering data with an exponential convolution. First, I'll define the following: n: an index for the point in the series. The first point is n = 1, the next n = 2, the next n = 3, etc. y n : the series of unfiltered points data values, for example altitudes. t n : the series of unfiltered points time values. Δt: the time separation of the data for uniformly spaced time points. Δt n : when time is not necessarily uniformly spaced, Δt n ‒Δt n‒1 . τ: the smoothing time constant. u n : normalized time values, t n / τ. Δu: Δt / τ (for uniformly spaced time points). Δu n : Δt n / τ. z n : the series of smoothed time points. So using these definitions, I want to convert from the unsmoothed series y n to the smoothed series z n . First, if an initial point is encountered, we start things rolling with the following: z 0 = y 0 Then assuming uniformly spaced data, a naive approach is the following: z n = z n‒1 exp

smoothing Garmin altitude profiles for power estimation

Image
Recall that I was contemplating using power in lieu of speed for determining whether a Garmin FIT file contains motorized segments. The speed criterion was fairly simple: if the data indicate the user got 500 meters ahead of a 14 m/sec pace over any segment, that segment was judged to be a portion of motorized travel. The entire region between load/unload opportunities was thus tagged and pruned. While cyclists may be able to sustain a high speed for short periods, by requiring they get a certain distance ahead of a threshold pace requires they exceed that pace for an extended time, or vastly exceed it for a shorter time. Example of critical power model applied to more realistic curve In the power-time domain,the critical power model accounts for that ability to sustain high intensity for short times, but only a lower intensity for longer times. The critical power model is very similar to the criteria used for speed: it says a rider, given sufficient time, can do a given amoun

Bike Nüt closed

Image
I heard today that Bike Nüt closed a few weeks ago. Sad news. I always enjoyed stopping there. It was a good destination for a long run, or a good place just to stop and say hello and see what new bike jewelery Huseyin, the owner, had inside his glass case. Plenty of shops sell high-end stuff to build up a bike to the highest standards of the boutique fashion weenies. The number of shops selling custom Seven, Parlee, or Serotta cycles in or near San Francisco is nothing short of staggering. Bike Nüt, however, didn't sell custom frames. Huseyin's philosophy was that frames are commodity: that the Taiwanese factories have produced a high, uniform standard, so save your dollars there. Instead you want to spend your money on good wheels and quality parts. On the frame area, until a year ago he was buying frames from Giant, then sending them out to have the paint stripped and clearcoat applied to reduce them to the raw, black bare carbon. In a way this seems silly: Gi

Garmin FIT activity splitter to eliminate large time gaps

I've gotten useful code on my third project for processing FIT files using Kiyokazu Suto 's Garmin::FIT package for Perl. First, I described fit_to_cols , which extracted selected data from FIT files and formatted it in a space-delimited file. Next I described fit_filter_motor_segments which attempted to identify segments where the Garmin was accidently left running in a fast car or train. That project's still being refined, as I described last post. Here is perhaps the most useful of the three, fit_split_on_gaps , which finds gaps of some specified minimum duration (default 8 hours, or otherwise specified with the -tgap option) and splits the FIT data into multiple sub-files at any gaps found which meet or exceed this threshold. This and the other codes can be found here, on Google Docs . It's fairly common in my experience to forget to "reset" my Garmin between activities, despite having set it to warn me at the start of a ride if I have not. Gol

adding altitude to motorized segment identification?

Image
A friend of mine, Brian, took his Garmin with him in his car, drove on local roads, did a run, got back in his car, and drove back home. He was careful never to drive much over 30 mph the whole time. He then loaded the data into my motorized FIT filter and..... nothing. It didn't identify any of the segments as motorized. The reason for this is the criterion I used, that the units must record progress which is at least 500 meters ahead of a 14 kph pace for some time interval, is designed to avoid tagging segments where a rider was descending a relatively fast mountain descent. Descending on a bike is faster than driving on local streets, so it would be relatively hopeless to expect, using crude speed measures only, that I'd be able to pick up local driving as suspicious. I was discussing this with Bill, another friend, when the idea came up of throwing altitude into the mix. Now this is hardly a new idea: it'd already been proposed on Strava forums that calculated

Garmin FIT motorized segment filter in Perl

Finally, squeezing in work in my train commute between San Francisco and Mountain View (which unfortunately hasn't been a bike commute as much as I'd like due to the pressures of two big projects at work, but digression opportunities ate limited in the middle of a sentence), I've managed to finish a working draft of my "motorized segment filter" in Perl. Like the fit_to_cols code I described recently, this code uses Kiyokazu Suto 's Garmin::FIT package for Perl. Unlike fit_to_cols, this one needs to be able to write as well as read FIT data. The Garmin:FIT perl module allows this, but to figure out how you really need to dig into the provided example, fitsed . Fitsed is uncommented and does a lot more than just read and write FIT files: it also has a parser for selecting and/or changing fields in records. All good stuff, but a simple equivalent of hello.c , an example which minimally demonstrates writing FIT data, would have been useful. But I worked i

a Perl Garmin-FIT to space-delimited table converter

I've been working on a code to filter motorized segments from FIT files, using Kiyokazu Suto 's Garmin::FIT package for Perl. However, to help with that work, I needed a way to quickly plot the data in a FIT file. So I wrote a script, fit_to_csv , to create a readable table from FIT data. The code produces a space-delimited file with each column describing a field either contained in the FIT file or derived from data in one or more fields of the FIT file. I have a series of scripts for handling such files and generating plots from them, or they can be trivially loaded into a spreadsheet like oocalc or even Excel , or almost any other plotting package, using the "CSV" format. When importing the CSV, make sure spaces and not commas are selected as the delimiter. The code's available here . On Linux, simply save the file, make sure it has executable permission, then run it in the standard Linux fashion. On Windows, well, you're on your own. I avoid Wi

Thomas Novikoff

Image
Thomas during week 3 of the 2010 Low-Key Hillclimbs (Judy Colwell photo) I was really shattered, yet not surprised, to read of Thomas Novikoff's death . Dead from cancer at only 29. I'd last seen Thomas on Mount Hamilton at the final Low-Key Hillclimb . He was looking thin and pale. He told me, in a forced-cheery voice which seemed to hide a deep depression, even fear, that his cancer was back. It was in his liver, he said. The Whipple procedure he'd received the preceding November hadn't worked as hoped. He'd wanted to climb, but instead was going to volunteer. The stomach pain was too great, he told me. Every time he rode hard his breathing caused too much pain. I was shocked. What do I say to that? Thomas had been riding the Low-Keys since 2007, first with Team Cambio, and later Webcor/Alto Velo. I was to later learn he'd finished third in the Race to the Sun , up mighty Haleakela, just 15 months before, and finished a very close second in

tagging motorized segments in FIT files: results

Image
Last time I described my proposed algorithm for detecting ride segments. The only detail I was missing was how I find where to find the transitions between motorized and nonmotorized transport. I'd set a threshold speed of 14 meters per second, but obviously one doesn't ride a bike up to that speed and then hop onto a passing vehicle. So instead, starting from where the speed crosses the 14 m/sec threshold, I search for the first interval of at least 10 seconds during which there are either no points (GPS off or out of signal range, for example) or else the speed fails to exceed a 2 m/sec threshold. This seems to work fairly well, although it can be tricky, as it may not take long to get off a train after it's stopped. Car-bike transitions probably tend to be slower. I use the time at the mid-point of these points as the threshold time for motorized versus non-motorized. I then scanned all stored activities for Oct, Nov, and Dec 2010. I found five which showed clear

finding motorized segments in FIT files: describe algorithm

Well, I've made some progress on the Garmin::FIT module, and so am coding my "motorized segment" filter. My algorithm isn't tested yet, but here's what I have in mind, slightly simplified: Find all segments where the speed is no less than some threshold for driving, for example 14 m/sec. This step is for computational efficiency only, to assist with the following step. Using this list of "fast" segments, find segments in which the Garmin reported progress at least some threshold distance ahead of the threshold for driving. For example, the make a list of all segments where the distance covered was at least 500 meters greater than the distance which would be covered @ 14 mps for the same time period. These segments may include points which are less than the speed threshold, and will exclude some points which exceed the speed threshold, since bicycles are capable of going fast when descending, but are generally unable to go sufficiently faster tha