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

NEW: Implementation of plot_images utility function #454

Merged
merged 67 commits into from Apr 6, 2015

Conversation

jat255
Copy link
Member

@jat255 jat255 commented Jan 29, 2015

Resolves #452.

This PR can be used as a starting point for the plot_images function. The majority of the code has been lifted from the plot_decomposition_loadings() method, and adapted so I could plot whichever images I wanted.

HOLDS:

FOR DEBATE:

  • debate merits of _deepcopy
  • change default tight_layout ?

TODO:

  • add testing notebook to hyperspy demos
  • let x and y axes be undefined and remove if the case
  • look into perc with vmin/vmax in image.plot()
  • replace example of EDS data with data from pburdet
  • remove print statements from image.plot() code
  • implement (or not) for 3D RGB images
  • docstring like numpy style
  • raise ValueError for incorrect scalebar input
  • investigate reshaping
  • pep8 compliance
  • move imports to beginning of file
  • Documentation in user guide
  • improve labeling of similar signals
  • Prevent plot_images() from causing iteration of prior calls to image.plot()
  • If images have different scales, they get squeezed. The aspect of plt.imshow should be optimized as it is in image.plot()
  • refactor plot_scalebar as scalebar (None, 'all', or list of int choosing which plots should have the scalebar)
  • one option for axes (None, 'default' (which is ticks and labels), 'ticks' (no labels))
  • Handling of input if a Signal instance is supplied instead of a list (like the output of get_decomposition_loadings() This actually already worked because Signal is iterable (I think), so I just added some checks to make sure it's a Signal if not a list. I'd be happy for suggestions of improvement on this part.
  • Adding option of fixing the colormap so it is shared between all images, rather than having independent ones for all the individual images
  • Plot scale bar option
  • Plot axes option
  • Pass extra args and kwargs to imshow
  • Option to disable titles
  • Add figure keyword to pass to existing MPL figure
  • Set default figsize to k * (per_row, rows) where k = np.max(plt.rcParams['figure.figsize']) / np.max(per_row, rows)
  • change default label_list to list of signal titles (metadata.General.title)
  • enable plotting when label_list is not the same length, printing useful output to user
  • implement single option label, which can be a str or a list
  • add support for RGB images
  • refactor signals to images
  • implement padding option to control spacing between images
  • Investigate switching to GridSpec for layout?
    • I don't think this is necessary, since using rect in plt.tight_layout solves the issue of overlapping colorbar
  • implement single option for colorbar (None, 'default', or 'single')
  • Fix colorbar placement
  • default cmap could be the one set generally with pyplot.set_cmap()
  • The label of the axis should be the same as image.plot(): "name (units)"
  • Should this function return a list of axes as plot_spectra?

@francisco-dlp francisco-dlp added this to the 0.8 milestone Jan 29, 2015
@francisco-dlp
Copy link
Member

Nice! Of course this needs to be documented in the User Guide to be ready to merge.

What do you thing about the following suggestions:

  • Add plot scale bar option.
  • Add plot_axes option.
  • Pass extra args and kwargs to imshow.
  • Add a figure keyword to pass an existing MPL figure.
  • Se the default figsize to: k * (per_row, rows) where k = np.max(plt.rcParams['figure.figsize']) / np.max(per_row, rows)

@jat255
Copy link
Member Author

jat255 commented Jan 29, 2015

I think all of those are good ideas. I've added them to the todo list. I'm working on the shared colorbar option currently.

@jat255
Copy link
Member Author

jat255 commented Jan 29, 2015

I've implemented the scalebar code using the Scale_Bar widget class. Is this the recommended way? It seems to work okay for me, but it takes what seems like a long time for the bars to appear (it's almost instantaneous, but I can watch them showing up, which seems like it is slow).

I suppose people won't be plotting enough images to make this an issue, but I was curious about the slowdown.

@jat255
Copy link
Member Author

jat255 commented Jan 29, 2015

