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

Intensity log scale plot #1727

Merged

Conversation

ericpre
Copy link
Member

@ericpre ericpre commented Sep 10, 2017

Following #1725, this PR add support for plotting signal on a log scale with the intensity_scale argument of the plot, which can be set to 'linear', 'log' or None.:

  • add intensity_scale to the plot method of the BaseSignal class,
  • scale can be toggled between linear and logarithmic scale by pressing the l key,
  • add power_spectrum and shifted parameters to the plot method of complex signals,
  • plot power spectum with zero frequency component shifted to the center for FFT by default,
  • add tests,
  • update doc,
  • ready for review.

By default if the signal to plot has the Signal.FFT node in the metadata, the power spectrum will be plotted on a log scale (the complex FFT can still be plotted by setting power_spectrum=False).

@ericpre
Copy link
Member Author

ericpre commented Oct 15, 2017

Fix #543.

@ericpre
Copy link
Member Author

ericpre commented Feb 19, 2018

@woozey, in case you fancy having a look! ;)

@thomasaarholt
Copy link
Contributor

I've been playing around with the log plotting. Works nicely!

One minor error: My (EELS) spectrum image has some negative values in it, but certainly not all of the values are negative:

>>> eels
<EELSSpectrum, title: EELS Spectrum Image, dimensions: (24, 56|2048)>
>>> eels.T.plot(intensity_scale='log')
ValueError: Data has no positive values, and therefore can not be log-scaled.

@thomasaarholt
Copy link
Contributor

thomasaarholt commented Feb 26, 2018

fft.plot(shifted=True/False) seems to have no effect on my fft signal.

image

@ericpre
Copy link
Member Author

ericpre commented Feb 26, 2018

Does the ValueError comes from matplotlib? Can you give the traceback, to make sure?
I don't think that there is anything sensible to do from our side.

@thomasaarholt
Copy link
Contributor

Based on a quick google search I think you are correct. I can double check tomorrow.

Any thought on the shifted?

… is plotted shifted. For convenience set the same options to the roi plot as the original signal plot.
@thomasaarholt
Copy link
Contributor

I haven't got time right now to look for the file with negative counts. I'm pretty sure that this was matplotlib, not you.

…sity_log_scale_plot

# Conflicts:
#	hyperspy/drawing/image.py
#	hyperspy/drawing/signal1d.py
@woozey
Copy link
Contributor

woozey commented Apr 16, 2018

Nice PR! It works fine for me. I'll try some more use cases and will give more feedback. Some comments that I have now:

  • Plotting shifted FFT by default is good for non-experienced users who just want to see their FFT as they are used to, though for advanced users plotting may give an impression that the FFT itself is shifted. Would it be better warning a user that the display shows shifted FFT while the signal itself is not?
  • Another one with regards to shifting:
holo.fft(shifted=True).plot()
holo.fft(shifted=False).plot()

Would result in:
screen shot 2018-04-16 at 14 46 41
screen shot 2018-04-16 at 14 46 59
whereas following the idea of your implementation it probably has to look the same. Would it be better to check the shifted node in metadata to decide if the display needs to be shifted or not?

  • And the last one, having in mind that you have done so much work figuring all of that out, it would be nice to have a possibility to plot any other signal on a log scale, something like s.plot(log_scale = True). Would you be able to implement that (probably in different PR) without a lot of extra efforts?

@ericpre
Copy link
Member Author

ericpre commented Apr 16, 2018

Thanks for the feedback! There are indeed quite a few things which need to be discussed here.

I did look at using the "shifted" node of the metadata, but I ended up not being happy about it.
Regarding the defaults, we could consider having different defaults for different kind of signal, which may become particularly relevant after the split of hyperspy/hyperspy-extension

This PR implements the intensity_scale option to the plot, which should allow you to plot any signal on a log scale. If not, this is a bug! ;)

@woozey
Copy link
Contributor

woozey commented Apr 16, 2018

I did look at using the "shifted" node of the metadata, but I ended up not being happy about it.

What is the reason? Is there anything that should be improved there?

Regarding the defaults, we could consider having different defaults for different kind of signal, which may become particularly relevant after the split of hyperspy/hyperspy-extension

I wouldn't say my worry are defaults, as I'm happy to have it shifted by default. My real worry is that it is confusing for a user getting the plots shifted although the actual data is not. So I still would strongly advice to put a warning or something in order to inform a user.

This PR implements the intensity_scale option to the plot, which should allow you to plot any signal on a log scale. If not, this is a bug! ;)

Indeed! I have overseen that for no reason! It works excellent! Thanks!

@ericpre
Copy link
Member Author

ericpre commented Apr 16, 2018

I don't remember exactly, but I don't think there was any particular reason, I just wanted to keep it simple and also hear what other people would expect as default behaviour before going forward.
Maybe it would be good to change the argument name from shifted to centered in the plot method? So that there is no confusion with the shifted node of the metadata, because this argument is only about having the plot with the (0,0) component in the centre regardless of the actual data.

The problem with warning is that people doesn't like it... maybe we could log this at the "info" level.

Normally, they should be a shortcut "l" (as L) to toggle from linear to log scale.

@woozey
Copy link
Contributor

woozey commented Apr 17, 2018

I don't remember exactly...
Maybe it would be good to change the argument name from shifted to centered in the plot method?

This sounds reasonable to me. The name does not really matter but that certainly will help avoiding confusion! Another option could be doing similar to matplotlib.pyplot.imshow(), i.e. calling the parameter origin and passing string to it, but this is up to you.

The problem with warning is that people doesn't like it... maybe we could log this at the "info" level.

Perhaps... I don't know what is the best practice in such cases.

Normally, they should be a shortcut "l" (as L) to toggle from linear to log scale.

This one works too!

@ericpre
Copy link
Member Author

ericpre commented Jul 12, 2018

Yes, indeed, "origin" got its drawback too... I did as you suggested and did two versions for the name of the parameter:

  1. with "shifted"
  2. with "shift"

I prefer the second one because it is clear what it does regardless if the FFT is shifted or not. Moreover, I find it is more convenient to use with ROI:

import hyperspy.api as hs
holo = hs.datasets.example_signals.object_hologram()
fft = holo.fft(True)
fft.plot(True) # True is for the power spectrum

With the version with "shifted":

import hyperspy.api as hs
holo = hs.datasets.example_signals.object_hologram()
fft = holo.fft(True)
fft.plot(True, shifted=True) # True is for the power spectrum, but we also need to specify `shifted=True` if not the FFT is shifted back...

@woozey
Copy link
Contributor

woozey commented Jul 17, 2018

Now I see the point for shift and shifted! Yes, I think shift would be better than. I'll test the latest version in a while.

@woozey
Copy link
Contributor

woozey commented Jul 17, 2018

Just gave it a try. Looks good to me!

…sity_log_scale_plot

# Conflicts:
#	hyperspy/drawing/image.py
Copy link
Member

@francisco-dlp francisco-dlp left a comment

Choose a reason for hiding this comment

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

In addition to the small comments I wonder if it wouldn't be better to replace the fft shift kwarg with a shift_fft or maybe better (for discovery) fft_shift method. The main advantage is that it'll enable going from unshifted to shifted and back to unshifted. It'll also make be more explicit that the operation doubles the size in memory of the data temporarily.

'not supported, falling back to the default '
'(linear).')
# if the `norm` kwargs is passed to matplotlib, we use it
norm = kwargs.pop('norm', None)
Copy link
Member

Choose a reason for hiding this comment

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

I would be good to add an example to the docstring and the user guide of how to use mpl's norm to e.g. display images with negative numbers using SymLogNorm


By default both methods calculate FFT and IFFT with origin at (0, 0) (not in the centre of FFT). Use `shifted=True` option to
By default, both methods calculate FFT and IFFT with origin at (0, 0) (not in the centre of FFT). Use ``shifted=True`` option to
Copy link
Member

Choose a reason for hiding this comment

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

I think that it would be good to mention here that ROIs don't work on "unshifted" FFTs shifted to plot.

@@ -53,6 +53,10 @@
plot_markers : bool, default True
Plot markers added using s.add_marker(marker, permanent=True).
Note, a large number of markers might lead to very slow plotting.
intensity_scale : {None, 'linear', 'log'}, default is None.
Copy link
Member

Choose a reason for hiding this comment

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

What about using "auto" instead of None. The advantage is that it's more explicit. I know that in HyperSpy None often means "auto", maybe something else to fix?

# set the image normalisation
if self.intensity_scale == 'log':
from matplotlib.colors import LogNorm
norm = LogNorm(vmin=self.vmin, vmax=self.vmax)
Copy link
Member

Choose a reason for hiding this comment

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

What happens if norm is in kwargs and intensity_scale == "log"?

Wouldn't it be clearer if we use norm instead of intensity_scale as the name for this kwarg. If it is a mpl norm, we use it directly, if "linear" or "log" or "auto" we do as it currently does for intensity_scale.

'not supported, falling back to the default '
'(linear).')
# if the `norm` kwargs is passed to matplotlib, we use it
norm = kwargs.pop('norm', None)
Copy link
Member

Choose a reason for hiding this comment

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

If the norm is an mpl norm it could be an instance or a class (currently it assumes that it is an instance). If it is a class, it could create the instance passing vmin and max to the constructor.

@@ -57,6 +57,9 @@ def add_phase_ramp(self, ramp_x, ramp_y, offset=0):
self.phase = phase

def plot(self,
power_spectrum=False,
intensity_scale=None,
shift=False,
Copy link
Member

Choose a reason for hiding this comment

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

What about calling this shift_fft instead?

self._plot.signal_title = self.metadata.General.title
title = self.metadata.General.title
if kwargs.get('power_spectrum', False):
title = title.replace('FFT', 'Power spectrum')
Copy link
Member

Choose a reason for hiding this comment

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

This assumes that FFT is contained in the title. It may not be the case because the user has changed it or the data comes from an external source.

title = self.metadata.General.title
if kwargs.get('power_spectrum', False):
title = title.replace('FFT', 'Power spectrum')
self._plot.quantity_label = 'Power spectral density'
Copy link
Member

Choose a reason for hiding this comment

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

What about the units?

@@ -3256,7 +3268,7 @@ def fft(self, shifted=False, **kwargs):

Parameters
----------
shifted : bool, optional
shift : bool, optional
If True, the origin of FFT will be shifted in the centre (Default: False).
Copy link
Member

Choose a reason for hiding this comment

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

to the centre?

im_fft.metadata.set_item('Signal.FFT.shifted', shifted)
im_fft.metadata.set_item('Signal.FFT.shifted', shift)
if hasattr(self.metadata.Signal, 'quantity'):
self.metadata.Signal.__delattr__('quantity')
Copy link
Member

Choose a reason for hiding this comment

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

Why removing the quantity? Couldn't we compute the right one with pint? Same for units.

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure if pint can do it properly.

Copy link
Member

@francisco-dlp francisco-dlp left a comment

Choose a reason for hiding this comment

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

In addition to the comments above I think that it would be better to replace the shift kwarg of fft with a fft_shift method. In this way:

  1. It'll be possible to "unshift" a "shifted" fft
  2. It'll be more explicit that the operations requires temporarily doubling the size in memory.

@ericpre
Copy link
Member Author

ericpre commented Aug 28, 2018

In addition to the comments above I think that it would be better to replace the shift kwarg of fft with a fft_shift method. In this way:

  1. It'll be possible to "unshift" a "shifted" fft

This is already the possible, isn't

  1. It'll be more explicit that the operations requires temporarily doubling the size in memory.

I don't see what is doubling the memory?

@francisco-dlp
Copy link
Member

I may be wrong, but I think that fftshift returns a copy of the data. Therefore, it requires 2x the memory compared to an "unshifted" fft.

@francisco-dlp
Copy link
Member

@ericpre, is this ready for review?

@ericpre
Copy link
Member Author

ericpre commented Aug 30, 2018

I may be wrong, but I think that fftshift returns a copy of the data. Therefore, it requires 2x the memory compared to an "unshifted" fft.

Even if it's return a copy (I haven't checked), for plotting it is computed only using the current index, so this is possibly not a big deal in term of memory issue?

Other than that, yes, it is ready for review.

@francisco-dlp
Copy link
Member

Sorry my point wasn't fully clear: I meant the shifted kwarg in fft. If you prefer, I merge this and I can submit a new PR to turn shfit into a fft_shift method.

@ericpre
Copy link
Member Author

ericpre commented Aug 30, 2018

Yes, let's do this in another PR.

@francisco-dlp francisco-dlp merged commit 86515e3 into hyperspy:RELEASE_next_minor Aug 30, 2018
@ericpre ericpre mentioned this pull request Aug 30, 2018
6 tasks
@ericpre ericpre deleted the intensity_log_scale_plot branch August 30, 2018 12:54
@ericpre ericpre mentioned this pull request Nov 5, 2019
8 tasks
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