Skip to content
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

Add unsharp mask feature and gallery example #2642

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

YueHazelZheng
Copy link
Contributor

@YueHazelZheng YueHazelZheng commented Apr 30, 2017

Description

This PR addresses issue #2477 and adds an unsharp mask feature, including a gallery example. Efficiency still needs to be improved and ideas for unit tests are appreciated.

Checklist

[It's fine to submit PRs which are a work in progress! But before they are merged, all PRs should provide:]

[For detailed information on these and other aspects see scikit-image contribution guidelines]

References

This PR implements the following paper: http://ieeexplore.ieee.org/abstract/document/826787/

For reviewers

  • Check that the PR title is short, concise, and will make sense 1 year
    later.
  • Check that new features are mentioned in doc/release/release_dev.rst.

@pep8speaks
Copy link

Hello @YueHazelZheng! Thanks for submitting the PR.

Line 26:1: E101 indentation contains mixed spaces and tabs
Line 26:1: W191 indentation contains tabs
Line 26:5: E128 continuation line under-indented for visual indent
Line 27:1: E101 indentation contains mixed spaces and tabs
Line 28:9: E128 continuation line under-indented for visual indent
Line 30:9: E128 continuation line under-indented for visual indent

@dvolgyes
Copy link
Contributor

Maybe it is just me, but wouldn't be better to call it "adaptive_unsharp_mask"?
The traditional unsharp masking is much simpler, and it might be confusing to call
the adaptive version as "unsharp_mask".
See: https://en.wikipedia.org/wiki/Unsharp_masking
Sharpened = Original + ( Original - Blurred ) * Amount

I would rename the function in the PR to reflect that it implements an adaptive version,
and in a separate PR (or in the same one) the traditional unsharp mask could be
also implemented.

A general nD implementation of the traditional unsharp_mask could be:

from numpy import clip, iinfo
from scipy.ndimage.filters.gaussian_filter import gaussian_filter
def unsharp_mask(image, radius=2, amount = 1.0, color = False):
   if color:
      # no blur in the color channel
      sigma = (radius,) * (len(image.shape)-1) + (0,)
   else:
      sigma = radius
   blurred = gaussian_filter(image, sigma=radius)
   return image + (image - blurred) * amount

It might be a good idea to clip the image, and/or enforce the same output type as the input was.
E.g.

   result = image + (image - blurred) * amount 
   # result has float type, regardless of the input
   if image.dtype.kind in ['u','i']:
      # only integer type have maximum/minimum in iinfo
      return clip(result, iinfo(image.dtype).min, iinfo(image.dtype).max).astype(image.dtype)
      # float types also could be clipped to [0,1] or [-1,1]
   return result

The clipping, type conversion, etc., is a question which should be decided for both versions.

(And obviously, the build errors must be fixed before further actions.)

@stefanv
Copy link
Member

stefanv commented Aug 21, 2017

@dvolgyes Do you happen to have a good reference citation? I think trying to find one is what led to the discovery of this paper, and its subsequent implementation.

@dvolgyes
Copy link
Contributor

Well, the problem is that unsharp masking is really really old technique, analog version dating back to the 1930s. So even in the 1970s, there are articles about the application but not on the original method.
Where you actually can find reference: image processing books/textbooks.

Most of the time it is just in the middle of the book, not even in a dedicated chapter, so you could cite
one or two pages from the book. On google books, you can actually look into the book, so
here are some links:

On Page 274, section 5.10.2:
https://books.google.com/books?id=JeDGn6Wmf1kC&pg=PA274&dq=image+processing+unsharp+masking&hl=en&sa=X&redir_esc=y#v=onepage&q&f=false

Example citation could be: Jayaraman, S., Esakkirajan, S., & Veerakima, T. (2009). Digital image processing, ed iii., page 274

Similarly, on page 174-175, see equation 6.38.
https://books.google.com/books?id=YpzWCwAAQBAJ&dq=Gonzalez%2C+R.C.%2C+Woods%2C+R.E.%3A+Digital+Image+Processing%2C&q=unsharp#v=snippet&q=unsharp&f=false

Another nice here, see page 104, section 5.7:
https://books.google.com/books?id=9irSCgAAQBAJ&pg=PA104&dq=unsharp+masking+intitle:image+intitle:processing&hl=en&sa=X&redir_esc=y#v=onepage&q=unsharp%20masking%20intitle%3Aimage%20intitle%3Aprocessing&f=false

And so on. Almost every intro-level digital image processing book has it.

@stefanv
Copy link
Member

stefanv commented Aug 22, 2017

@dvolgyes I agree with your comments above. Also, I have an excellent person in mind who could lead this heroic work nudge nudge ;)

@dvolgyes
Copy link
Contributor

I have no idea how could I change somebody else's PR.
So what I can offer: I can make a new PR for the basic unsharp masking,
and this current PR after some rework (e.g. renaming the function to adaptive_unsharp_masking) could contribute the adaptive version.
I am busy at this moment but I can do it in the next couple of days.
Until then I would appreciate a hint: should we clip the resulting image if it exceeds the dynamic range?
e.g. an image with 0-255 or 0-1 dynamic range could go beyond the limits if the USM has extreme "amount" parameter (a.k.a. overshooting). Should we clip the image to [0,255] / [0,1]?
(I would say yes, otherwise with integers we could have an overflow. With floats, the issue is not so pressing, but still.) Or should I make a parameter with default value, e.g. clip=True?

@stefanv
Copy link
Member

stefanv commented Aug 25, 2017

@dvolgyes This section of the user guide is relevant:

Additionally, some functions take a preserve_range argument where a range
conversion is convenient but not necessary. For example, interpolation in
transform.warp requires an image of type float, which should have a range
in [0, 1]. So, by default, input images will be rescaled to this range.
However, in some cases, the image values represent physical measurements, such
as temperature or rainfall values, that the user does not want rescaled.
With preserve_range=True, the original range of the data will be
preserved, even though the output is a float image. Users must then ensure
this non-standard image is properly processed by downstream functions, which
may expect an image in [0, 1].

>>> from skimage import data
>>> from skimage.transform import rescale
>>> image = data.coins()
>>> image.dtype, image.min(), image.max(), image.shape
(dtype('uint8'), 1, 252, (303, 384))
>>> rescaled = rescale(image, 0.5)
>>> (rescaled.dtype, np.round(rescaled.min(), 4),
...  np.round(rescaled.max(), 4), rescaled.shape)
(dtype('float64'), 0.0147, 0.9456, (152, 192))
>>> rescaled = rescale(image, 0.5, preserve_range=True)
>>> (rescaled.dtype, np.round(rescaled.min()),
...  np.round(rescaled.max()), rescaled.shape
(dtype('float64'), 4.0, 241.0, (152, 192))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants