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
Implementation of ndimage filters #3184
Conversation
This is the first step towards a complete ndimage.filter suite. The functions here are designed to be flexible so that all other ndimage.filter functions can be created.
All other filters will be based on this core set of code.
I wonder if we should exposed the 1-D convolve as |
@jakirkham It is possible, a quick idea of how to do it would be. Note I have never used
The downsides of this compared to
|
The 8 tests that are not passing are actually apparently a bug in Scipy. I have submitted a bug report over there: scipy/scipy#11661. It does not handle |
First change (the w[] to weights[] I forgot to do earlier) was the most major.
Bad copy-paste naming of the tests along with avoiding a scipy bug.
@coderforlife -- I'm interested in CuPy's support/implementation of
I took a quick-ish spin of Anyway, as you may know, a = [1, 2, 3]
b = [0, 1, 0.5]
y = np.convolve(a, b, mode="full") Yields: Convolution/correlation is a point by point overlapping and sliding dot product that starts with a mirrored version of the filter (b) and the data (a). The last element in the mirrored filter is overlapped with the first element of the data, and the dot product is taken as the filter moves element by element over the data. To be explicit: mirrored_b (denoted by The convolution works like: y[0] = m_b[2] * a[0] = 0 * 1 = 0
y[1] = m_b[2] * a[1] + m_b[1] * a[0] = 0 * 2 + 1 * 1 = 1
y[2] = m_b[2] * a[2] + m_b[1] * a[1] + m_b[0] * a[0] = 0 * 3 + 1 * 2 + 0.5 * 1 = 2.5
y[3] = m_b[1] * a[2] + m_b[0] * a[1] = 1 * 3 + 0.5 * 2 = 4
y[4] = m_b[0] * a[2] = 0.5 * 3 = 1.5 Here's a visualization of what's going on too: https://en.wikipedia.org/wiki/Convolution#/media/File:Convolution_of_box_signal_with_itself2.gif Again, correlation is very similar, but you don't have to flip the filter component ( |
@awthomp I know that convolution and correlation are essentially the same thing with mirrored 'kernels', 'weights', or 'filter' depending on what you call the smaller of the two arrays. For the 1D case, it does this along an axis instead of in all dimensions. Unlike The following code demonstrates the equality:
where I have corrected the code from above to properly align the output with what
This should handle all cases but may not be the most efficient for all cases. In all likelihood, Implementing |
Hi @coderforlife -- We've actually taken care of the correlate/convolve in cusignal. For large arrays/filters, we're getting a 1-2 order of magnitude perf gain over Scipy Signal, but -- as you can see -- we default to numpy (rather than invoking an fft) for small array sizes. This isn't ideal. Much of cusignal is based on changing SciPy Signal Numpy calls for CuPy; when there's not support (like correlate2d or upfirdn), we wrote custom Numba CUDA kernels and are now exploring Raw CuPy Modules. Usage examples are here: https://github.com/rapidsai/cusignal/blob/d64df2fc2c40bbdba4aad34641460176552cd5f6/notebooks/api_guide/signaltools_examples.ipynb |
Hey @awthomp, I had implemented |
@grlee77 Awesome! Looking forward to seeing your work. In the meantime, we also implemented the 1D double precision raw kernel for |
This results in a single style of kernel to maintain while also mainting the speed of 1D kernels.
cupyx/scipy/ndimage/filters.py
Outdated
format(j=j, type=int_type)) | ||
else: | ||
boundary = _generate_boundary_condition_ops( | ||
mode, 'ix_{}'.format(j), 'xsize_{}'.format(j)) | ||
loops.append(''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loops.append(''' | |
loops.append(''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for refactoring to only avoid duplicate 1d and nd code paths!
I can confirm good performance of the refactored kernel. The only problem I encountered was the indentation error above.
The 1D case now performs approximately the same as what I had in cupyimg
. 3D cases I tested are up to 25% faster for this kernel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please address this!
thanks 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completed in #3390
cupyx/scipy/ndimage/filters.py
Outdated
if not hasattr(origin, '__getitem__'): | ||
origin = [origin, ] * input.ndim | ||
def _fix_sequence_arg(arg, ndim, name, conv=lambda x: x): | ||
if hasattr(arg, '__iter__') and not isinstance(arg, str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we favor try/except AttributeError over hasattr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@emcastillo Can you point me to some example code you want me to use?
Just curious why y'all prefer try/except over hasattr
?
https://stackoverflow.com/questions/903130/hasattr-vs-try-except-block-to-deal-with-non-existent-attributes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, yes, we use try/ except AttributeError because it is faster.
If you think that in this routine speed doesn't matter you can leave it as is 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be cleaner and faster to check if arg
is not a str
and then just do arg = iter(arg)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I tried swapping
if hasattr(arg, '__iter__') and not isinstance(arg, str):
with
if not isinstance(arg, str):
try:
arg = iter(arg)
except AttributeError:
return NotImplemented
Now most/all tests are failing. Are these not equivalent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The exception for an invalid iter with iter
is
TypeError: 'object' object is not iterable
😅 this might be confusing
A fix for this will be included in SciPy 1.5 (scipy/scipy#11683). You can avoid the failing test cases by decorating those cases with: @testing.with_requires('scipy>=1.5.0') |
Jenkins CI test (for commit 693a2cc, target branch master) failed with status FAILURE. |
Jenkins, test this please |
Successfully created a job for commit 04e6ab3: |
It looks like I forgot to decorate the However, the vast majority of the failures were from the morphology code. |
Jenkins CI test (for commit 04e6ab3, target branch master) failed with status FAILURE. |
Looks good (except morphology tests, someone else needs to fix that). I am curious about the coverage test, is it being run without scipy? It says none of my code (except the function declarations and imports) is covered... |
Do you happen to know why morphology tests are failing?
Don't care too much about the coverage test :) |
So 2 easy things to fix and 1 thing I will have to look into, could be medium or hard. |
If you want you can restore the old functions and someone can fix them later in a follow-up PR |
This is used by morphology.grey_erosion() and grey_dilation().
I have fixed all of the issues. One other issue that didn't come up in the tests is that their tests used |
Awesome work and thanks once again for patiently addressing our comments!🙇♂️ |
Jenkins, test this please |
Successfully created a job for commit 97c1c94: |
It's okay - I already have the next PR planned and it will hopefully go smoother. It is added all of the other basic filters on top of this core set ( I am thinking to do them in two batches: non-linear filters (the last 3 in that list) since they actually have a bit of new cuda code with them, then do all of those linear filters (which all just call the I have also started working on |
Jenkins CI test (for commit 97c1c94, target branch master) succeeded! |
wow, thats awesome! This PR will be merged after the release today, right now code is frozen. |
This is great! Thanks everyone for pushing this forward 😄 |
So far this PR includes improved
correlate
andconvolve
functions (in terms of speed, uses similar technique to #3179) along with implementations ofcorrelate1d
andconvolve1d
along with tests for them. The underlying kernel creation has been generalized so that it can be used to implement all of the other filters and those will be progressively added to this PR (I have an implementation of them but haven't tested them yet).This works to address #2099 and #3111.
Current status of tests is that it is passing 7120 tests (in test_filters.py) and failing 8 (they are all with the 1d functions along axis 0 using mode=mirror with 4D images but no other axis or mode or less-dimension image).