Move extremization, blob_LoG from Images#223
Conversation
Codecov Report
@@ Coverage Diff @@
## master #223 +/- ##
==========================================
- Coverage 91.31% 90.29% -1.02%
==========================================
Files 9 12 +3
Lines 1450 1535 +85
==========================================
+ Hits 1324 1386 +62
- Misses 126 149 +23
Continue to review full report at Codecov.
|
|
Is the doc failure (segfault 😱) any reason to hold off on merging this? |
|
The documentation segfault might be related to Plots, see also JuliaImages/juliaimages.github.io#210. I haven't had the time to investigate it so the best strategy is to temporarily remove it. |
src/extrema.jl
Outdated
| # If i is in the interior, we don't have to worry about i+j being out-of-bounds | ||
| for j in Rregion | ||
| j == z && continue | ||
| if !Base.Order.lt(order, img[i+j], img_i) |
There was a problem hiding this comment.
I was actually thinking if it's better to expose this as argument f and make
findlocalextrema(f, img; [dims,] [edges])There was a problem hiding this comment.
And furthermore, maybe it's better to have it work similarly to mapwindow:
findall_window(f, img, window; [border,] [indices])There was a problem hiding this comment.
That's pretty interesting. From a pragmatic standpoint one would probably want to smooth with a separable filter and then use a 3x3 window rather than an nxn window (the latter is O(n^2), the former is O(n)). But I do see the point.
I guess |
This makes more dramatic depatures from our old implementations: - add a `show` method for `BlobLog` - encode the entire multidimensional `σ` (after multiplying by `σshape`) - add a threshold to dispose of spurious maxima by default - separate the filtering of `blob_LoG` into an independent (but non-exported) function - change the API of `findlocalmaxima` etc to be `window`-based rather than `dims`-based. This is strictly more flexible. Co-authored-by: Johnny Chen <johnnychen94@hotmail.com>
|
OK, I've made the suggested changes and more. Much nicer IMO, but see what you think. Thanks for suggesting the more thorough revision, it's really the perfect time. I didn't quite mimic the The commit comment 8d854bb explains the changes. |
There was a problem hiding this comment.
I'm thinking of an even generic window version of findall that iterates over all the interior points of img and return all p that satisfies f(window_p) == true. Just like mapwindow is the window version of map, I'm thinking it as the window version of findall.
Since we need this PR for JuliaImages/ImageSegmentation.jl#72, maybe we can leave findall_window as future work if that sounds too much coding work.
| freqkernel, spacekernel | ||
| freqkernel, spacekernel, | ||
| findlocalminima, findlocalmaxima, | ||
| blob_LoG, BlobLoG |
There was a problem hiding this comment.
Do we really need to export the struct BlobLoG?
There was a problem hiding this comment.
Together with my kwarg method, it makes the output cut/pasteable ("round-trippable"), but maybe that's not important.
| Rinterior = clippedinds(R0, halfwindow) | ||
| Rwindow = _colon(-halfwindow, halfwindow) | ||
| z = zero(halfwindow) | ||
| for i in R |
There was a problem hiding this comment.
It looks valid for me to skip the default bounds checking here:
| for i in R | |
| @inbounds for i in R |
An excellent design concept and a worthy goal. No time like the present... |
Yeah... I'm also running short of time.. Merge when you ready. |
|
Sorry, I meant "there is no better time than the present." So I'll try it now. (Yes, I am busy too, but your suggestion is too good to leave for later.) EDIT: https://idioms.thefreedictionary.com/There%27s+no+time+like+the+present |
|
One interesting question: when you're within a window's width of the boundary, the center point of the valid box is not the base point. Should |
My understanding on results = CartesianIndex{N}[]
for (window_ax, i) in zip(TileIterator(axes(X), window)), CartesianIndices(X))
window = @view X[window_ax...]
f(window) && push!(results, i)
endWell. Some efforts are needed to achieve the current performance of For |
|
Currently we exclude windows where |
|
OK, see if you like this API. Performance: julia> A = rand(100, 101);
julia> @btime findlocalmaxima($A);
132.642 μs (11 allocations: 64.47 KiB)
julia> @btime ImageFiltering.findmax_window($A, $(ImageFiltering.default_window(A)));
182.121 μs (11 allocations: 64.47 KiB)So not too shabby, but probably enough of a difference that we still want |
|
Will Of course, the performance is really an issue. Edit: let me also take a try :) This function is quite interesting. |
5bd10ea to
d84449d
Compare
|
My docs were lying a bit, I combined two things I shouldn't have combined. This is more accurate (the code itself hasn't changed). |
|
Did you see this (edited) comment:
That's the core problem with your implementation in JuliaImages/ImageSegmentation.jl#72 (comment). |
Right, I think I'm thinking of the same implementation as your current version. I'll try more if there are better ideas to optimize the performance and then will comment. If we allow paddings like |
|
Not sure I understand this:
That only passes the indices, and leaves the array itself implicit. That's pretty different from how |
|
Ooh, I got it down a little further: julia> @btime ImageFiltering.findmax_window($A, $(ImageFiltering.default_window(A)));
158.325 μs (6 allocations: 43.30 KiB)Do you think that's a small enough gap (~15%) that we could do away with |
src/findall_window.jl
Outdated
| @inline function all_window(f::F, V::AbstractArray, basepoint::CartesianIndex, excludepoint::Union{Nothing,CartesianIndex}=nothing) where F | ||
| @inbounds ref = V[basepoint] | ||
| @inbounds for (i, v) in pairs(V) | ||
| i == excludepoint || f(ref, v) || return false |
There was a problem hiding this comment.
I'm expecting f(ref, v) is scalar operation and is type stable, but if I change to
i == excludepoint || ref == v || return falseI get
julia> @btime ImageFiltering.findmax_window($A, $(ImageFiltering.default_window(A)));
74.347 μs (1 allocation: 80 bytes)Surprisingly less allocations, do you have any ideas?
Edit: hmmm, maybe is due to the push! operation in findall_window.
There was a problem hiding this comment.
Presumably the output array is empty? So it's probably just push! reallocating the output buffer.
There was a problem hiding this comment.
With
function findall_window(f::F, img::AbstractArray{T,N}, window::Dims{N}; allow_edges::NTuple{N,Bool}=ntuple(_->true, N)) where {F,T<:Union{Gray,Number},N}
buffer_size = min(512, length(img)) # a heuristic threshold to work around the overhead of `push!`
basepoints = Vector{CartesianIndex{N}}(undef, buffer_size)
Iedge = CartesianIndex(map(!, allow_edges))
R0 = CartesianIndices(img)
R = clippedinds(R0, Iedge)
halfwindow = CartesianIndex(map(x -> x >> 1, window))
i_positive = 0
@inbounds for i in R
Rview = _colon(i-halfwindow, i+halfwindow) ∩ R0
if f(OffsetArray(view(img, Rview), Rview.indices), i)
i_positive += 1
if length(basepoints) < i_positive
resize!(basepoints, min(2length(basepoints), length(img)))
end
basepoints[i_positive] = i
end
end
return resize!(basepoints, i_positive)
endI'm getting closer:
julia> @btime ImageFiltering.findmax_window($A, $(ImageFiltering.default_window(A)));
146.643 μs (4 allocations: 60.31 KiB) # after
167.950 μs (11 allocations: 64.47 KiB) # beforeWill this be too tricky?
There was a problem hiding this comment.
sizehint! does the same thing and is much simpler.
There was a problem hiding this comment.
And these optimizations could be applied to findlocalextrema, widening the gap once again. (The same comment applies to my own i == excludepoint || ... change.)
I think the key question is any overhead from creating the wrapper, which findlocalextrema doesn't do.
|
Should we split out the |
|
Since the API is settled we can have both here and then try to improve the performance in future PRs. Edit: I'll travel back to Shanghai tomorrow for the new semester so won't be available to tackle the performance issue this weekend. |
johnnychen94
left a comment
There was a problem hiding this comment.
Since the API is settled we can have both here and then try to improve the performance in future PRs.
Well... Sorry for the back and forth. Let's separate it to another PR so that we have more time to think about the implementation.
src/findall_window.jl
Outdated
| @@ -0,0 +1,51 @@ | |||
| """ | |||
| findall_window(f, img::AbstractArray{T,N}, window::Dims{N}; allow_edges::(true...,)) | |||
There was a problem hiding this comment.
I just realize that allow_edges doesn't work correctly when window != (3, 3).
There was a problem hiding this comment.
It depends on what you mean by "correctly." The sense here is, "will it return a solution on the edge of the image?" In that sense it works fine (I think). What sense did you mean?
db0068a to
88a9e89
Compare
I think (?) the best way to do this is to make a minor bump. So we should make sure we move everything at once. Am I missing things? What about
imROF?xref JuliaImages/ImageSegmentation.jl#72