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

Phase unwrapping: Branch cut algorithm #680

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

Conversation

josteinbf
Copy link
Contributor

My other PR on phase unwrapping, #644, is stalled (copyright issues), but I've had another implementation lying around for a while which is perfectly safe to include.

This PR implements the branch cut algorithm. Anyone wanting to review this in detail is encouraged to have a look at Goldstein's classic paper on phase unwrapping[1] before diving in; I've implemented the same algorithm with some slight modifications, mostly to allow treating masked arrays properly.

Functionality, tests, docs and examples should be about complete. I lack a test case for masked array input---ideas for good test cases welcome!

[1] R. M. Goldstein, H. A. Zebker, C. L. Werner, "Satellite radar
interferometry: Two-dimensional phase unwrapping", Radio Science 23
(1988) 4, pp 713--720.

@josteinbf
Copy link
Contributor Author

@ahojnnes @JDWarner Any chance of any of you reviewing this?

@ahojnnes
Copy link
Member

@josteinbf Sorry for the delay. I am not sure if we really need a separate queue implementation. Is it performance critical (if not, we could use PyList)? Otherwise the queue implementation should be moved to skimage/_shared.

Apart from that the implementation looks very good!

@josteinbf
Copy link
Contributor Author

@ahojnnes Thanks for looking into this, and thanks for your complements on the implementation. Performance-wise I guess it should be good enough, but I'm worried about readability. How does readability look to someone who did not code it? Do you feel you need to struggle to understand what's going on?

With regards to the queue, it is used rather intensively, but I have not really researched the options here much---I just figured any basic BSD-licensed C implementation would do. The average number of items in the queue is probably proportional to the number of pixels in the image (the proportionality constant is likely highly dependent on the input, but it could in bad cases get close to one), and items are enqueued and dequeued at a high frequency (all pixels enter the queue at least twice). By PyList, do you mean a python list, just accessed from C? I have no idea how efficient that is, never used something like that before. In summary, I'd very much appreciate advise on the queue implementation :) As this implementation only deals with 2D images the memory overhead of the queue is not critical, but using more memory for the queue could hurt performance through more fetches/stores (the number of flops per queue item is small).



