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

RAG drawing function #1087

Merged
merged 21 commits into from Aug 18, 2014

Conversation

@vighneshbirodkar
Copy link
Contributor

commented Jul 28, 2014

figure_1

figure_2

@coveralls

This comment has been minimized.

Copy link

commented Jul 28, 2014

Coverage Status

Coverage decreased (-0.01%) when pulling edb63bc on vighneshbirodkar:rag_draw into 85a5cea on scikit-image:master.

@coveralls

This comment has been minimized.

Copy link

commented Jul 28, 2014

Coverage Status

Coverage decreased (-0.01%) when pulling edb63bc on vighneshbirodkar:rag_draw into 85a5cea on scikit-image:master.

@@ -0,0 +1,28 @@
"""
===========
RAG Drawing

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Drawing Region Adjacency Graphs (RAGs)

@@ -201,3 +204,105 @@ def rag_mean_color(image, labels, connectivity=2):
graph[x][y]['weight'] = np.linalg.norm(diff)

return graph


def rag_draw(labels, rag, img, border_color=(0, 0, 0), node_color = (1, 1, 0),

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor
  • I'd call this draw_rag.
  • PEP8: no spaces around = for kwargs
  • Can we also add a colormap argument that takes a matplotlib colormap string and maps the edge colour to that? I know that in many cases this will be less visible, but I still think it could be very useful. One way to make it always visible would be to draw a thick black edge and then a thinner colormapped edge within it. We can experiment with this once the colormap code is in there.
  • The low-color and high-color keywords could be replaced by the colormap argument: see this stackoverflow Q&A for how to do this. I would do this by defining two custom colormaps within this module or within draw: single_colormap and two_color_colormap.

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 5, 2014

Author Contributor

draw:single_colormap will draw with just one color, right ?

How about we add a color argument, which will draw all edges with a single color and colormap argument which taken any matplotlib colormap ?

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 5, 2014

Author Contributor

We will have to modify draw.line to support variable edge thickness

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor
  • yes, regarding color. You're right that it would be less confusing to have two args, but make "color" "edge_color".
  • I'm pretty sure we have a line thickness somewhere — can you find out where the function is that draws the overlay for the LineProfile widget for the viewer?

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Oh and "edge_color" should be a constant by default, say, green, and "colormap" should be None. Passing colormap would override whatever edge_color was doing.

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 5, 2014

Author Contributor

You want to just display the image in a viewer, rather than returning a modified image ?

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

no no, returning the modified image is right. I'm just saying, the viewer returns a thick line, so there must be a function to compute those line indices.

Parameters
----------
labels : ndarray, shape(M, N, [..., P,])

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

If I'm not mistaken, this function only draws in 2D, so change this docstring accordingly. Also, space after shape.

dimensions `(M, N)`.
rag : RAG
The Region Adjacency Graph.
img : ndarray, shape(M, N, [..., P,] 3)

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

as above

The Region Adjacency Graph.
img : ndarray, shape(M, N, [..., P,] 3)
Input image.
border_color : length-3 sequence, optional

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Can you check whether len-4 sequences (including alpha) also work? Or could also work? It would be awesome if they did.

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 5, 2014

Author Contributor

It can be done, I think we can support alpha in all our drawing functions. Matplotlib can certainly interpolate with an alpha channel

Input image.
border_color : length-3 sequence, optional
RGB color of the border of regions. Specifying `None` won't draw
the border. Black by default.

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

I would prefer None to be the default. Additionally, depending on what this is passed to, it would be great if any matplotlib colorspec (e.g. 'k' for black, "#0000FF" for blue) would work here.

border_color : length-3 sequence, optional
RGB color of the border of regions. Specifying `None` won't draw
the border. Black by default.
node_color : length-3 sequeunce, optional

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Sequence (typo)

color mapped between `low_color` and `high_color` depending on their
weight. Edges with low weights are more like `low_color` whereas edges
with high weights are more like `high_color`.
thresh : float, optiona;

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

typo

are draw with `low_color`. Green by default.
high_color : length-3 sequeunce, optional
RGB color of the edges with high weight. If specified, the edges are
color mapped between `low_color` and `high_color` depending on their

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

colormapped (one word)

Examples
--------
>>> from skimage import data, graph, segmentation
>>> img = data.lena()

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Maybe use "coffee" here for consistency.

# Handling the case where one node has multiple labels
# offset is 1 so that regionprops does not ignore 0
offset = 1
for n, d in rag.nodes_iter(data=True):

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Firstly, prefer iteration-based approaches to setup + increment. So here:

for offset, (node, data) in rag.nodes_iter(data=True):
    # [...]
        rag_labels[labels == l] = offset + 1

But secondly, this approach will be ridiculously slow, because you are doing an O(labels.size) operation for each and every label. You should create a numpy array as a map and then do the transformation in one step. See the relabel_sequential function or Tree.get_map from my ultrametric tree library for examples of numpy-arrays-as-maps.

And thirdly, don't use l as a variable name, as it is sometimes hard to read and distinguish from 1. Prefer label. Or anything else. =P

regions = measure.regionprops(rag_labels)
for region in regions:
# Because we kept the offset as 1
rag.node[region['label'] - 1]['centroid'] = region['centroid']

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Ok this is actually super-annoying. I'm beginning to think that we should by convention forbid 0 labels in RAGs. So the 0 label will be ignored by draw_rag. Then all of this +1/-1 goes away. Thoughts?

Another alternative would be to ignore the 0 label and have a completely separate case to deal with it. Both would be preferable to this wrangling, I think.

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 5, 2014

Author Contributor

This only happens when we use regionprops and want to measure properties of regions. I think the correct place to fix this would be in the segmentation module. If we have regions labeled 0 they will always be ignored by regionprops

# Because we kept the offset as 1
rag.node[region['label'] - 1]['centroid'] = region['centroid']

if not border_color is None:

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

if border_color is not None =)

if not border_color is None:
out = segmentation.mark_boundaries(out, rag_labels, color=border_color)

if not high_color is None:

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

if high_color is not None

max_weight = max([d['weight'] for x, y, d in rag.edges_iter(data=True)
if d['weight'] < thresh])
min_weight = min([d['weight'] for x, y, d in rag.edges_iter(data=True)
if d['weight'] < thresh])

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor
  • you don't need the if here.
  • make the list once and then pick out the min and max.
if d['weight'] < thresh])

for n1, n2, data in rag.edges_iter(data=True):

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

remove empty line

norm_weight = ((rag[n1][n2]['weight'] - min_weight) /
(max_weight - min_weight))
out[line] = (norm_weight * high_color +
(1 - norm_weight) * low_color)

This comment has been minimized.

Copy link
@jni

jni Aug 5, 2014

Contributor

Interesting! This is a linear color map, which Matplotlib can create for you. It's probably better for users to be able to select their own. =)

@vighneshbirodkar

This comment has been minimized.

Copy link
Contributor Author

commented Aug 5, 2014

@jni
I have addressed everything except the thick line point. The viewer draws thick lines through matplotlib. The ideal place for a thick line call, if we choose to support it, would be skimage.draw.thick_line IMHO.

@stefanv

This comment has been minimized.

Copy link
Member

commented Aug 6, 2014

An example of how to draw thick lines (that should be turned into a utility
function) can be found in the paper repository:

https://github.com/scikit-image/scikit-image-paper/tree/master/skimage/figures

The graph drawing looks great! I see some weird connections in there,
connecting "across" one another--is that correct?

Labels: @jni should we set the whole label (-1 for background etc.)
situation as a blocker to be resolved by the next release?

@vighneshbirodkar

This comment has been minimized.

Copy link
Contributor Author

commented Aug 6, 2014

@stefanv Yes, In some cases the regains are irregularly shaped, so that is expected

Edit:Regions

out = graph.draw_rag(labels, g, img, colormap=cmap, thresh=30)
plt.figure()
plt.title("RAG with edge weights less than 30, color "
"mapped between green and red.")

This comment has been minimized.

Copy link
@jni

jni Aug 12, 2014

Contributor

cyan and red?

Parameters
----------
labels : ndarray, shape(M, N)

This comment has been minimized.

Copy link
@jni

jni Aug 12, 2014

Contributor

shape (M, N)

(include space) (and same thing below)

The labelled image.
rag : RAG
The Region Adjacency Graph.
img : ndarray, shape(M, N, 3)

This comment has been minimized.

Copy link
@jni

jni Aug 12, 2014

Contributor

I guess we can allow RAG drawing for black and white images, using gray2rgb?

And actually, this just occurred to me: in many applications, the image will only be for illustration, and what one really wants is to see the fine detail of the RAG (e.g. with the cubehelix or hot colormaps). For those applications, even converting a color image to grayscale would be advantageous!

For this, I would add an option, desaturate_image=False, which would call rgb2gray on the image when set to True.

If you place the optional rgb2gray call after the gray2rgb call, it should work seamlessly with black and white images.

Does this make sense?

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 12, 2014

Author Contributor

I am a little confused. How will converting to gray aid visualization ?

Returns
-------
out : ndarray, shape(M, N, [..., P,] 3)

This comment has been minimized.

Copy link
@jni

jni Aug 12, 2014

Contributor

Again, this is only for 2D images, so this should read shape (M, N, 3) (again, with the space)

line = draw.line(r1, c1, r2, c2)

if colormap is not None:
current_color = smap.to_rgba([data['weight']])[0][:-1]

This comment has been minimized.

Copy link
@jni

jni Aug 12, 2014

Contributor

Reduce this to a single call: out[line] = smap.to_rgba(...

I guess that to_rgba can't take a single scalar value?

This comment has been minimized.

Copy link
@vighneshbirodkar

vighneshbirodkar Aug 13, 2014

Author Contributor

No it can't.

@jni

This comment has been minimized.

Copy link
Contributor

commented Aug 12, 2014

@vighneshbirodkar this is also nearly ready, thanks! I added just a few more comments.

@stefanv depends when the release is. It won't be a fun task because of the deprecation schedule. (ie it's not a matter of fixing, but rather putting in all the right deprecation warnings and details in the to-do.) I'm pretty busy until October or so, and I've been the main whiner about this issue, so I imagine I'll be the one to do it. =P And actually I think it's more urgent to fix our morphology. (See #1070 and #1074).

@stefanv

This comment has been minimized.

Copy link
Member

commented Aug 12, 2014

Why the desaturation option, and not leave that to the user?

@jni

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2014

@vighneshbirodkar @stefanv

Why: with many colormap/image combinations, a significant number of edges will be very difficult to see because of low contrast between edge color and image color. Desaturating the image will relieve most of these problems.

Why not leave it to the user: why not leave all drawing to the user? =) The idea of a library is to make common things easy. My prediction (which could admittedly turn out to be false) is that this will be a common operation.

Actually thinking about this function, it might turn out to be the most important of this gsoc, because it will allow visual, interactive development of new RAG-based algorithms, by rapidly seeing which edges are not close to their "true" value. That's an exciting prospect!

Happy to be overruled re: desaturation, but that's the rationale.

@vighneshbirodkar

This comment has been minimized.

Copy link
Contributor Author

commented Aug 15, 2014

I have also update release_dev.txt with details about my RAG contributions.

@coveralls

This comment has been minimized.

Copy link

commented Aug 15, 2014

Coverage Status

Coverage decreased (-0.05%) when pulling 6e09628 on vighneshbirodkar:rag_draw into a363802 on scikit-image:master.

@coveralls

This comment has been minimized.

Copy link

commented Aug 15, 2014

Coverage Status

Coverage decreased (-0.05%) when pulling c43180f on vighneshbirodkar:rag_draw into a363802 on scikit-image:master.

@coveralls

This comment has been minimized.

Copy link

commented Aug 15, 2014

Coverage Status

Coverage decreased (-0.05%) when pulling 344e2a5 on vighneshbirodkar:rag_draw into a363802 on scikit-image:master.

Given a labelled image and its corresponding RAG, draw the nodes and edges
of the RAG on the image with the specified colors. Nodes are markes by
the centroids of the corresposning regions.

This comment has been minimized.

Copy link
@jni

jni Aug 15, 2014

Contributor

"nodes are marked"
"corresponding"

plt.figure()
plt.title("RAG with edge weights less than 30, color "
"mapped between blue and orange.")

This comment has been minimized.

Copy link
@jni

jni Aug 15, 2014

Contributor

Could you add one more example using a continuous colormap? Have a look at this list. I'm quite partial to 'cubehelix' (weirdly not listed there), but any reasonable map from that page would be acceptable.

mapping.
desaturate : bool, optional
Convert the image to grayscale before displaying. Particularly helps
visualiztion when using the `colormap` option.

This comment has been minimized.

Copy link
@jni

jni Aug 15, 2014

Contributor

visualization

@jni

This comment has been minimized.

Copy link
Contributor

commented Aug 15, 2014

@vighneshbirodkar there were a couple of typos, so I've taken the liberty of adding one more task. =P

@vighneshbirodkar

This comment has been minimized.

Copy link
Contributor Author

commented Aug 15, 2014

@jni Cubehelix it is.

@coveralls

This comment has been minimized.

Copy link

commented Aug 15, 2014

Coverage Status

Coverage decreased (-0.04%) when pulling 3f7ce2e on vighneshbirodkar:rag_draw into a363802 on scikit-image:master.

jni added a commit that referenced this pull request Aug 18, 2014
RAG drawing function
@jni jni merged commit 25d6c58 into scikit-image:master Aug 18, 2014
1 check passed
1 check passed
continuous-integration/travis-ci The Travis CI build passed
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.