Skip to content

ENH/DEP: use a more robust, external implementation of the Line Integral Convolution (LIC) algorithm #5131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

neutrinoceros
Copy link
Member

@neutrinoceros neutrinoceros commented Mar 7, 2025

PR Summary

I've been spending some time rewriting the Line Integral Convolution (LIC) algorithm in Rust and published it as a new, minimal, Python package: rLIC
Because I wanted to see how it compared with yt's builtin Cython extension, in terms of performance, I ran a couple tests, and found that rLIC was not only much faster1 (more than x2):

bench_res_var_k
this benchmark shows the execution time of convolving a 512 x 512 image with various kernel sizes

... it also seems to be more correct. See this very simple test case, where yt's implementation appears to be completely broken:

comp_simple

This is the one example from our docs, as of yt 4.4.0
test_lic_native_Slice_z_density

and here it is with this branch

test_lic_rlic_Slice_z_density

so, instead of fixing yt's implementation, I would like to propose yt adopts rLIC as a dependency.

Since yt's implementation doesn't appear to be correct, it is no surprise that rLIC isn't a drop-in replacement: new results are not identical. Bug-for-bug backward compatibility being excluded, I also made a couple adjustments, like replacing the legacy prng API that's discouraged in numpy.

Important notes:

  • rLIC wheels are abi3, which means that the package is already future compatible with Python 3.14+ and will remain so until CPython breaks abi3 and creates an abi4. If this happens, I will publish new wheels. The point being that depending on rLIC is not expected to cause any disturbance when adding support for newer Python versions in yt
  • I support all platforms that numpy 2.2 supports
  • rLIC is not yet distributed through conda-forge, which I'm aware is a requirement for any dependency of another package that wants to be shipped there. I don't mind putting the package on conda-forge too, pending acceptance of this proposal
    update: nevermind I'm going for it anyway Add rlic conda-forge/staged-recipes#29385
    update 2: it's up on conda-forge

PR Checklist

  • New features are documented, with docstrings and narrative docs
  • Adds a test for any bugs fixed. Adds tests for new features.

Footnotes

  1. if you're wondering, no, it's not just because my version is in Rust. I did spend time optimizing the implementation, in particular I avoided wasteful speculative executions which are extremely common with the naive implementation of this algorithm.

@neutrinoceros neutrinoceros added bug viz: 2D proposal Proposals for enhancements, changes, etc performance dependencies Pull requests that update a dependency file labels Mar 7, 2025
lim=(0.5, 0.6),
cmap="binary",
alpha=0.8,
alpha=0.5,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that a lower default alpha (opacity) made more sense with the correct implementation where "ink" is more uniformly distributed across the image so it's easier to hide the background completely if you're not careful.

Comment on lines -3292 to -3294
mask = ~(np.isfinite(pixX) & np.isfinite(pixY))
lic_data[mask] = np.nan
lic_data_clip[mask] = np.nan
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part was initially added to fix #3738
rLIC natively handles NaNs in the input vector fields: streamlines are simply terminated as soon as a NaN is encountered, so yt doesn't need to clean after itself anymore.

@@ -3238,10 +3238,10 @@ def __init__(
field_x,
field_y,
texture=None,
kernellen=50.0,
kernellen=50,
Copy link
Member Author

@neutrinoceros neutrinoceros Mar 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a float never made sense here: kernellen represents the length of a 1D array. I'm just fixing this in passing.

@neutrinoceros neutrinoceros added this to the 4.5.0 milestone Mar 7, 2025
@neutrinoceros
Copy link
Member Author

(I'm putting this on the 4.5.0 milestone, mostly as to mean I don't want it to be backported, but it shouldn't block any particular release)

@matthewturk
Copy link
Member

whoa.

if self.texture is None:
prng = np.random.RandomState(0x4D3D3D3)
self.texture = prng.random_sample((nx, ny))
elif self.texture.shape != (nx, ny):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this check because rlic.convolve already runs sanity checks over its inputs, including this one.

@neutrinoceros neutrinoceros changed the title ENH/DEP: use more a more robust, external implementation of the Line Integral Convolution (LIC) algorithm ENH/DEP: use a more robust, external implementation of the Line Integral Convolution (LIC) algorithm Mar 7, 2025
@neutrinoceros
Copy link
Member Author

@olebole, do you have any objection to, or questions about adding this dependency to yt ? Would you expect it could be a burden to package rlic for debian ?

@neutrinoceros neutrinoceros marked this pull request as ready for review March 7, 2025 18:34
@olebole
Copy link
Contributor

olebole commented Mar 9, 2025

@neutrinoceros I will not be able to package it for the upcoming Debian "Trixie" release which will come summer/autumn of this year, but for later this should be possible.
However, this would be my first package that uses Rust, so I am totally unfamiliar with its ecosystem, so expect some trivial questions during the packaging 😁

@neutrinoceros
Copy link
Member Author

Happy to go down this road with you, it is also my first experience mixing rust and Python, so I'm genuinely curious to find how easy or difficult the overall process turns out !

@neutrinoceros
Copy link
Member Author

Let's see how it plays out over at conda-forge first conda-forge/staged-recipes#29385

@neutrinoceros
Copy link
Member Author

conda-forge is done.
@matthewturk, you sounded pretty enthusiast, do you think this can go in yt 4.5.0 ?

@neutrinoceros
Copy link
Member Author

power cycling this to refresh CI

@neutrinoceros
Copy link
Member Author

Moving to rLIC should soon unblock the missing feature for LIC: compat with periodic datasets neutrinoceros/rlic#196

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug dependencies Pull requests that update a dependency file performance proposal Proposals for enhancements, changes, etc viz: 2D
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants