Add contour to SliceViewer

It would be helpful to add a single contour with variable Z height to SliceViewer. Scrubbing a slider to change the contour value gives a quick check that data is not obscured due to the choice of colormap, particularly in high dynamic range or noisy data.

I’ve mocked up an example of the sort of thing I’d like below in workbench 4.1.

Thanks,

Ian

# The following line helps with future compatibility with Python 3
# print must now be used as a function, e.g print('Hello','World')
from __future__ import (absolute_import, division, print_function, unicode_literals)

# import mantid algorithms, numpy and matplotlib
from mantid.simpleapi import *

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.widgets import Slider

import numpy as np

iris_qs = np.array ([0.441681334, 0.483671882, 0.525249485, 0.566709615, 0.607871243, 0.648712688,
                     0.68905912, 0.729197261, 0.768800911, 0.808151513, 0.847076439, 0.885409749,
                     0.923423859, 0.96080983, 0.997833347, 1.034331276, 1.070284393, 1.105540291,
                     1.140349517, 1.174558088, 1.208021461, 1.240977444, 1.273158206, 1.304792439,
                     1.335739401, 1.36598279, 1.3953958, 1.424187456, 1.45212377, 1.47940327, 1.505903526,
                     1.531610579, 1.556417755, 1.580501346, 1.603752442, 1.626075215, 1.647628302, 1.668313535,
                     1.688046387, 1.70696709, 1.724921875, 1.74203834, 1.758237222, 1.77350999, 1.787795858,
                     1.80114719, 1.81360256, 1.825102652, 1.835641411, 1.845178726, 1.853782376])

hbar = 0.0006582119514
# time constant in ns
Tau = 0.164575
# 1/time constant in meV
T = 1/Tau * hbar
#jump length in Angstrom
l = 10


def fwhm(qlist = iris_qs):
    """ Return dictionary of broadenings for list of Q according to Chudley Elliot """
    out = [(1/Tau) * (1-((np.sin(q*l))/(q*l))) for q in qlist]
    return(dict(zip(qlist,out)))

def EISF(qlist = iris_qs, r = 1.2):
    """ Return dictionary of simplified peak intensity with Q for 3-fold jump """
    out = [(1.0/3.0)*(1+2*((np.sqrt(3)*q*r)**-1*np.sin(np.sqrt(3)*q*r))) for q in qlist]
    return(dict(zip(qlist,out)))
    
ce = fwhm()
eisf = EISF()

xs = np.linspace(-5, 5, 1000)
x0 = 0
ss = []
for (q) in sorted(ce):
    b = ce[q] * eisf[q]
    ss += [(1/np.pi)* (0.5*b/((x-x0)**2+(0.5*b)**2))for x in xs]  

print (len(xs))
print (len(ss))

example = CreateWorkspace(DataX = xs, DataY = ss, NSpec=51, UnitX = "Energy")

X = example.readX(0)
Y = iris_qs
x,y = np.meshgrid(X,Y)
z = example.extractY()

fig = plt.figure()
ax = fig.add_subplot(111)
# move bottom up to make room for slider
plt.subplots_adjust(bottom=0.25)

# plot
surface = ax.pcolormesh(x, y, z, cmap=cm.viridis, alpha =0.5, antialiased=True)

# create slider
axamp = plt.axes([0.05, 0.15, 0.85, 0.03])
zcontour = Slider(axamp, 'S', np.amin(z), np.amax(z), np.mean(z))
# plot initial contour value
contour1 = ax.contour(x, y, z, levels =[np.mean(z)], cmap=cm.Oranges, linewidths=1)
print(type(contour1))
# What to do when the slider is moved
def update(val):
    #delete current contour
    ax.collections.pop()
    amp = zcontour.val
    contourl = ax.contour(x, y, z, levels =[amp], cmap=cm.Oranges, linewidths=1)
    fig.canvas.draw_idle()

zcontour.on_changed(update)

plt.show()

So we can decide the best way to implement this, how often would you need to do these checks?

Hi Anthony,
If this is available I will use it every time I collect data. At the moment I can do a similar thing by adjusting the max/min of the colourmap multiple times, but it is a bit slow and clunky.
The way I’m imagining, there would be a tick box to “show contour” which then gives a text entry so a specific value with a linked slider for scrubbing through values. A spinbox also/instead would be fine - whatever gives the best performance. Would it also be possible to change the colourmap scales to spinboxes?