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

annotate with two different colors based on threshold #104

Merged
merged 1 commit into from
Jul 9, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions trackpy/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ def plot_traj(traj, colorby='particle', mpp=1, label=False, superimpose=None,
ptraj = plot_traj # convenience alias

@make_axes
def annotate(centroids, image, circle_size=170, color='g',
invert=False, ax=None):
def annotate(centroids, image, circle_size=170, color=None,
invert=False, ax=None, split_category=None, split_thresh=None):
"""Mark identified features with white circles.

Parameters
Expand All @@ -146,17 +146,36 @@ def annotate(centroids, image, circle_size=170, color='g',
image : image array (or string path to image file)
circle_size : size of circle annotations in matplotlib's annoying
arbitrary units, default 170
color : string
default 'g'
color : single matplotlib color or a list of multiple colors
default None
invert : If you give a filepath as the image, specify whether to invert
black and white. Default True.
ax : matplotlib axes object, defaults to current axes

split_category : string, parameter to use to split the data into sections
default None
split_thresh : single value or list of ints or floats threshold to split
particles into sections for plotting in multiple colors
default None

Returns
------
axes
"""
import matplotlib.pyplot as plt
from itertools import tee, izip
from collections import Iterable

# https://docs.python.org/2/library/itertools.html
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return izip(a, b)

if color is None:
color = 'g'
if not (split_thresh, Iterable):
Copy link
Contributor

Choose a reason for hiding this comment

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

I know the train's already left the station, but I don't see how this test wouldn't always evaluate to True. Did you mean to write if not isinstance(split_thresh, Iterable)?

In any case, a string also counts as iterable, so there has to be another way.

Sorry I've been doing nothing but writing comments. I'll be back on my development computer by tomorrow, I promise :).

Copy link
Member

Choose a reason for hiding this comment

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

My fault. This is what I get for late-night merging.

On Wednesday, July 9, 2014, Nathan Keim notifications@github.com wrote:

In trackpy/plots.py:

 Returns
 ------
 axes
 """
 import matplotlib.pyplot as plt
  • from itertools import tee, izip
  • from collections import Iterable

I know the train's already left the station, but I don't see how this test
wouldn't always evaluate to True. Did you mean to write if not
isinstance(split_thresh, Iterable)?

In any case, a string also counts as iterable, so there has to be another
way.

Sorry I've been doing nothing but writing comments. I'll be back on my
development computer by tomorrow, I promise :).


Reply to this email directly or view it on GitHub
https://github.com/soft-matter/trackpy/pull/104/files#r14724711.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This bit of code is to handle the case where split_thresh is a single number. I turn it into a list because split_thresh[0] needs to be valid

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the code, as written, will never put anything into a list, scalar or not. A multi-element tuple always evaluates to True.

I can say that I definitely get the wrong behavior when I try this code at the IPython prompt.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the case I was trying to handle:
tp.annotate(f, frames[0],color=['purple','green'],split_category='signal',split_thresh=10)
I see now that it is broken. What is the proper way to check if something is iterable?

Copy link
Member

Choose a reason for hiding this comment

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

isinstance(foo, Iterable) will return True if foo is iterable (and properly implements the ABC stuff from collections).

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, Tom! I've been doing it a much less elegant way. I guess the only complication is if you'd like strings to count as single values; then you'd need something like isinstance(foo, Iterable) and not isinstance(foo, str). But it looks like that's not the case in this code.

Copy link
Member

Choose a reason for hiding this comment

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

A wonderful phrase I learned last night, 'duck typing brings a can of worms: either you go fishing with them or eat them'

split_thresh = [split_thresh]

# The parameter image can be an image object or a filename.
if isinstance(image, basestring):
Expand All @@ -167,10 +186,30 @@ def annotate(centroids, image, circle_size=170, color='g',
ax.imshow(image, origin='upper', shape=image.shape, cmap=plt.cm.gray)
ax.set_xlim(0, image.shape[1])
ax.set_ylim(0, image.shape[0])
ax.scatter(centroids['x'], centroids['y'],
s=circle_size, facecolors='none', edgecolors=color)

if split_category is None:
if np.size(color) > 1:
Copy link
Member

Choose a reason for hiding this comment

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

does this interact badly with strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's fine with strings. One of my successful test cases was: tp.annotate(f, frames[0],color='purple'). I added this exception after trying tp.annotate(f, frames[0],color=['purple','green']), and the annotation circles alternated between the two colors-- not a good result. np.size('green')=1, np.size(['green','purple']=2

Copy link
Member

Choose a reason for hiding this comment

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

Indeed: np.size('green') is 1 even though len('green') is five. This was news to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll have to remember that. Thanks, Becca!

raise ValueError("multiple colors specified, no split category specified")
ax.scatter(centroids['x'], centroids['y'],
s=circle_size, facecolors='none', edgecolors=color)
else:
if len(color) != len(split_thresh) + 1:
raise ValueError("number of colors must be number of thresholds plus 1")
low = centroids[split_category] < split_thresh[0]
ax.scatter(centroids['x'][low], centroids['y'][low],
s=circle_size, facecolors='none', edgecolors=color[0])

for c, (bot, top) in izip(color[1:-1], pairwise(split_thresh)):
indx = ((centroids[split_category]) >= bot) & ((centroids[split_category]) < top)
ax.scatter(centroids['x'][indx], centroids['y'][indx],
s=circle_size, facecolors='none', edgecolors=c)

high = centroids[split_category] >= split_thresh[-1]
ax.scatter(centroids['x'][high], centroids['y'][high],
s=circle_size, facecolors='none', edgecolors=color[-1])
return ax


@make_axes
def mass_ecc(f, ax=None):
"""Plot each particle's mass versus eccentricity."""
Expand Down