The default figsize formula needed some tweaks, but I think I've got it working the way you intended. Care to comment on if it looks alright?

# If using a single colorbar, add it
if plot_colorbar and single_colorbar:
f.subplots_adjust(right=0.8)
cbar_ax = f.add_axes([0.9, 0.1, 0.03, 0.8])
Copy link
Member Author

Choose a reason for hiding this comment

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

This is not very flexible, and leads to the colorbar overlapping some of the images if the per_row value is high enough (see figure). There must be a better way to do this that makes it more flexible?

figure_1

Add interpolation parameter
@pburdet
Copy link
Contributor

pburdet commented Jan 30, 2015

Nice function. I had a go with a list of images exported by get_lines_intensity and it works well. I have few suggestion.

utils.plot.plot_images(s.get_lines_intensity())
  • The default label_list could be the metadata.General.title of each images
  • single_colorbar overlaps the images as you spotted. The should be a way to place it on the right of the plots.

figure_8

  • I got a problem with per_row = 2 and per_row = 1

capture

figure_2

  • When all options disable, the images are too far apart for my taste. Could we have a "padding" option?

figure_10

  • You should be able to plot if the length of the lable_list is different from the length of the images list. If it is too long take only the first one, if it is two short repeat the label_list.
  • The difference between label_list and signal_label is not easy too understand. I suggest one single option call labels. If it is a str then it works as signal_label, if it is a list of str then it works as label_list.

@pburdet
Copy link
Contributor

pburdet commented Jan 30, 2015

Following...

  • One option for label. It could be None, 'default' (the title of the images), str or a list of str.
  • One option for colorbar. It could be None, 'default' or 'single'
  • plot_scalebar should be scalebar for consistency. It could be None, 'default', 'single' (could be interesting to have this option)
  • One option for axes. None, 'default', 'ticks' (only the ticks)

@jat255
Copy link
Member Author

jat255 commented Jan 30, 2015

Thanks for the comments, @pburdet. I'll work on implementing these suggestions.

As for the per_row issue, I cannot reproduce it on my system. Using the call

utils.plot.plot_images(si_EDS.get_lines_intensity(), per_row=<2 OR 1>)

I get the following figures, which look okay (aside from the complete lack of signal, but that's a different problem 😏)

perrow1
perrow2

@francisco-dlp
Copy link
Member

It just came to my mind that plot_images needs to support RGB images. It is not difficult to implement, just add:

from hyperspy.misc import rgb_tools

 if rgb_tools.is_rgbx(data):
    plot_colorbar = False 
    data = rgb_tools.rgbx2regular_array(data, plot_friendly=True)

@francisco-dlp
Copy link
Member

By the way, wouldn't it be better to rename "signals" to "images"?

@jat255
Copy link
Member Author

jat255 commented Jan 30, 2015

Probably :)

1. Force disabling of single colorbar if any included image is RGB
2. Reworked handling of `data` so it can be plotted by imshow whether RGB or not
3. Fixed bug (new line 492) that forced all plotted images to have same size
4. Axes will be labeled as 'pixels' if undefined in the actual object
5. If `plot_colorbar` is set, and a mix of RGB and regular Signals is provided, colorbars will be plotted for the Signals (but not the RGB images)
@jat255
Copy link
Member Author

jat255 commented Jan 30, 2015

I believe RGB plotting should now be fully implemented. I tried to make it as signal-agnostic as possible, so it is possible to plot a mix of RGB images and regular signals. Here is example output of three RGB images with 2 line intensity Signals:

utils.plot.plot_images([rgbIm1, rgbIm2, si_EDS.get_lines_intensity()[1], rgbIm3, si_EDS.get_lines_intensity()[2]], plot_colorbar=True, per_row=3)

mixedsignals

Please feel free to test this and make sure I didn't leave anything out.

…yperspy into ENH_plot_images_function

Solves conflict in PR github.com:#3

