Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 165 additions & 20 deletions mmg_toolbox/diffraction/msmapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from mmg_toolbox import version_info
from mmg_toolbox.nexus import nexus_writer as nw
from mmg_toolbox.fitting import FitResults
from .plotly_colourmaps import PLOTLY_CMAPS

from collections.abc import Callable


MSMAPPER_VERSION = '1.9'
Expand Down Expand Up @@ -57,17 +60,20 @@ def create_bean(input_files, output_file, start=None, shape=None, step=None,
"inputs": input_files, # Filename of scan file
"output": output_file,
# Output filename - must be in processing directory, or somewhere you can write to
"splitterName": "gaussian", # one of the following strings "nearest", "gaussian", "negexp", "inverse"
# one of the following strings "nearest", "gaussian", "negexp", "inverse"
"splitterName": "gaussian",
"splitterParameter": 2.0,
# splitter's parameter is distance to half-height of the weight function.
# If you use None or "" then it is treated as "nearest"
"scaleFactor": 2.0,
# the oversampling factor for each image; to ensure that are no gaps in between pixels in mapping
"step": step,
# a single value or list if 3 values and determines the lengths of each side of the voxels in the volume
"start": start, # location in HKL space of the bottom corner of the array.
# location in HKL space of the bottom corner of the array.
"start": start,
"shape": shape, # size of the array to create for reciprocal space volume
"reduceToNonZero": reduce_box # True/False, if True, attempts to reduce the volume output
# True/False, if True, attempts to reduce the volume output
"reduceToNonZero": reduce_box
}
if output_mode:
bean['outputMode'] = output_mode
Expand All @@ -86,12 +92,13 @@ def create_bean(input_files, output_file, start=None, shape=None, step=None,


def update_msmapper_nexus(filename: str, hkl_slice: tuple[np.ndarray, np.ndarray, np.ndarray],
orthogonal_axes: tuple[np.ndarray, np.ndarray, np.ndarray] | None = None,
average_axes: tuple[np.ndarray, np.ndarray, np.ndarray] | None = None,
orthogonal_axes: tuple[np.ndarray,
np.ndarray, np.ndarray] | None = None,
average_axes: tuple[np.ndarray,
np.ndarray, np.ndarray] | None = None,
fit_options: dict | None = None, h_result: FitResults | None = None,
k_result: FitResults | None = None, l_result: FitResults | None = None,
q_result: FitResults | None = None, tth_result: FitResults | None = None):

"""
Update the NeXus file generated by msmapper with additional analysis data.

Expand Down Expand Up @@ -191,7 +198,8 @@ def update_msmapper_nexus(filename: str, hkl_slice: tuple[np.ndarray, np.ndarray

with h5py.File(filename, 'a') as hdf:
entry = nw.add_nxentry(hdf, 'analysis', definition=None, default=True)
nw.add_nxprocess(entry, 'process', program='mmg_toolbox', version=version_info())
nw.add_nxprocess(entry, 'process',
program='mmg_toolbox', version=version_info())

# hkl
h = hdf['/processed/reciprocal_space/h-axis'][:]
Expand All @@ -205,7 +213,8 @@ def update_msmapper_nexus(filename: str, hkl_slice: tuple[np.ndarray, np.ndarray
k_axis['k'] = h5py.SoftLink('/processed/reciprocal_space/k-axis')
nw.add_nxfield(k_axis, 'intensity', k_slice)

l_axis = nw.add_nxdata(entry, 'l_axis', ['l'], 'intensity', default=True)
l_axis = nw.add_nxdata(
entry, 'l_axis', ['l'], 'intensity', default=True)
l_axis['l'] = h5py.SoftLink('/processed/reciprocal_space/l-axis')
nw.add_nxfield(l_axis, 'intensity', l_slice)

Expand All @@ -216,7 +225,8 @@ def update_msmapper_nexus(filename: str, hkl_slice: tuple[np.ndarray, np.ndarray
nw.add_nxfield(orthog, 'Qz', qz, units='1/angstrom')
orthog['volume'] = h5py.SoftLink('/processed/reciprocal_space/volume')
orthog['weight'] = h5py.SoftLink('/processed/reciprocal_space/weight')
nw.add_attr(orthog, Qx_indices=1, Qy_indices=2, Qz_indices=0, default_slice=len(l) // 2)
nw.add_attr(orthog, Qx_indices=1, Qy_indices=2,
Qz_indices=0, default_slice=len(l) // 2)

# volume averaged at each magnitude
qvsi = nw.add_nxdata(entry, 'wavevector', ['q'], 'intensity')
Expand All @@ -231,20 +241,155 @@ def update_msmapper_nexus(filename: str, hkl_slice: tuple[np.ndarray, np.ndarray
fit_proc = nw.add_nxprocess(entry, 'peak_fit',
program='mmg_toolbox.utils.fitting.multipeakfit',
**fit_options)
nw.add_nxnote(fit_proc, 'fit_results_h', 'fit results for h-axis', data=str(h_result))
nw.add_nxnote(fit_proc, 'fit_results_h',
'fit results for h-axis', data=str(h_result))
nw.add_nxparameters(fit_proc, 'fit_data_h', **h_result.results())
nw.add_nxnote(fit_proc, 'fit_results_k', 'fit results for k-axis', data=str(k_result))
nw.add_nxnote(fit_proc, 'fit_results_k',
'fit results for k-axis', data=str(k_result))
nw.add_nxparameters(fit_proc, 'fit_data_k', **k_result.results())
nw.add_nxnote(fit_proc, 'fit_results_l', 'fit results for l-axis', data=str(l_result))
nw.add_nxnote(fit_proc, 'fit_results_l',
'fit results for l-axis', data=str(l_result))
nw.add_nxparameters(fit_proc, 'fit_data_l', **l_result.results())
nw.add_nxnote(fit_proc, 'fit_results_q', 'fit results for wavevector magnitude |Q|', data=str(q_result))
nw.add_nxnote(fit_proc, 'fit_results_q',
'fit results for wavevector magnitude |Q|', data=str(q_result))
nw.add_nxparameters(fit_proc, 'fit_data_q', **q_result.results())
nw.add_nxnote(fit_proc, 'fit_results_tth', 'fit results for remapped two-theta', data=str(tth_result))
nw.add_nxparameters(fit_proc, 'fit_data_tth', **tth_result.results())
nw.add_nxnote(fit_proc, 'fit_results_tth',
'fit results for remapped two-theta', data=str(tth_result))
nw.add_nxparameters(fit_proc, 'fit_data_tth',
**tth_result.results())

nw.add_nxfield(h_axis, 'fit', h_result.fit_data(
h, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(k_axis, 'fit', k_result.fit_data(
k, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(l_axis, 'fit', l_result.fit_data(
l, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(qvsi, 'fit', q_result.fit_data(
wavevector, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(tthvsi, 'fit', tth_result.fit_data(
tth, ntimes=1)[1], add_to_signal=True)


def plot_voxel_image(
h: np.ndarray,
k: np.ndarray,
l: np.ndarray,
values: np.ndarray,
title: str | None = None,
cmap: str = "inferno",
figsize: tuple[int, int] = (9, 6),
isomin: float = 0.001,
isomax: float = 1.0,
):
"""
Plots an interactive 3D volume using Plotly and ipywidgets.

:param h: 3D array containing h coordinates.
:type h: np.ndarray

:param k: 3D array containing k coordinates.
:type k: np.ndarray

:param l: 3D array containing l coordinates.
:type l: np.ndarray

:param values: 3D array containing the voxel values.
:type values: np.ndarray

nw.add_nxfield(h_axis, 'fit', h_result.fit_data(h, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(k_axis, 'fit', k_result.fit_data(k, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(l_axis, 'fit', l_result.fit_data(l, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(qvsi, 'fit', q_result.fit_data(wavevector, ntimes=1)[1], add_to_signal=True)
nw.add_nxfield(tthvsi, 'fit', tth_result.fit_data(tth, ntimes=1)[1], add_to_signal=True)
:param title: Title to be displayed on the plot.
:type title: str | None

:param cmap: Name of the colourmap to be used in plot. Defaults to "inferno".
:type cmap: str

:param figsize: Tuple containing the width and height of the plot in inches.
:type figsize: tuple[int,int] | None

:param isomin: Float that sets the minimum boudary for the iso-surface plot.
:type isomin: float

:param isomax: Float that sets the maximum boundary for the iso-surface plot.
:type isomax: float

:returns: VBox. ipywidgets VBox containing controls and the figure widget.
:rtype ipywidgets.Box:

:returns: fig.write_image. A function to save the figure to a given filepath
:rtype fig.write_image: function (str) -> void
"""

Comment thread
DanPorter marked this conversation as resolved.
import plotly.graph_objects as go
from ipywidgets import VBox, HBox, Dropdown, FloatText

fig = go.FigureWidget(
data=go.Volume(
x=h.flatten(),
y=k.flatten(),
z=l.flatten(),
value=values,
colorscale=cmap,
isomin=isomin,
isomax=isomax,
opacity=0.1,
surface_count=10,
showscale=False,
),
layout={
'title': title,
'scene': {
'xaxis': {'title': 'H (r.l.u.)'},
'yaxis': {'title': 'K (r.l.u.)'},
'zaxis': {'title': 'L (r.l.u.)'},
'aspectmode': 'data',
'camera': {
'eye': {
'x': 1.5,
'y': 1.5,
'z': 1.5,
}
}
},
'width': figsize[0] * 96,
'height': figsize[1] * 96,
},
)

colourmap_dropdown = Dropdown(
options=PLOTLY_CMAPS,
value=cmap,
description='Colourmap:',
style={'description_width': 'initial'}
)

isomin_input = FloatText(
value=isomin,
description="isomin:",
style={"description_width": "50px"},
layout={"width": "150px"},
)

isomax_input = FloatText(
value=isomax,
description="isomax:",
style={"description_width": "50px"},
layout={"width": "150px"},
)

def update_plot(_) -> None:
cmap = colourmap_dropdown.value
vmin = isomin_input.value
vmax = isomax_input.value

with fig.batch_update():
fig.data[0].colorscale = cmap
fig.data[0].isomin = vmin
fig.data[0].isomax = vmax

colourmap_dropdown.observe(update_plot, names='value')
isomin_input.observe(update_plot, names='value')
isomax_input.observe(update_plot, names='value')

iso_controls = HBox([isomin_input, isomax_input])

widget = VBox([colourmap_dropdown, iso_controls, fig])
return widget, fig.write_image
96 changes: 96 additions & 0 deletions mmg_toolbox/diffraction/plotly_colourmaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
PLOTLY_CMAPS = [
'aggrnyl',
'agsunset',
'blackbody',
'bluered',
'blues',
'blugrn',
'bluyl',
'brwnyl',
'bugn',
'bupu',
'burg',
'burgyl',
'cividis',
'darkmint',
'electric',
'emrld',
'gnbu',
'greens',
'greys',
'hot',
'inferno',
'jet',
'magenta',
'magma',
'mint',
'orrd',
'oranges',
'oryel',
'peach',
'pinkyl',
'plasma',
'plotly3',
'pubu',
'pubugn',
'purd',
'purp',
'purples',
'purpor',
'rainbow',
'rdbu',
'rdpu',
'redor',
'reds',
'sunset',
'sunsetdark',
'teal',
'tealgrn',
'turbo',
'viridis',
'ylgn',
'ylgnbu',
'ylorbr',
'ylorrd',
'algae',
'amp',
'deep',
'dense',
'gray',
'haline',
'ice',
'matter',
'solar',
'speed',
'tempo',
'thermal',
'turbid',
'armyrose',
'brbg',
'earth',
'fall',
'geyser',
'prgn',
'piyg',
'picnic',
'portland',
'puor',
'rdgy',
'rdylbu',
'rdylgn',
'spectral',
'tealrose',
'temps',
'tropic',
'balance',
'curl',
'delta',
'oxy',
'edge',
'hsv',
'icefire',
'phase',
'twilight',
'mrybm',
'mygbm',
]
Loading
Loading