Skip to content

Commit 7129134

Browse files
committed
ENH: add xray-fluorescence map example
1 parent 5e5ffe1 commit 7129134

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

xrf/scan_3624.h5

7.8 MB
Binary file not shown.

xrf/xrf_interact.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# https://drive.google.com/open?id=0B5vxvuZBEEfTRGdXZ2NXUjNKUUk
2+
3+
import h5py
4+
import matplotlib.gridspec as gridspec
5+
import matplotlib.widgets as mwidgets
6+
from matplotlib import path
7+
import numpy as np
8+
9+
10+
# uncomment this to set the backend
11+
# import matplotlib
12+
# matplotlib.use('Qt4Agg')
13+
import matplotlib.pyplot as plt
14+
15+
16+
class XRFInteract(object):
17+
def __init__(self, counts, positions, fig=None, pos_order=None,
18+
norm=None):
19+
20+
if pos_order is None:
21+
pos_order = {'x': 0,
22+
'y': 1}
23+
# extract x/y data
24+
self.x_pos = xpos = positions[pos_order['x']]
25+
self.y_pos = ypos = positions[pos_order['y']]
26+
self.points = np.transpose((xpos.ravel(), ypos.ravel()))
27+
# sort ouf the normalization
28+
if norm is None:
29+
norm = np.ones_like(self.x_pos)
30+
31+
norm = np.atleast_3d(norm[:])
32+
self.counts = counts[:] / norm
33+
34+
# compute values we will use for extents below
35+
dx = np.diff(xpos.mean(axis=0)).mean()
36+
dy = np.diff(ypos.mean(axis=1)).mean()
37+
left = xpos[:, 0].mean() - dx/2
38+
right = xpos[:, -1].mean() + dx/2
39+
top = ypos[0].mean() - dy/2
40+
bot = ypos[-1].mean() + dy/2
41+
42+
# create a figure if we must
43+
if fig is None:
44+
import matplotlib.pyplot as plt
45+
fig = plt.figure(tight_layout=True)
46+
# clear the figure
47+
fig.clf()
48+
# set the window title (look at the tool bar)
49+
fig.canvas.set_window_title('XRF map')
50+
self.fig = fig
51+
# set up the figure layout
52+
gs = gridspec.GridSpec(2, 1, height_ratios=[4, 1])
53+
54+
# set up the top panel (the map)
55+
self.ax_im = fig.add_subplot(gs[0, 0], gid='imgmap')
56+
self.ax_im.set_xlabel('x [?]')
57+
self.ax_im.set_ylabel('y [?]')
58+
self.ax_im.set_title(
59+
'shift-click to select pixel, '
60+
'alt-drag to draw region, '
61+
'right-click to reset')
62+
63+
# set up the lower axes (the average spectrum of the ROI)
64+
self.ax_spec = fig.add_subplot(gs[1, 0], gid='spectrum')
65+
self.ax_spec.set_ylabel('counts [?]')
66+
self.ax_spec.set_xlabel('bin number')
67+
self.ax_spec.set_yscale('log')
68+
self.ax_spec.set_title('click-and-drag to select energy region')
69+
self._EROI_txt = self.ax_spec.annotate('ROI: all',
70+
xy=(0, 1),
71+
xytext=(0, 5),
72+
xycoords='axes fraction',
73+
textcoords='offset points')
74+
self._pixel_txt = self.ax_spec.annotate('map average',
75+
xy=(1, 1),
76+
xytext=(0, 5),
77+
xycoords='axes fraction',
78+
textcoords='offset points',
79+
ha='right')
80+
81+
# show the initial image
82+
self.im = self.ax_im.imshow(self.counts[:, :, :].sum(axis=2),
83+
cmap='viridis',
84+
interpolation='nearest',
85+
extent=[left, right, bot, top]
86+
)
87+
# and colorbar
88+
self.cb = self.fig.colorbar(self.im, ax=self.ax_im)
89+
90+
# and the ROI mask (overlay in red)
91+
self.mask = np.ones(self.x_pos.shape, dtype='bool')
92+
self.mask_im = self.ax_im.imshow(self._overlay_image,
93+
interpolation='nearest',
94+
extent=[left, right, bot, top],
95+
zorder=self.im.get_zorder())
96+
self.mask_im.mouseover = False # do not consider for mouseover text
97+
98+
# set up the spectrum, to start average everything
99+
self.spec, = self.ax_spec.plot(
100+
self.counts.mean(axis=(0, 1)),
101+
lw=2)
102+
103+
# set up the selector widget for the specturm
104+
self.selector = mwidgets.SpanSelector(self.ax_spec,
105+
self._on_span,
106+
'horizontal',
107+
useblit=True, minspan=2,
108+
span_stays=True)
109+
# placeholder for the lasso selector
110+
self.lasso = None
111+
# hook up the mouse events for the XRF map
112+
self.cid = self.fig.canvas.mpl_connect('button_press_event',
113+
self._on_click)
114+
115+
@property
116+
def _overlay_image(self):
117+
ret = np.zeros(self.mask.shape + (4,), dtype='uint8')
118+
if np.all(self.mask):
119+
return ret
120+
ret[:, :, 0] = 255
121+
ret[:, :, 3] = 100 * self.mask.astype('uint8')
122+
return ret
123+
124+
def _on_click(self, event):
125+
# not in the right axes, bail
126+
ax = event.inaxes
127+
if ax is None or ax.get_gid() != 'imgmap':
128+
return
129+
# if right click, clear ROI
130+
if event.button == 3:
131+
return self._reset_spectrum()
132+
133+
# if alt, start lasso
134+
if event.key == 'alt':
135+
return self._lasso_on_press(event)
136+
# if shift, select a pixel
137+
if event.key == 'shift':
138+
return self._pixel_select(event)
139+
140+
def _reset_spectrum(self):
141+
self.mask = np.ones(self.x_pos.shape, dtype='bool')
142+
self.mask_im.set_data(self._overlay_image)
143+
new_y_data = self.counts.mean(axis=(0, 1))
144+
self.spec.set_ydata(new_y_data)
145+
self._pixel_txt.set_text('map average')
146+
self.ax_spec.relim()
147+
self.ax_spec.autoscale(True, axis='y')
148+
self.fig.canvas.draw_idle()
149+
150+
def _pixel_select(self, event):
151+
152+
x, y = event.xdata, event.ydata
153+
# get index by assuming even spacing
154+
# TODO use kdtree?
155+
diff = np.hypot((self.x_pos - x), (self.y_pos - y))
156+
y_ind, x_ind = np.unravel_index(np.argmin(diff), diff.shape)
157+
158+
# get the spectrum for this point
159+
new_y_data = self.counts[y_ind, x_ind, :]
160+
self.mask = np.zeros(self.x_pos.shape, dtype='bool')
161+
self.mask[y_ind, x_ind] = True
162+
self.mask_im.set_data(self._overlay_image)
163+
self._pixel_txt.set_text(
164+
'pixel: [{:d}, {:d}] ({:.3g}, {:.3g})'.format(
165+
y_ind, x_ind,
166+
self.x_pos[y_ind, x_ind],
167+
self.y_pos[y_ind, x_ind]))
168+
169+
self.spec.set_ydata(new_y_data)
170+
self.ax_spec.relim()
171+
self.ax_spec.autoscale(True, axis='y')
172+
self.fig.canvas.draw_idle()
173+
174+
def _on_span(self, vmin, vmax):
175+
vmin, vmax = map(int, (vmin, vmax))
176+
new_image = self.counts[:, :, vmin:vmax].sum(axis=2)
177+
new_max = new_image.max()
178+
self._EROI_txt.set_text('ROI: {}:{}'.format(vmin, vmax))
179+
self.im.set_data(new_image)
180+
self.im.set_clim(0, new_max)
181+
self.fig.canvas.draw_idle()
182+
183+
def _lasso_on_press(self, event):
184+
self.lasso = mwidgets.Lasso(event.inaxes, (event.xdata, event.ydata),
185+
self._lasso_call_back)
186+
187+
def _lasso_call_back(self, verts):
188+
p = path.Path(verts)
189+
190+
new_mask = p.contains_points(self.points).reshape(*self.x_pos.shape)
191+
self.mask = new_mask
192+
self.mask_im.set_data(self._overlay_image)
193+
new_y_data = self.counts[new_mask].mean(axis=0)
194+
self._pixel_txt.set_text('lasso mask')
195+
self.spec.set_ydata(new_y_data)
196+
self.ax_spec.relim()
197+
self.ax_spec.autoscale(True, axis='y')
198+
self.fig.canvas.draw_idle()
199+
200+
201+
# def make_text_demo(inp='BNL', n_chan=1000):
202+
# '''Make some synthetic data
203+
# '''
204+
# from matplotlib.figure import Figure
205+
# from matplotlib.backends.backend_agg import FigureCanvas
206+
# fig = Figure()
207+
# canvas = FigureCanvas(fig)
208+
# canvas.draw()
209+
# im_shape = fig.canvas.get_width_height()[::-1] + (3,)
210+
# t = fig.text(.5, .5, '', fontsize=350, ha='center', va='center')
211+
# counts = np.random.rand(*(im_shape[:2] + (n_chan,)))
212+
# x = np.linspace(0, 1, n_chan)
213+
# for j, l in enumerate(inp):
214+
# t.set_text(l)
215+
# fig.canvas.draw()
216+
# im = np.fromstring(fig.canvas.tostring_rgb(),
217+
# dtype=np.uint8).reshape(im_shape)
218+
# im = 255 - np.mean(im, axis=2, keepdims=True)
219+
# counts += (150 * im * np.exp(-500 * ((1+j)/(len(inp) + 1) - x)**2)
220+
# .reshape(1, 1, -1))
221+
# del im
222+
#
223+
# return counts
224+
#
225+
#
226+
# counts = make_text_demo()
227+
# N, M = counts.shape[:2]
228+
# X, Y = np.meshgrid(range(M), range(N))
229+
# pos = np.stack([.01*X + 100, .01*Y + 50])
230+
#
231+
# xrf = XRFInteract(counts, pos)
232+
233+
# to look at a data file
234+
fn = 'scan_3624.h5'
235+
F = h5py.File(fn, 'r')
236+
g = F['xrfmap']
237+
238+
xrf = XRFInteract(g['detsum']['counts'][:], g['positions']['pos'][:],
239+
norm=g['scalers']['val'][:, :, 0])
240+
241+
# un comment out this line to use 'interacitve' mode
242+
# plt.ion()
243+
plt.show()

0 commit comments

Comments
 (0)