Conflicts:
	hyperspy/drawing/utils.py

Also changed handling of RGB information to maintain a boolean list (same length as list of inputs) that tells whether or not signal at that position is an RGB image
@jat255
Copy link
Member Author

jat255 commented Mar 12, 2015

Ok, here's a working example for the 3D RGB images:

utils.plot.plot_images(im[:-1], per_row=10, axes_decor=None,tight_layout=True)

plot_images_rgb2

@jat255
Copy link
Member Author

jat255 commented Mar 12, 2015

As for the deepcopy() question, I think we have to choose a lesser of two evils, no? (Until the new slicing method is implemented) Either an existing image.plot() will update when calling plot_images(), or we will have to make a copy of the data.

After quickly testing, with the deepcopy() method, it does appear that the data remains in memory after the figure is closed, persisting until the ipython kernel is restarted. Is there any way to force python to clear that data when the figure is closed? That could be a good solution.

@francisco-dlp
Copy link
Member

This is an ugly (but hopefully less evil than the alternatives) workaround:

amc = im.axes_manager.deepcopy()
ambk = im.axes_manager
im.axes_manager = amc
for image in im:
    # do something
image.axes_manager = ambk
del amc

If we decide to use it, we should leave a note in the code indicating that this should be replaced with the proper alternative once it is available.

@pburdet
Copy link
Contributor

pburdet commented Mar 26, 2015

Should we use a lower value for namefrac? Here is an example where 0.5 doesn't give a good result

figure_1

colorbar='multi',
scalebar=None,
scalebar_color='white',
interp='nearest',
Copy link
Member

Choose a reason for hiding this comment

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

Is the interp argument necessary given that **kwargs are passed to imshow?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, you're correct that interpolation will work as a keyword. If this is done however, then the interpolation will default to matplotlib's rcParam value. If this is okay, then I have no problem having interpolation as a keyword. Perhaps we could temporarily override the default to 'nearest', as shown here?

@francisco-dlp francisco-dlp changed the title ENH: Implementation of plot_images utility function NEW: Implementation of plot_images utility function Mar 28, 2015
@francisco-dlp
Copy link
Member

#489 should fix #160 that is currently holding this PR.

@jat255
Copy link
Member Author

jat255 commented Mar 31, 2015

Should we use a lower value for namefrac? Here is an example where 0.5 doesn't give a good result

Probably. 0.5 was a guess based off of what I was working with at the time, but I'm sure different situations will call for different thresholds. What about if we make it adjustable and use 0.4 as a default?

@jat255
Copy link
Member Author

jat255 commented Mar 31, 2015

Here's an example of the new namefrac_thresh parameter:

si_EDS = load("plot_images_eds.hdf5")
im = si_EDS.get_lines_intensity()
for i in [0.6,0.8]:
    utils.plot.plot_images(im, per_row=2, tight_layout=True, axes_decor='off', 
                           suptitle_fontsize=20, colorbar='single', 
                           scalebar='all', scalebar_color='white',namefrac_thresh=i,
                           padding={'top':0.85,'bottom':0.10,'left':0.05,'right':0.85,'wspace':0.10,'hspace':0.20})
    fig = gcf()
    fig.canvas.set_window_title('threshold = ' + repr(i))

plot_image-namefrac_thresh

@jat255
Copy link
Member Author

jat255 commented Apr 1, 2015

The perc option for image contrast is now implemented. It works (in my testing) when using both single and multi colorbars. I basically copied the optimize_contrast method from drawing/image.py as a private function.

Here's it is action:

Multi colorbars:

im1_3 = signals.Image(np.random.random((2, 3, 64, 64)))
im1_3.metadata.General.title = 'multi-dimensional image'
plt.set_cmap('RdYlBu')

for i in [0, 0.25, 0.5]:
    utils.plot.plot_images(im1_3,namefrac_thresh=0, perc=i,per_row=2)
    plt.tight_layout(rect=[0, 0, 1, 0.9])
    gcf().canvas.set_window_title('perc = ' + repr(i))

