Source code for drtsans.api
from drtsans.dataobjects import getDataType, DataType, IQazimuthal
# https://docs.mantidproject.org/nightly/algorithms/CloneWorkspace-v1.html
# https://docs.mantidproject.org/nightly/algorithms/CreateSingleValuedWorkspace-v1.html
# https://docs.mantidproject.org/nightly/algorithms/Minus-v1.html
# https://docs.mantidproject.org/nightly/algorithms/RebinToWorkspace-v1.html
# https://docs.mantidproject.org/nightly/algorithms/RenameWorkspace-v1.html
# mtd is https://docs.mantidproject.org/nightly/api/python/mantid/api/AnalysisDataServiceImpl.html
from mantid.simpleapi import CloneWorkspace, CreateSingleValuedWorkspace, Minus, mtd, RebinToWorkspace
import numpy as np
__all__ = ["subtract_background", "NoDataProcessedError"]
[docs]
class NoDataProcessedError(RuntimeError):
def __init__(self, message="No data was processed. Check the input data."):
super().__init__(message)
[docs]
def subtract_background(input_workspace, background, scale=1.0, scale_error=0.0, output_workspace=None):
r"""
Subtract a prepared background from a prepared sample.
Perform a rebin if sample and background have different binning.
**Mantid algorithms used:**
:ref:`CloneWorkspace <algm-CloneWorkspace-v1>`
:ref:`CreateSingleValuedWorkspace <algm-CreateSingleValuedWorkspace-v1>`
:ref:`Minus <algm-Minus-v1>`
:ref:`Multiply <algm-Multiply-v1>`
:ref:`RebinToWorkspace <algm-RebinToWorkspace-v1>`
Parameters
----------
input_workspace: str, ~mantid.api.MatrixWorkspace, ~drtsans.dataobjects.IQmod, ~drtsans.dataobjects.IQazimuthal
Sample workspace.
background: str, ~mantid.api.MatrixWorkspace, ~drtsans.dataobjects.IQmod, ~drtsans.dataobjects.IQazimuthal
Background workspace.
scale: float
Rescale background intensities by this multiplicative factor before
subtraction from the sample.
scale_error: float
Uncertainty in scale factor
output_workspace: str
Name of the sample corrected by the background. If :py:obj:`None`, then
``input_workspace`` will be overwritten. In the case of using data from
:py:obj:`~drtsans.dataobjects`, this parameter is ignored.
Returns
-------
~mantid.api.MatrixWorkspace
"""
# get the types of input_workspace and background
id_input = getDataType(input_workspace)
id_background = getDataType(background)
# the data types must be the same
if id_input != id_background:
raise ValueError("Cannot subtract background(type={}) from data(type={})".format(id_input, id_background))
# convert IQmod to a mantid workspace
if id_input == DataType.IQ_MOD:
input_workspace = input_workspace.to_workspace()
background = background.to_workspace()
id_input = DataType.WORKSPACE2D
# do the math
if id_input == DataType.IQ_AZIMUTHAL:
# verify that the qx and qy match
try:
assert np.all(input_workspace.qx == background.qx), "Qx must match"
except AssertionError:
print(f"{type(input_workspace)}: Qx size: {input_workspace.qx.shape}, {background.qx.shape}")
print(f"Qx min: {input_workspace.qx[0]}, {background.qx[0]}")
print(f"Qx max: {input_workspace.qx[-1]}, {background.qx[-1]}")
assert np.all(input_workspace.qx == background.qx), "Qx must match"
assert np.all(input_workspace.qy == background.qy), "Qy must match"
# do the math
y = input_workspace.intensity - scale * background.intensity
e = np.sqrt(
np.square(input_workspace.error)
+ np.square(scale * background.error)
+ np.square(scale_error * background.intensity)
)
return IQazimuthal(
intensity=y,
error=e,
qx=input_workspace.qx,
qy=input_workspace.qy,
delta_qx=input_workspace.delta_qx,
delta_qy=input_workspace.delta_qy,
wavelength=input_workspace.wavelength,
)
elif id_input == DataType.WORKSPACE2D:
if output_workspace is None:
output_workspace = str(input_workspace)
# get handle on input_workspace
input_workspace = mtd[str(input_workspace)]
workspaces_to_delete = []
# prepare to scale the background out-of-place so the background is not modified for subsequent calls
# make the background match the input_workspace binning if possible
if input_workspace.isHistogramData() and background.isHistogramData():
# rebin the background to match the input_workspace
# this will do nothing if the x-axis is already the same
# put it in the output_workspace to save space in memory
background_rebinned = RebinToWorkspace(
WorkspaceToRebin=background,
WorkspaceToMatch=input_workspace,
OutputWorkspace=mtd.unique_hidden_name(),
)
else:
# subtract will check that the x-axis is identical
# otherwise the subtraction will fail
background_rebinned = CloneWorkspace(InputWorkspace=background, OutputWorkspace=mtd.unique_hidden_name())
workspaces_to_delete.append(str(background_rebinned))
# need to create a special object if the uncertainty in the scale was specified
if scale_error != 0.0:
scale = CreateSingleValuedWorkspace(
DataValue=scale, ErrorValue=scale_error, OutputWorkspace=mtd.unique_hidden_name()
)
workspaces_to_delete.append(str(scale))
# this takes care of the uncertainties as well
background_rebinned *= scale
# the minus algorithm makes sure the workspaces are compatible
# and does the correct thing with the uncertainties
Minus(
LHSWorkspace=input_workspace,
RHSWorkspace=background_rebinned,
OutputWorkspace=output_workspace,
)
for name in workspaces_to_delete:
if mtd.doesExist(name):
mtd.remove(name)
return mtd[output_workspace]
else:
# DataType.IQ_CRYSTAL is not implemented currently
# this will catch other types if they are added later as well
raise NotImplementedError('Cannot do operation with "{}"'.format(id_input))
def _set_uncertainty_from_numpy(wksp, uncertainty):
"""Inner function to override the uncertainties on a workspace. This is a detail that
is easy to mess up"""
# TODO add support for more workspace types / dimensions
# this works well for single value workspaces
for i in range(uncertainty.shape[0]):
wksp.setE(i, np.array([uncertainty[i]]))
return wksp