def find_branch_cuts(residues):
'''Connect residues with branch cuts such that the length of the cuts
Copy link
Member

Choose a reason for hiding this comment

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

One-liner as a short description.

@ahojnnes
Copy link
Member

@josteinbf Let's keep the current queue implementation, but move it to skimage/_shared. Maybe we can also wrap it into a nice class, e.g. http://docs.cython.org/src/tutorial/clibraries.html ?

@stefanv
Copy link
Member

stefanv commented Sep 15, 2013

Here's one description of the allocation pattern: http://www.laurentluce.com/posts/python-list-implementation/

from libc.math cimport M_PI


def unwrap_naive_1d(double[::1] image, double[::1] unwrapped_image):
Copy link
Member

Choose a reason for hiding this comment

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

double[::1] => double[:]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you please explain the difference? I thought double[::1] would assume a C-ordered array (not patched together by memory chunks scattered in different places, but one continuous allocation), and allow cython to deal with this more efficiently?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, you are right, of course. Disregard my suggestion ;-)

@josteinbf
Copy link
Contributor Author

@ahojnnes Thanks for your comments. This week is too busy for me to respond properly to this, but I hope to get to it next week.

@josteinbf
Copy link
Contributor Author

@ahojnnes The queue OO wrapper is done now and in shared. It does indeed look a lot better, good suggestion. One question: I have assumed that cython will deallocate the queue for me when the queue variable goes out of scope, can you confirm that this is correct? If I'm wrong about that, the code will leak memory.

@josteinbf
Copy link
Contributor Author

@stefanv Thanks for the link on allocation patterns. The contents convince me that PyList is not an option for this code, since pop(0) and insert(0) are common operations in the code, both of which are O(n). With the separate queue implementation, these are both O(1).

@josteinbf
Copy link
Contributor Author

@ahojnnes I think I have addressed all of your comments now. Where I have not answered to your comment, I have just implemented your suggestion. There are a few questions for you that I hope you can answer above.

@josteinbf josteinbf mentioned this pull request Nov 22, 2013
@josteinbf
Copy link
Contributor Author

I'm in the process of rebasing this, which will require a fair few changes, since there is a substantial overlap with #644, which was just merged. Some decisions need to be made; I plan to do the following:

  • The public API will be a single function with a method kwarg choosing between the Goldstein implementation (this PR) and the more sophisticated method implemented by Phase unwrapping #644: unwrap_phase(..., method=None).
  • find_residues will be a public function, as it is useful for diagnosing problems when unwrapping.
  • The example will demonstrate the reliability method (the default), so it will not change much from Phase unwrapping #644.

Comments welcome.

@ahojnnes
Copy link
Member

@josteinbf This sounds like a perfect plan to me! find_residues should also definitely be public.

Maybe you can combine both examples into one script comparing the methods side-by-side?

@josteinbf
Copy link
Contributor Author

I'll have a look at what the differences between the methods really are. However, I fear that since we have only relatively unrealistic example data, we will not be able to demonstrate how the methods are different. If we want to do that, we would probably have to include an extra image. Would that be possible? It would of course increase the size of the distribution.

@ahojnnes
Copy link
Member

What kind of images do you have and what size are they?

@josteinbf
Copy link
Contributor Author

@ahojnnes Rebased.

Is there an easy way I can view a test coverage report from this fancy coverall thing? With the modified API, I'm certain there are some untested lines lying around relating to the method kwarg.

@josteinbf
Copy link
Contributor Author

@ahojnnes Regarding comparisons in the example: I think we should do that in a separate PR (if we should do it at all---the papers cited will certainly have information on this). This one's big enough already.

@josteinbf
Copy link
Contributor Author

A single doctest is failing, and I'm having trouble seeing what the problem is:

======================================================================
FAIL: Doctest: skimage.exposure.unwrap.find_phase_residues
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/doctest.py", line 2201, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for skimage.exposure.unwrap.find_phase_residues
  File "/home/jostein/Programming/scikit-image/skimage/exposure/unwrap.py", line 308, in find_phase_residues

----------------------------------------------------------------------
File "/home/jostein/Programming/scikit-image/skimage/exposure/unwrap.py", line 349, in skimage.exposure.unwrap.find_phase_residues
Failed example:
    find_phase_residues(image)   # "--" indicates a masked element
Expected:
    masked_array(data =
     [[0 0 0 --]
     [0 1 0 --]
     [0 0 0 --]
     [-- -- -- --]],
                 mask =
     [[False False False  True]
     [False False False  True]
     [False False False  True]
     [ True  True  True  True]],
           fill_value = 999999)
Got:
    masked_array(data =
     [[0 0 0 --]
     [0 1 0 --]
     [0 0 0 --]
     [-- -- -- --]],
                 mask =
     [[False False False  True]
     [False False False  True]
     [False False False  True]
     [ True  True  True  True]],
           fill_value = 999999)
    <BLANKLINE>


----------------------------------------------------------------------

I can only see a single difference, the <BLANKLINE> at the end. Is that really the problem? If so, how to fix it? I've already tried adding extra blank lines in the docstring.

@josteinbf
Copy link
Contributor Author

A bit of googling turned up the solution to the blank line issue. I expect tests to pass now.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.79%) when pulling 5ab2ad4 on josteinbf:phase-unwrap-goldstein into caf183a on scikit-image:master.

@josteinbf
Copy link
Contributor Author

c86aae2 should have failing tests. These tests may mean that the branch cuts implementation has a bug related to handling masked regions---I don't know yet. Until I know, please don't merge.

@stefanv
Copy link
Member

stefanv commented Nov 10, 2014

This PR's birthday is almost here!

@aaron-parsons
Copy link

Hi.

Myself, @darrenbatey and @pierrethibault are currently looking at phase unwrapping, and possibly extending this functionality in scikit-image.

What's the state of this PR? If we were to update it so that the files are in the right place, what's left to do to get the PR through?

Aaron

@stefanv
Copy link
Member

stefanv commented Oct 30, 2018

@aaron-parsons That's excellent news; a thorough review at this point would be most welcome and helpful, especially given that the PR has been dormant since 2014. Thank you!

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

7 participants