plot_image-perc_multi

Single colorbars:

im1_3 = signals.Image(np.random.random((2, 3, 64, 64)))
im1_3.metadata.General.title = 'multi-dimensional image'
plt.set_cmap('RdYlBu')

for i in [0, 0.25, 0.49]:
    utils.plot.plot_images(im1_3,namefrac_thresh=0, perc=i,per_row=2,colorbar='single')
    plt.tight_layout(rect=[0, 0, 0.9, 0.9])
    gcf().canvas.set_window_title('perc = ' + repr(i))

plot_image-perc_single

@jat255
Copy link
Member Author

jat255 commented Apr 1, 2015

Undefined axes traits now will cause the image axes to not be labeled, rather than changing to a default x/y and pixels, per the discussion on 03/12/15. See below for example in action.

Undefined axis name:

im1_3 = signals.Image(np.random.random((2, 3, 64, 64)))
im1_3.metadata.General.title = 'multi-dimensional image'
im1_3.axes_manager.signal_axes[0].units = 'nm'
im1_3.axes_manager.signal_axes[1].units = 'nm'
im1_3.axes_manager.signal_axes[0].name = 'x'
# im1_3.axes_manager.signal_axes[1].name = 'y'
utils.plot.plot_images(im1_3,namefrac_thresh=0, perc=0, axes_decor='all',tight_layout=True)
gcf().canvas.set_window_title('undefined y-axis name')

plot_image-axes-undefined

All axes traits defined:

im1_3 = signals.Image(np.random.random((2, 3, 64, 64)))
im1_3.metadata.General.title = 'multi-dimensional image'
im1_3.axes_manager.signal_axes[0].units = 'nm'
im1_3.axes_manager.signal_axes[1].units = 'nm'
im1_3.axes_manager.signal_axes[0].name = 'x'
im1_3.axes_manager.signal_axes[1].name = 'y'
utils.plot.plot_images(im1_3,namefrac_thresh=0, perc=0, axes_decor='all',tight_layout=True)
gcf().canvas.set_window_title('all axes defined')

plot_image-axes-defined

@jat255
Copy link
Member Author

jat255 commented Apr 1, 2015

I've tried to play around some with the memory issue by explicitly deleting the things that are deepcopyed (like @francisco-dlp suggested), but I cannot get it to work. Even if I del everything that is copied (ims and images), the memory continues to grow upon subsequent calls to plot_images. I've also tried using the gc garbage collector interface by using gc.collect() after deleting the , but this does not appear to work either...

I'm not sure at this point what is actually being copied vs. not, and why things won't clear from memory. I guess somewhere (even after deleting and closing figures) there are some pointers that are left over to the copies, which is why Python won't delete them fully? I'm pretty stumped on this one, so if anyone has suggestions, I'd be happy to try.

@pburdet
Copy link
Contributor

pburdet commented Apr 2, 2015

Thanks a lot.

As a big data user, I'm for trying to avoid as much as possible any deepcopy. I don't thing we need it, do we?
I don't think we should store data in the test folder of hyperspy. Have a look in #497, I solved this problem by letting the user download the data from a server (as in the EDS tutorial). You can do the same in the documentation.

@vidartf
Copy link
Member

vidartf commented Apr 3, 2015

@jat255 I've had some similar experiences while developing the UI. Does it leak for other MPL backends than Qt? The MPL Qt backend is a bit lazy in cleaning up its references, and as Qt is a C++ library, that can throw the GC off a bit.

@pburdet
Copy link
Contributor

pburdet commented Apr 6, 2015

Thanks a lot @jat255. Let's merge it as it is.

pburdet added a commit that referenced this pull request Apr 6, 2015
NEW: Implementation of plot_images utility function
@pburdet pburdet merged commit 3f47b66 into hyperspy:master Apr 6, 2015
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

4 participants