Add SPIFF correction#799
Conversation
|
@nkeim |
|
Hi! Thanks for this PR!! I’ve seen SPIFF before and I’m glad it could be part of trackpy.
The more “active” maintainers are also faculty, so our attention to trackpy (for non-urgent matters) is somewhat seasonal. I can’t give you a definite timeline, but obviously our activity tends to pick up in late Spring/early summer. We’ll do our best to take a look before then!
Nathan
On Feb 3, 2026, at 2:17 PM, Abel Putnoki ***@***.***> wrote:
[https://avatars.githubusercontent.com/u/29568469?s=20&v=4]putnokiabel left a comment (soft-matter/trackpy#799)<#799 (comment)>
@nkeim<https://github.com/nkeim>
Hi, I'm new here!
Is there a process for contributing to trackpy (other than just submitting a PR and waiting)?
—
Reply to this email directly, view it on GitHub<#799 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AAWY23ISYEZVWVCFIDEAVKL4KDX4HAVCNFSM6AAAAACS6XKRE6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTQNBTGE3TKOBZGE>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
nkeim
left a comment
There was a problem hiding this comment.
Looks great! See comments.
I'm open to adding a spiff option to locate and batch. We'd have to be deliberate about the behavior when there aren't enough features.
- Perhaps the values could be True, False, and None (or 'auto')? Only
Truewould raise a warning if there were too few features. - I'd prefer to leave it turned off by default in the next trackpy release. We can add it to the walkthrough to let people experiment with it. But you're right that making it the default in a later release could make sense — the performance penalty is small, and it's (currently) hard to imagine cases where the correction would be unwelcome.
| (as opposed to applying this function for each individual frame). | ||
| If f contains less than 100 features, f is returned as-is, due to lack of data. | ||
| """ | ||
| if len(f) < 100: |
There was a problem hiding this comment.
It's not based on data or experiments, just intuition. I extracted it to a variable and commented on this to make the assumption explicit (and invite further optimization). I also reduced the requirement from 100 to 50.
My reasoning behind the number is:
- 50-100+ features seems clearly enough to make a decent correction (at least to an extent that the result is better than without a correction).
- 5 features seems clearly not enough, to an extent that applying the correction might make the results worse.
- 10-20 features would probably, on average, make results slightly better, but might make results worse in a few cases
- Prefer going for a conservative requirement (higher minimum number of features) so that we know we practically never make the results worse by applying a correction
To optimize this even further, instead of optimizing the minimum number of features, I'd sooner go for something more elaborate, like:
Not just considering the number of features, but also the distribution of the sub-pixel values across features. That way, we could lower the minimum number of features (e.g. from 50 to 10), if some basic assumptions are met, for example:
- the subpixel values are somewhat symmetric across the center of the pixel.
- There is some minimum spread (or minimum standard deviation) to the subpixel values, e.g. if we only have 10 features, and some of the subpixel values are very close to each other or even equal, we are perhaps better off not doing the correction.
I'd prefer to keep further optimization of the minimum feature requirement outside the scope of this PR (and go for a conservative limit for now) to see how well the correction actually works for the users of trackpy (across different usecases), then optimize the feature requirement further in a future PR. I could create separate Github issues for further optimizing the SPIFF correction.
|
I thought of one reason not to make SPIFF automatic: it can mask a poor choice of locate parameters. SPIFF works best when the corrections are small—and it does not correct other data like mass and eccentricity. At least until we think of something better, it's probably best to let users decide when the raw coordinates are good enough, and then apply the correction before linking—and after applying cuts on eccentricity, etc. to remove spurious particles. |
|
Thank you @nkeim for the review! |
| ``'auto'``, the correction is applied silently when there are | ||
| enough features, and skipped otherwise. Pooling features across | ||
| many frames is the recommended way to use SPIFF. Note that this | ||
| argument is not compatible with ``output``. |
There was a problem hiding this comment.
Good catch. But, if output is enabled, why not apply spiff to individual frames? As an example, my group typically uses streaming not because there are too many frames, but because each frame has O(10^5) features.
There was a problem hiding this comment.
Good point! I updated this to apply spiff to individual frames when output is enabled.
Future work:
We could split apply_spiff_correction into two methods, one of which creates the SPIFF function based on the data, and the other actually applies the SPIFF function to data.
This way we could apply SPIFF frame-by-frame but update the SPIFF function with additional data every frame so that the correction gets more and more accurate as there as more frames.
|
@putnokiabel I just merged #803 so that the CI tests pass. If you can, please rebase your branch onto upstream/master so we can run the checks. Now that we can see it implemented, I want to check our reasoning one last time. The argument against is what I stated above: while you're trying to figure out The argument for is that you typically optimize your parameters on a single frame, and then you run feature-finding on all frames. So you'd only use So, at this point I think we're making the right choice, but adding kwargs is serious business, so I wanted to lay out the reasoning and make a little room for debate! |
Co-authored-by: Nathan Keim <keim@psu.edu>
|
@nkeim I do think that the way of optimizing
As I understand it, in the ideal case, we don't optimize I mentioned the diameter should likely be determined by other means. I'm not sure on what other means would be best at the moment.
That said, diameter optimization is a somewhat unrelated issue, that, for now, could be addressed with updating documentation / warning users to first optimize parameters before enabling SPIFF. (especially since the importance of diameter optimization seems to slightly get less when spiff is enabled) I do believe the benefit of adding this as a (disabled-by-default) argument outweighs the drawback of some users not optimizing the diameter as well, but happy to change it if you think otherwise! I could either:
What do you think? |
|
Thanks! It sounds like this should be an update to the walkthrough notebook in the trackpy-examples repo. There just needs to be different guidance about choosing I think this PR is ready to merge once the walkthrough changes are drafted! One last thing: Could the journal article citations please be in the docstring of |
|
@nkeim I also created soft-matter/trackpy-examples#66 with an updated walkthrough. Do you think a method to optimize the diameter automatically (like I mentioned in my previous comment) would be a useful addition to trackpy? In that case I'll create an issue (and file a separate PR for this). |
nkeim
left a comment
There was a problem hiding this comment.
I have this one last change to suggest. We might also consider renaming apply_spiff_correction to be shorter, in keeping with most other API function names in trackpy. Do you have an idea? spiff_correct? If not, it can be a separate PR :).
|
@nkeim good point! I updated the function name to just Alternatively it could be called |
Closes #695. Closes #413.
Feature
Based on https://www.nature.com/articles/s41598-017-14166-6?WT.feed_name=subjects_optical-manipulation-and-tweezers#Sec9 .
Use SPIFF to remove pixel-locking bias and improve sub-pixel accuracy of located features.
In the test cases shown (see
test_spiff.py), you can see an order of magnitude better accuracy when the SPIFF correction is applied.Sample results
Verify by adding the following line in

test_spiff.pyand running the tests:2D
error before: 0.1300
error after SPIFF: 0.01998
3D
error before SPIFF: 0.2486
error after SPIFF: 0.0227
Note on documentation and further integration
I did not invest time into updating the documentation and further integrating it into the basic features yet, as I wanted to see what the maintainers think of the feature first.
Since it seems like SPIFF improves accuracy pretty much universally, I'd suggest the following (but feel free to ignore or suggest something else):
apply_spiffargument intp.locate()andtp.batch()(could be enabled or disabled by default) and have those functions apply the SPIFF correction so the user doesn't have to know or think about it necessarily.apply_spiffargument as well as the underlyingapply_spiff_correctionfunction.apply_spiff_correctionmethod for more details on this).