Skip to content

Commit 86e6449

Browse files
committed
Experiment updates
## experiment.py - replaced _scan_numbers with all_scan_numbers - allow scans to be loaded from NexusScan objects - add get_scan_number() - add get_nearby_scan_numbers() - improved find_scans - add get_value_changes() Add examples and tests. All tests pass. ## xas/container_functions.py - add find_similar_measurements
1 parent 89963ca commit 86e6449

11 files changed

Lines changed: 409 additions & 81 deletions

File tree

docs/examples/examples.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ for scan in exp[-5:]:
5555

5656
### Plotting
5757
```python
58-
from mmg_toolbox import data_file_reader
58+
from mmg_toolbox import data_file_reader, Experiment
5959
from mmg_toolbox.plotting.matplotlib import set_plot_defaults
6060
set_plot_defaults() # changes the matplotlib defaults to larger fonts, thicker lines etc.
6161

@@ -66,4 +66,7 @@ scan.plot.plot(xaxis='axes', yaxis='signal')
6666
# multi-line plot
6767
scan.plot.plot(xaxis='axes', yaxis=['signal', 'signal2'])
6868

69+
# Plotting multiple files
70+
exp = Experiment('/dls/i06/2025/mm1234-1', instrument='i06')
71+
exp.plot.multi_lines(*range(12345, 12355), value='Ta')
6972
```
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
mmg_toolbox example
3+
Example script to load multiple files from a 2D grid scan
4+
"""
5+
6+
import numpy as np
7+
import matplotlib.pyplot as plt
8+
from mmg_toolbox import Experiment
9+
10+
11+
exp = Experiment('/dls/science/groups/das/ExampleData/i16/2dscans_2026/', instrument='i16')
12+
13+
# Add custom ROI
14+
exp.add_roi('new_roi', cen_i=95, cen_j=200, wid_i=40, wid_j=45, image_name='pil3_100k')
15+
16+
scan_numbers = range(1144247, 1144272)
17+
scans = exp.scans(*scan_numbers)
18+
ttl = exp.generate_scans_title(*scan_numbers)
19+
20+
xx, yy, image = exp.generate_mesh(*scan_numbers, axes='sx', values='sy', signal='signal')
21+
22+
plt.figure()
23+
plt.pcolormesh(xx, yy, image)
24+
plt.axis('image')
25+
cb = plt.colorbar(label='Intensity')
26+
plt.xlabel('sx',fontsize=22)
27+
plt.ylabel('sy',fontsize=22)
28+
plt.title(ttl,fontsize=18)
29+
30+
"""
31+
# perform fits and extract data
32+
sx_values = np.zeros(len(scans))
33+
sy_values = np.zeros(len(scans))
34+
area_values = np.zeros(len(scans))
35+
for n, scan in enumerate(scans):
36+
s = scan.map.get_image_shape()
37+
scan.map.add_roi('pilsum', s[0]//2, s[1]//2, s[0], s[1]) # add ROI for whole detector
38+
result = scan.fit.multi_peak_fit(
39+
xaxis='axes', # default scan axes
40+
yaxis='pilsum_total / Transmission / (rc / 300)', #'signal', # default scan values
41+
npeaks=None, # determine the number of peaks automatically
42+
min_peak_power=None,
43+
peak_distance_idx=6,
44+
model='Gaussian',
45+
background='Slope',
46+
plot_result=True
47+
)
48+
sx_values[n] = scan('mean(sx)')
49+
sy_values[n] = scan('mean(sy)')
50+
area_values[n] = result.amplitude # total area under all peaks
51+
#area_values[n] = result.fwhm # width
52+
#area_values[n] = result.center # centre
53+
54+
55+
# Determine the repeat length of the scans
56+
delta_x = np.abs(np.diff(sx_values))
57+
ch_idx_x = np.where(delta_x > delta_x.max()*0.9) # find biggest changes
58+
ch_delta_x = np.diff(ch_idx_x)
59+
rep_len_x = np.round(np.mean(ch_delta_x))
60+
delta_y = np.abs(np.diff(sy_values))
61+
ch_idx_y = np.where(delta_y > delta_y.max()*0.9) # find biggest changes
62+
ch_delta_y = np.diff(ch_idx_y)
63+
rep_len_y = np.round(np.mean(ch_delta_y))
64+
print('Scans in sx are repeating every {} iterations'.format(rep_len_x))
65+
print('Scans in sy are repeating every {} iterations'.format(rep_len_y))
66+
rep_len = int(max(rep_len_x, rep_len_y))
67+
68+
# Reshape into square arrays
69+
# If this is problematic, look at scipy.interpolate.griddata
70+
sx_squareA = sx_values[:rep_len*(len(sx_values)//rep_len)].reshape(-1,rep_len)
71+
sy_squareA = sy_values[:rep_len*(len(sy_values)//rep_len)].reshape(-1,rep_len)
72+
area_squareA = area_values[:rep_len*(len(area_values)//rep_len)].reshape(-1,rep_len)
73+
74+
plt.figure(figsize=(12, 10), dpi=60)
75+
plt.pcolormesh(sx_squareA, sy_squareA, area_squareA, shading='auto')
76+
plt.axis('image')
77+
cb = plt.colorbar(label='Amplitude')
78+
plt.xlabel('sx',fontsize=22)
79+
plt.ylabel('sy',fontsize=22)
80+
plt.title(ttl,fontsize=18)
81+
cb.set_label('Integrated Area',fontsize=22)
82+
"""

examples/example_experiment.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,35 @@
33
Example script using the Experiment class to get files from data folders
44
"""
55

6+
import numpy as np
67
import matplotlib.pyplot as plt
78
from mmg_toolbox import Experiment
89

910
datadir1 = '/dls/science/groups/das/ExampleData/i16/azimuths'
1011
datadir2 = '/dls/science/groups/das/ExampleData/hdfmap_tests/i16/cm37262-1'
1112
exp = Experiment(datadir1, datadir2)
12-
all_scans = exp.all_scan_numbers()
13+
all_scan_nos = exp.all_scan_numbers()
1314

14-
print('\n'.join(exp.scans_str(*all_scans)))
15+
# Print information - all scans
16+
print('\n'.join(exp.scans_str(*all_scan_nos)))
17+
# single scan
18+
print(exp.scan_str(-1)) # -1 is a shortcut for the latest scan, 0 is the first scan
1519

20+
# Load scans
21+
scan = exp.scan(1032510) # NexusDataHolder object (loads data)
22+
print(repr(scan))
23+
scan1, scan2, scan3 = exp.scans(*range(-3, 0)) # NexusScan object (lazy loader)
24+
25+
# Search for scans
26+
scan_nos = exp.get_nearby_scan_numbers(1032510)
27+
scans = exp.find_scans(*scan_nos, **{'_cmd': 'scan energy'})
28+
29+
# Find values that change between scans
30+
values = exp.get_value_changes(*scan_nos)
31+
print('\n'.join(f"{name} : {np.std(array):.2f}" for name, array in values.items()))
32+
33+
34+
# Plotting
1635
rng = range(1032510, 1032521)
1736
# exp.plot(*rng)
1837
exp.plot.surface_3d(*rng)
@@ -21,9 +40,9 @@
2140

2241
exp.plot.detail(-1)
2342

24-
exp.plot.surface_2d(*all_scans[:5], xaxis='eta_fly', signal='signal', values='psi')
25-
exp.plot.surface_3d(*all_scans[:5], xaxis='eta_fly', signal='signal', values='psi')
43+
exp.plot.surface_2d(*all_scan_nos[:5], xaxis='eta_fly', signal='signal', values='psi')
44+
exp.plot.surface_3d(*all_scan_nos[:5], xaxis='eta_fly', signal='signal', values='psi')
2645

27-
exp.plot.lines_3d(*all_scans[:5], xaxis='eta_fly', signal='signal', values='psi')
46+
exp.plot.lines_3d(*all_scan_nos[:5], xaxis='eta_fly', signal='signal', values='psi')
2847

2948
plt.show()

examples/example_i16_xmcd.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,53 @@
77
import numpy as np
88
import matplotlib.pyplot as plt
99
from mmg_toolbox import Experiment
10-
from mmg_toolbox.xas import average_polarised_scans, average_scans
10+
from mmg_toolbox.xas import average_polarised_scans
1111

1212

13-
exp = Experiment('/dls/i16/data/2026/cm44164-9', instrument='i16')
13+
exp = Experiment('/dls/science/groups/das/ExampleData/i16/vortex_2026', instrument='i16')
1414

15-
spectra = exp.load_xas(1145818)
15+
### Single Energy Vortex spectrum ###
16+
spectrum, = exp.load_xas(1146115, element_edge='Ir L32') # scan x 1 3 1 xsp3 1
17+
m = spectrum.metadata
18+
print(f"Spectrum: {spectrum.name} T={m.temp:.1f} K, B={m.mag_field:+3.1f} T, pol='{m.pol}'")
19+
20+
# Processing
21+
processed_spectrum = spectrum.trim(1000, 20000).remove_background('flat')
22+
fig = processed_spectrum.create_figure()
23+
24+
### Incident Energy Scan with Vortex ###
25+
scan, = exp.scans(1146116)
26+
# Add ROI window - xsp3 has 2 channels, 4096 bins. create window around bin 915 == 9150 eV
27+
scan.map.add_roi('new_window', 1, 915, 2, 100, 'xsp3')
28+
spectrum = scan.xas_spectra()
29+
30+
fig2 = spectrum.create_figure() # show the different Windows
1631

17-
for spectrum in spectra:
18-
m = spectrum.metadata
19-
print(f"Spectrum: {spectrum.name} T={m.temp:.1f} K, B={m.mag_field:+3.1f} T, pol='{m.pol}'")
32+
33+
### Merge and compare scans
34+
exp.add_roi('my_window', 1, 915, 2, 100, 'xsp3') # Add ROI to exp so it is added to every scan
35+
spectra = exp.load_xas(*range(1146116, 1146124))
2036

2137
# Processing
2238
spectra = [
23-
spectrum.trim(1000, 30000).remove_background('flat')
39+
spectrum.trim(5, 0).remove_background('flat')
2440
for spectrum in spectra
2541
]
2642

27-
av = average_scans(*spectra)
43+
# Find opposite polarisations, sum similar polarisations
44+
pol1, pol2 = average_polarised_scans(*spectra)
45+
print(f"Found {len(pol1.parents)} spectra with '{pol1.metadata.pol}' polarisation," +
46+
" and {len(pol2.parents)} spectra with '{pol2.metadata.pol}' polarisation")
47+
48+
# Align spectra using cross-correlation
49+
pol1 = pol1.align_spectra('my_window')
50+
pol2 = pol2.align_spectra('my_window')
51+
52+
# Calculate subtraction
53+
xmld = pol1 - pol2
2854

29-
print(repr(av))
55+
fig3 = xmld.create_sum_rules_figure(figsize=[12, 10], dpi=100)
3056

31-
av.create_figure()
3257
plt.show()
3358

3459

mmg_toolbox/beamline_metadata/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class C:
3232
}
3333

3434
REPLACE_NAMES = {
35-
# NEW_NAME: EXPRESSION
35+
# NEW_NAME: EXPRESSION (cannot include NEW_NAME) #TODO: fix this in HdfMap
3636
'_t': '(count_time|counttime|t?(1.0))',
3737
'_cmd': DEFAULT_SCAN_DESCRIPTION.strip('{}'),
3838
}

mmg_toolbox/plotting/exp_plot_manager.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
import matplotlib.pyplot as plt
77
import hdfmap
8-
from ..utils.experiment import Experiment
8+
from ..utils.experiment import Experiment, ScanFile
99
from .matplotlib import (
1010
set_plot_defaults, generate_subplots, plot_lines, plot_2d_surface, plot_3d_surface, plot_3d_lines,
1111
FIG_SIZE, FIG_DPI, DEFAULT_CMAP, Axes3D
@@ -32,7 +32,7 @@ def __init__(self, experiment: Experiment):
3232
def __call__(self, *args, **kwargs):
3333
return self.plot(*args, **kwargs)
3434

35-
def plot(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
35+
def plot(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
3636
xaxis: str = 'axes', yaxis: str | list[str] = 'signal', axes: plt.Axes | None = None, **kwargs) -> plt.Axes:
3737
"""
3838
Create matplotlib figure with a line plot from a or several scans
@@ -57,7 +57,7 @@ def plot(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
5757
axes.set_title(self.exp.generate_scans_title(*scan_files))
5858
return axes
5959

60-
def image(self, scan_file: int | str = -1, hdf_map: hdfmap.NexusMap | None = None,
60+
def image(self, scan_file: ScanFile = -1, hdf_map: hdfmap.NexusMap | None = None,
6161
index: int | tuple | slice | None = None, xaxis: str = 'axes',
6262
axes: plt.Axes | None = None, clim: tuple[float, float] | None = None,
6363
cmap: str = DEFAULT_CMAP, colorbar: bool = False, **kwargs) -> plt.Axes | None:
@@ -79,7 +79,7 @@ def image(self, scan_file: int | str = -1, hdf_map: hdfmap.NexusMap | None = No
7979
return scan.plot.image(index, xaxis, axes, clim, cmap, colorbar, **kwargs)
8080
return None
8181

82-
def detail(self, scan_file: int | str = -1, hdf_map: hdfmap.NexusMap | None = None,
82+
def detail(self, scan_file: ScanFile = -1, hdf_map: hdfmap.NexusMap | None = None,
8383
xaxis: str = 'axes', yaxis: str | list[str] = 'signal',
8484
index: int | tuple | slice | None = None, clim: tuple[float, float] | None = None,
8585
cmap: str = DEFAULT_CMAP, **kwargs) -> plt.Figure:
@@ -107,8 +107,9 @@ def detail(self, scan_file: int | str = -1, hdf_map: hdfmap.NexusMap | None = No
107107
lt.set_title(None)
108108

109109
# Top right - image plot
110-
scan.plot.image(index, xaxis, cmap=cmap, clim=clim, axes=rt)
111-
rt.set_title(None)
110+
if scan.map.image_data:
111+
scan.plot.image(index, xaxis, cmap=cmap, clim=clim, axes=rt)
112+
rt.set_title(None)
112113

113114
# Bottom-Left - details
114115
details = self.exp.scan_str(scan_file, hdf_map=hdf_map)
@@ -122,7 +123,7 @@ def detail(self, scan_file: int | str = -1, hdf_map: hdfmap.NexusMap | None = No
122123
rb.text(-0.2, 1, fit_report, ha='left', va='top', multialignment="left", fontsize=12, wrap=True)
123124
return fig
124125

125-
def multi_lines(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
126+
def multi_lines(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
126127
xaxis: str = 'axes', yaxis: str = 'signal', value: str | None = None,
127128
axes: plt.Axes | None = None, **kwargs) -> plt.Axes:
128129
"""
@@ -162,7 +163,7 @@ def multi_lines(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None =
162163
plt.colorbar(sm, ax=axes, label=value_label)
163164
return axes
164165

165-
def multi_plot(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
166+
def multi_plot(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
166167
xaxis: str = 'axes', yaxis: str | list[str] = 'signal', value: str | None = None,
167168
subplots: tuple[int, int] = (4, 4), **kwargs) -> list[tuple[plt.Figure, plt.Axes]]:
168169
"""
@@ -190,7 +191,29 @@ def multi_plot(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = N
190191
ax.set_title(ttl_exp)
191192
return fig_ax
192193

193-
def surface_2d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
194+
def multi_image(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
195+
index: int | tuple | slice | None = None, xaxis: str = 'axes',
196+
clim: tuple[float, float] | None = None, cmap: str = DEFAULT_CMAP,
197+
colorbar: bool = False, **kwargs) -> list[tuple[plt.Figure, plt.Axes]]:
198+
"""
199+
Plot scan images in matplotlib figure (if available)
200+
:param scan_files: scan number or filename (multiple allowed)
201+
:param hdf_map: hdfmap object or None
202+
:param index: int, detector image index, 0-length of scan, if None, use centre index
203+
:param xaxis: name or address of xaxis dataset
204+
:param clim: [min, max] colormap cut-offs (None for auto)
205+
:param cmap: str colormap name (None for auto)
206+
:param colorbar: False/ True add colorbar to plot
207+
:param kwargs: additional arguments for plot_detector_image
208+
:return: [(Figure, Axes)] for each scan
209+
"""
210+
fig_axes = generate_subplots(len(scan_files))
211+
for scan, (fig, axes) in zip(scan_files, fig_axes):
212+
self.image(scan, axes=axes, hdf_map=hdf_map, index=index, xaxis=xaxis,
213+
clim=clim, cmap=cmap, colorbar=colorbar, **kwargs)
214+
return fig_axes
215+
216+
def surface_2d(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
194217
xaxis: str = 'axes', signal: str = 'signal', values: str | None = None,
195218
axes: plt.Axes | None = None, clim: tuple[float, float] | None = None,
196219
axlim: str = 'image', **kwargs) -> plt.Axes:
@@ -226,7 +249,7 @@ def surface_2d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = N
226249
axes.figure.colorbar(surf, ax=axes, label=signal)
227250
return axes
228251

229-
def lines_3d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
252+
def lines_3d(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
230253
xaxis: str = 'axes', signal: str = 'signal', values: str | None = None,
231254
axes: Axes3D | None = None, legend: bool = False, **kwargs) -> Axes3D:
232255
"""
@@ -265,7 +288,7 @@ def lines_3d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = Non
265288
axes.legend()
266289
return axes
267290

268-
def surface_3d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = None,
291+
def surface_3d(self, *scan_files: ScanFile, hdf_map: hdfmap.NexusMap | None = None,
269292
xaxis: str = 'axes', signal: str = 'signal', values: str | None = None,
270293
axes: Axes3D | None = None, clim: tuple[float, float] | None = None,
271294
axlim: str = 'image', **kwargs) -> Axes3D:
@@ -302,7 +325,7 @@ def surface_3d(self, *scan_files: int | str, hdf_map: hdfmap.NexusMap | None = N
302325
axes.set_zlabel(signal_label)
303326
return axes
304327

305-
def metadata(self, *scan_files: int | str, values: str | list[str], hdf_map: hdfmap.NexusMap | None = None,
328+
def metadata(self, *scan_files: ScanFile, values: str | list[str], hdf_map: hdfmap.NexusMap | None = None,
306329
axes: plt.Axes | None = None) -> plt.Axes:
307330
"""
308331
Create matplotlib figure with plot of metadata vs scan number

0 commit comments

Comments
 (0)