Fix bugs and clean up grayscale morphology operators#6695
Fix bugs and clean up grayscale morphology operators#6695stefanv merged 36 commits intoscikit-image:mainfrom
Conversation
|
OK, I've found a way to make the desired changes without changing the output using default parameters (except where the output was wrong). The binary and gray-value version of the morphological operators are not equivalent, they can only be made equivalent by changing the output of one set. I've updated the description above to match the new situation. The tests still fail for the same reason -- they pass when using the correct |
lagru
left a comment
There was a problem hiding this comment.
Thanks @crisluengo for this! I only started reviewing this but you already got a lot right by yourself! 👌 Please feel welcome to ask for clarification or discuss if you don't agree with my suggestions.
There was a problem hiding this comment.
I'm currently thinking about how to can keep our API correct and consistent in the long run. How do we justify adding the the mirror parameter only to some of our functions that use a footprint whose symmetry would impact their output. Rather than let a bug influence our API I would prefer to skip the perhaps a bit trivial mirror parameter and enable the user to do so beforehand with mirror_footprint.
Maybe we could add mirror temporarily to binary_dilation as part of a deprecation path? E.g.
- Release x: In
binary_dilation, addmirror=True. UsingTruewill raise aFutureWarningthat mirroring will be removed in the future. Advise the user to usemirror=Falseand applymirror_footprintif they need it. - Release x+1: Remove the mirroring behavior. Using the
mirrorparameter at all raises aFutureWarningthat the parameter is deprecated. - Release x+2: Remove the
mirrorparameter.
For users that use footprint=None anyway, we could reduce the noise by not raising deprecation warnings at all in that case. What do you think?
Yes, this would be cleaner
I'm not sure about forcing people to add a I am not sure there is a clean way to change this behavior other than changing the function name (i.e. adding a new function, deprecating the old one). I think the simplest solution is to keep the behavior as it is now, and document the difference between |
I can live with that. 😅 @scikit-image/core do we already collect these TODO's for skimage2 somewhere in a more structured and explicit list than our milestone? Otherwise we should probably start such a document sooner rather than later.. |
f9ae1c1 to
212c2e8
Compare
|
I also added an input guard for the |
| Parameters | ||
| ---------- | ||
| footprint : ndarray or tuple | ||
| The input footprint or sequence of footprints |
There was a problem hiding this comment.
| The input footprint or sequence of footprints | |
| A footprint expressed as a 2-D array of 1's and 0's. The footprint can | |
| also be provided as a sequence of smaller footprints. |
| Parameters | ||
| ---------- | ||
| footprint : ndarray or tuple | ||
| The input footprint or sequence of footprints |
There was a problem hiding this comment.
| The input footprint or sequence of footprints | |
| A footprint expressed as a 2-D array of 1's and 0's. The footprint can | |
| also be provided as a sequence of smaller footprints. |
This should also warn in case shift_x & shift_y are passed as positional arguments.
There was a problem hiding this comment.
We need to come up with a solution that works with changelist (which we use to generate a release note draft). If scientific-python/changelist#45 get's implemented, we can just update the PR description accordingly even after this is merged.
In the meantime, the summary will get put in one section only depending on the label. In that case we can format manually at release time.
By making sure that everything goes through _iterate_binary_func I actually discovered a bug which is addressed in 4a31f39.
| """ | ||
| fp, num_iter = footprints[0] | ||
| gray_func(image, footprint=fp, output=out) | ||
| gray_func(image, footprint=fp, output=out, mode=mode, cval=cval) |
There was a problem hiding this comment.
Approving on the condition that https://gitlab.com/scikit-image/data/-/merge_requests/23 is merged. 🎉
@crisluengo, I took the liberty to tweak / add a bit which I think was the way faster option for this PR. Please feel welcome to review or revert. Thank you very much for your work and especially clarifications. :D
|
@lagru Thank you so much for your work on this PR. Next time I'll make sure to keep my PRs small. :) |
There was a problem hiding this comment.
Overall, the PR looks good to me, although I suspect there are a lot of subtleties.
@crisluengo Any chance you could join us tomorrow morning 9am Pacific for the community call to discuss this PR?
| out : ndarray of bool, optional | ||
| The array to store the result of the morphology. If None is | ||
| passed, a new array will be allocated. | ||
| mode : str, optional |
There was a problem hiding this comment.
This argument name caught me by surprise, since usually mode defines how boundary values are obtained: {'reflect', 'constant', 'edge', 'symmetric', 'wrap'}.
There was a problem hiding this comment.
It is exactly the same meaning, but the options are different. The ndimage function we call here only has the option of a constant value (you can either pad with 0s or with 1s). We’re using the strings ‘min’ and ‘max’ for those two options, they match the strings used in the gray-scale morphology. And we added the ‘ignore’ option (which we also added to the gray-scale version), which is ‘max’ for erosion and ‘min’ for dilation.
We cannot easily implement ‘reflect’ and the other more complex modes. Though these are available in the gray-scale versions of the operators, which we could be using here instead. They all do the same thing for binary images anyway.
There was a problem hiding this comment.
Ah! Thanks for explaining. I think that's fine then.
|
@stefanv I might be able to hop on that call for a bit, if you think it’d be useful. |
I'll be around from 9:15; a quick "this is the problem, here's all the things I did to fix it, here's what remains to be done in the future" would be great. We need to make sure all those future TODOs are documented, either in an issue or in the TODO.txt. |
|
@stefanv OK, I’ll hop on the call 9:15 Pacific. |
|
Thank you Cris! And thanks, Lars, for carefully reviewing and shepherding the PR through. |
|
Now we need to resolve scientific-python/changelist#45 so we can represent this properly in our release notes. Otherwise, we have to do so manually (see updated PR description). |
Description
This PR affects
skimage/morphology/gray.py:Makes the dilation mirror the footprint.shift_xandshift_yparameters, in favour of the newmirrorparameter (see below).Makeserosion,dilation,openingandclosing, with default parameters and a binary input image, identical to the equivalentbinary_...functions.white_tophat, and complicates the code forblack_tophatso that it matcheswhite_tophat. Both now special-case forout is image.modeandcvalparameters, with the special optionNonethat sets the pixels outside the image to the max value for the image's dtype in the erosion, and to the min in the dilation. This causes the pixels outside the image to be ignored in these operations. The default boundary condition is'reflect', as it was before (because that is the default in ndimage), but that condition causes surprising results if the footprint is not simple. Addingmode=Noneis recommended in these cases. (Dilation and erosion image boundary extension #6665).border_valueparameter tobinary_erosionandbinary_dilation, they might come in handy (inbinary.py).mirrorparameter toerosion,dilation,binary_erosionandbinary_dilation, setting it toTruewill mirror the footprint. Forbinary_dilationit isTrueby default, to match previous behavior. This is necessary to build the opening and closing correctly (ingray.pyandbinary.py). For skimage 2.0 I would suggest setting the defaultmirrorto the same value for the gray-value and binary versions of the same operator.skimage.morphology.pad_footprintandskimage.morphology.mirror_footprint(infootprints.py).pad_footprintis used so thatmirror_footprintworks correctly for even-sized footprints, and to be able to keep things consistent with previous behavior. Note that the binary dilation and erosion pad on the left, and the gray-value ones on the right. This is the reason that we can't make these operators equivalent without breaking changes. For skimage 2.0 I would suggest making this padding consistent between the two versions.This PR closes #6665 and addresses #6676 -- it was a lot easier fixing both at the same time.
This PR causes changes to the output for the opening, closing, and tophat if the footprint is not mirror symmetric (e.g. if it's even in size). This is because the output was incorrect for these cases previously, see #6676.
@stefanv and @lagru: Thank you for your help with this.
Test failure
The CI test failed on GitHub. It ran OK on my machine only if I changed the line
to not use
fetch, and to have the absolute path to the file (which has changed). I think thisfetchfunction is broken, it reads the file from elsewhere, not from the cloned repository. Note that I needed to change this file because the opening, closing, and tophat functions have been fixed.Checklist
./doc/examples(new features only)./benchmarks, if your changes aren't covered by anexisting benchmark
For reviewers
later.
__init__.py.doc/release/release_dev.rst.example, to backport to v0.19.x after merging, add the following in a PR
comment:
@meeseeksdev backport to v0.19.xrun-benchmarklabel. To rerun, the labelcan be removed and then added again. The benchmark output can be checked in
the "Actions" tab.
Release note
Summarize the introduced changes in the code block below in one or a few sentences. The
summary will be included in the next release notes automatically: