Unverified Commit 6e35ac97 authored by Peterson, Peter's avatar Peterson, Peter Committed by GitHub
Browse files

Merge pull request #30374 from mantidproject/22_rebin_ragged

Add RebinRagged algorithm
parents 2b24d708 b5eac941
......@@ -639,9 +639,15 @@ bool Algorithm::executeInternal() {
}
// Throw because something was invalid
if (numErrors > 0) {
std::stringstream msg;
msg << "Some invalid Properties found: [ ";
for (auto &error : errors) {
msg << error.first << " ";
}
msg << "]";
notificationCenter().postNotification(
new ErrorNotification(this, "Some invalid Properties found"));
throw std::runtime_error("Some invalid Properties found");
throw std::runtime_error(msg.str());
}
}
}
......
......@@ -117,9 +117,9 @@ public:
group.execute();
m_testee.setProperty("InputWorkspaces", "group");
m_testee.setProperty("OutputWorkspace", "out");
TS_ASSERT_THROWS_EQUALS(m_testee.execute(), const std::runtime_error &e,
std::string(e.what()),
"Some invalid Properties found");
TS_ASSERT_THROWS_EQUALS(
m_testee.execute(), const std::runtime_error &e, std::string(e.what()),
"Some invalid Properties found: [ InputWorkspaces ]");
}
void testWSWithoutDxValues() {
......
......@@ -186,7 +186,7 @@ public:
TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("DataType", "Calibrated"))
TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e,
std::string(e.what()),
"Some invalid Properties found")
"Some invalid Properties found: [ DataType ]")
}
void test_D20_scan() {
......
......@@ -142,6 +142,7 @@ set(PYTHON_PLUGINS
algorithms/PoldiCreatePeaksFromFile.py
algorithms/PoldiLoadRuns.py
algorithms/PoldiMerge.py
algorithms/RebinRagged.py
algorithms/RefinePowderDiffProfileSeq.py
algorithms/ReflectometryReductionOneLiveData.py
algorithms/ReflectometrySliceEventWorkspace.py
......
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# pylint: disable=no-init,invalid-name
from mantid.api import *
from mantid.simpleapi import ConjoinWorkspaces, Rebin, DeleteWorkspace, ExtractSpectra
from mantid.kernel import *
import numpy as np
class RebinRagged(PythonAlgorithm):
def category(self):
return "Transforms\\Splitting"
def seeAlso(self):
return ['Rebin', 'ResampleX']
def name(self):
return "RebinRagged"
def summary(self):
return "Rebin each spectrum of a workspace independently. There is only one delta allowed per spectrum"
def PyInit(self):
self.declareProperty(
WorkspaceProperty("InputWorkspace", "", direction=Direction.Input),
"input workspace",
)
self.declareProperty(
WorkspaceProperty("OutputWorkspace", "", direction=Direction.Output),
"output workspace",
)
self.declareProperty(FloatArrayProperty("XMin"),
"minimum x values with NaN meaning no minimum")
self.declareProperty(FloatArrayProperty("XMax"),
"maximum x values with NaN meaning no maximum")
self.declareProperty(FloatArrayProperty("Delta"), "step parameter for rebin")
def validateInputs(self):
inputWS = self.getProperty("InputWorkspace").value
xmins = self.getProperty("XMin").value
xmaxs = self.getProperty("XMax").value
deltas = self.getProperty("Delta").value
numSpec = inputWS.getNumberHistograms()
numMin = len(xmins)
numMax = len(xmaxs)
numDelta = len(deltas)
errors = {}
if numDelta == 0:
errors["Delta"] = "Must specify binning"
elif not (numDelta == 1 or numDelta == numSpec):
errors["Delta"] = "Must specify for each spetra ({}!={})".format(numDelta, numSpec)
if np.any(np.invert(np.isfinite(xmins))):
errors["Delta"] = "All must be finite"
elif np.any(deltas == 0.0):
errors["Delta"] = "All must be nonzero"
if numMin == 0 and numMax == 0:
errors["XMin"] = "Must specify minimums or maximums"
errors["XMax"] = "Must specify minimums or maximums"
if numMin > 1 and numMin != numSpec:
errors["XMin"] = "Must specify min for each spectra ({}!={})".format(numMin, numSpec)
if numMax > 1 and numMax != numSpec:
errors["XMax"] = "Must specify max for each spectra ({}!={})".format(numMax, numSpec)
return errors
def __use_simple_rebin(self, xmins, xmaxs, deltas):
if len(xmins) == 1 and len(xmaxs) == 1 and len(deltas) == 1:
return True
if len(xmins) == 0 or len(xmaxs) == 1 or len(deltas) == 0:
return False
# is there (effectively) only one xmin?
if not (len(xmins) == 1 or np.alltrue(xmins == xmins[0])):
return False
# is there (effectively) only one xmin?
if not (len(xmaxs) == 1 or np.alltrue(xmaxs == xmaxs[0])):
return False
# is there (effectively) only one xmin?
if not (len(deltas) == 1 or np.alltrue(deltas == deltas[0])):
return False
# all of these point to 'just do rebin'
return True
def __extend_value(self, numSpec, array, replaceNan):
# change it to be the right length
if len(array) == 0:
array = np.full((numSpec, ), Property.EMPTY_DBL)
elif len(array) == 1:
array = np.full((numSpec, ), array[0])
# replace nan with EMPTY_DBL
indices = np.where(np.invert(np.isfinite(array)))
array[indices] = Property.EMPTY_DBL
return array
def PyExec(self):
inputWS = self.getProperty("InputWorkspace").value
outputWS = self.getProperty("OutputWorkspace").valueAsStr
xmins = self.getProperty("XMin").value
xmaxs = self.getProperty("XMax").value
deltas = self.getProperty("Delta").value
if self.__use_simple_rebin(xmins, xmaxs, deltas):
# plain old rebin should have been used
name = "__{}_rebinned_".format(outputWS)
params = (xmins[0], deltas[0], xmaxs[0])
Rebin(InputWorkspace=inputWS, OutputWorkspace=name, Params=params)
self.setProperty("OutputWorkspace", mtd[name])
DeleteWorkspace(name)
else:
numSpec = inputWS.getNumberHistograms()
# fill out the values for min and max as appropriate
xmins = self.__extend_value(numSpec, xmins, replaceNan=True)
xmaxs = self.__extend_value(numSpec, xmaxs, replaceNan=True)
deltas = self.__extend_value(numSpec, deltas, replaceNan=False)
self.log().debug("MIN: " + str(xmins))
self.log().debug("DELTA:" + str(deltas))
self.log().debug("MAX: " + str(xmaxs))
# temporary workspaces should be hidden
names = ["__{}_spec_{}".format(outputWS, i) for i in range(len(xmins))]
# how much the progress bar moves forward for each spectrum
progStep = float(1) / float(3 * numSpec)
# crop out each spectra and conjoin to a temporary workspace
accumulationWS = None
for i, (name, xmin, xmax, delta) in enumerate(zip(names, xmins, xmaxs, deltas)):
# don't go beyond the range of the data
x = inputWS.readX(i)
if xmin < x[0] or xmin == Property.EMPTY_DBL:
xmin = x[0]
if xmax > x[-1] or xmax == Property.EMPTY_DBL:
xmax = x[-1]
progStart = 3 * i * progStep
try:
# extract the range of the spectrum requested
ExtractSpectra(InputWorkspace=inputWS,
OutputWorkspace=name,
StartWorkspaceIndex=i,
EndWorkspaceIndex=i,
XMin=xmin,
XMax=xmax,
startProgress=progStart,
endProgress=(progStart + progStep),
EnableLogging=False)
# rebin the data
Rebin(InputWorkspace=name,
OutputWorkspace=name,
Params=(xmin, delta, xmax),
startProgress=progStart,
endProgress=(progStart + progStep),
EnableLogging=False)
except Exception as e:
raise RuntimeError('for index={}'.format(i)) from e
# accumulate
if accumulationWS is None:
accumulationWS = name # messes up progress during very first step
else:
# this deletes both input workspaces
ConjoinWorkspaces(InputWorkspace1=accumulationWS,
InputWorkspace2=name,
startProgress=(progStart + 2 * progStep),
endProgress=(progStart + 3 * progStep),
EnableLogging=False)
self.setProperty("OutputWorkspace", mtd[accumulationWS])
DeleteWorkspace(accumulationWS)
AlgorithmFactory.subscribe(RebinRagged)
......@@ -86,6 +86,7 @@ set(TEST_PY_FILES
OptimizeCrystalPlacementByRunTest.py
PDConvertRealSpaceTest.py
PDConvertReciprocalSpaceTest.py
RebinRaggedTest.py
ReflectometryReductionOneLiveDataTest.py
ReflectometrySliceEventWorkspaceTest.py
RetrieveRunInfoTest.py
......
......@@ -120,12 +120,12 @@ class MatchPeaksTest(unittest.TestCase):
self._args['InputWorkspace'] = self._in1
run1 = run_algorithm('MatchPeaks', **self._args)
self.assertTrue(run1.isExecuted())
self.assertEqual('Some invalid Properties found', str(contextManager.exception))
self.assertEqual('Some invalid Properties found: [ InputWorkspace2 ]', str(contextManager.exception))
with self.assertRaises(RuntimeError) as contextManager:
self._args['InputWorkspace'] = self._in2
run2 = run_algorithm('MatchPeaks', **self._args)
self.assertTrue(run2.isExecuted())
self.assertEqual('Some invalid Properties found', str(contextManager.exception))
self.assertEqual('Some invalid Properties found: [ InputWorkspace2 ]', str(contextManager.exception))
def testValidateInputWorkspace2(self):
self._args['InputWorkspace'] = self._ws_shift
......@@ -134,11 +134,11 @@ class MatchPeaksTest(unittest.TestCase):
with self.assertRaises(RuntimeError) as contextManager:
self._args['InputWorkspace2'] = self._in1
run_algorithm('MatchPeaks', **self._args)
self.assertEqual('Some invalid Properties found', str(contextManager.exception))
self.assertEqual('Some invalid Properties found: [ InputWorkspace2 InputWorkspace3 ]', str(contextManager.exception))
with self.assertRaises(RuntimeError) as contextManager:
self._args['InputWorkspace2'] = self._in2
run_algorithm('MatchPeaks', **self._args)
self.assertEqual('Some invalid Properties found', str(contextManager.exception))
self.assertEqual('Some invalid Properties found: [ InputWorkspace2 InputWorkspace3 ]', str(contextManager.exception))
def testValidateInputWorkspace3(self):
self._args['InputWorkspace'] = self._ws_shift
......@@ -147,7 +147,7 @@ class MatchPeaksTest(unittest.TestCase):
self.assertTrue(sys.version_info >= (2, 7))
with self.assertRaises(RuntimeError) as contextManager:
run_algorithm('MatchPeaks', **self._args)
self.assertEqual('Some invalid Properties found', str(contextManager.exception))
self.assertEqual('Some invalid Properties found: [ InputWorkspace2 InputWorkspace3 ]', str(contextManager.exception))
def testMatchCenter(self):
# Input workspace should match its center
......
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
import unittest
import mantid.simpleapi as api
from testhelpers import run_algorithm
from mantid.api import AnalysisDataService
import numpy as np
from numpy import nan as np_nan
from numpy import inf as np_inf
try:
from math import nan as math_nan
except ImportError:
math_nan = np_nan # rhel7 python is too old
class RebinRaggedTest(unittest.TestCase):
def test_nomad_inplace(self):
api.LoadNexusProcessed(Filename="NOM_91796_banks.nxs", OutputWorkspace="NOM_91796_banks")
alg_test = run_algorithm(
"RebinRagged",
InputWorkspace="NOM_91796_banks",
OutputWorkspace="NOM_91796_banks",
XMin=[0.67, 1.20, 2.42, 3.70, 4.12, 0.39],
Delta=0.02, # original data bin size
XMax=[10.20, 20.8, np_nan, math_nan, np_nan, 9.35])
self.assertTrue(alg_test.isExecuted())
# Verify ....
outputws = AnalysisDataService.retrieve("NOM_91796_banks")
for i, Xlen in enumerate([478, 981, 1880, 1816, 1795, 449]):
self.assertEqual(len(outputws.readX(i)), Xlen)
AnalysisDataService.remove("NOM_91796_banks")
def test_nomad_no_mins(self):
api.LoadNexusProcessed(Filename="NOM_91796_banks.nxs", OutputWorkspace="NOM_91796_banks")
alg_test = run_algorithm(
"RebinRagged",
InputWorkspace="NOM_91796_banks",
OutputWorkspace="NOM_91796_banks",
Delta=0.04, # double original data bin size
XMax=[10.20, 20.8, np_inf, math_nan, np_nan, 9.35])
self.assertTrue(alg_test.isExecuted())
# Verify ....
outputws = AnalysisDataService.retrieve("NOM_91796_banks")
for i, Xlen in enumerate([256, 521, 1001, 1001, 1001,
235]): # larger than in test_nomad_inplace
self.assertEqual(len(outputws.readX(i)), Xlen)
AnalysisDataService.remove("NOM_91796_banks")
def test_sample_workspace(self):
# numpy 1.7 (on rhel7) doesn't have np.full
xmins = np.full((200, ), 2600.0)
xmins[11] = 3000.0
xmaxs = np.full((200, ), 6200.0)
xmaxs[12] = 5000.0
deltas = np.full(200, 400.0)
deltas[13] = 600.0
ws = api.CreateSampleWorkspace()
rebinned = api.RebinRagged(ws, XMin=xmins, XMax=xmaxs, Delta=deltas)
self.assertEqual(rebinned.getNumberHistograms(), 200)
for i in range(rebinned.getNumberHistograms()):
label = "index={}".format(i)
if i == 11:
self.assertEqual(rebinned.readX(i).size, 9, label)
elif i == 12 or i == 13:
self.assertEqual(rebinned.readX(i).size, 7, label)
else:
self.assertEqual(
rebinned.readX(i).size,
10,
)
y = rebinned.readY(i)
if i == 13:
np.testing.assert_almost_equal(0.9, y, err_msg=label)
else:
# parameters are set so all y-values are 0.6
np.testing.assert_almost_equal(0.6, y, err_msg=label)
if __name__ == "__main__":
unittest.main()
......@@ -157,9 +157,9 @@ public:
alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("RangeLower", 2.))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("RangeUpper", 1.))
TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error const &e,
e.what(),
std::string("Some invalid Properties found"))
TS_ASSERT_THROWS_EQUALS(
alg.execute(), std::runtime_error const &e, e.what(),
std::string("Some invalid Properties found: [ RangeUpper ]"))
}
void test_invalidEndAndStartIndicesThrows() {
......@@ -176,9 +176,9 @@ public:
alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("StartWorkspaceIndex", 2))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("EndWorkspaceIndex", 1))
TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error const &e,
e.what(),
std::string("Some invalid Properties found"))
TS_ASSERT_THROWS_EQUALS(
alg.execute(), std::runtime_error const &e, e.what(),
std::string("Some invalid Properties found: [ EndWorkspaceIndex ]"))
}
private:
......
......@@ -191,9 +191,16 @@ private:
alg.setProperty("FirstSlitSizeSampleLog", "slit1.size"))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("SecondSlitName", slit2))
TS_ASSERT_THROWS_NOTHING(
alg.setProperty("SecondSlitSizeSampleLog", "slit2.size"))
TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e, e.what(),
std::string("Some invalid Properties found"))
alg.setProperty("SecondSlitSizeSampleLog", "slit2.size"));
if (slit == 1) {
TS_ASSERT_THROWS_EQUALS(
alg.execute(), std::runtime_error & e, e.what(),
std::string("Some invalid Properties found: [ FirstSlitName ]"));
} else if (slit == 2) {
TS_ASSERT_THROWS_EQUALS(
alg.execute(), std::runtime_error & e, e.what(),
std::string("Some invalid Properties found: [ SecondSlitName ]"));
}
TS_ASSERT(!alg.isExecuted())
}
......
......@@ -276,9 +276,10 @@ private:
TS_ASSERT_THROWS_NOTHING(alg->setProperty("SecondSlitName", slit1))
TS_ASSERT_THROWS_NOTHING(alg->setProperty("SecondSlitSizeSampleLog", slit2))
TS_ASSERT_THROWS_NOTHING(alg->setProperty("TOFChannelWidth", TOF_BIN_WIDTH))
TS_ASSERT_THROWS_EQUALS(alg->execute(), const std::runtime_error &e,
e.what(),
std::string("Some invalid Properties found"))
TS_ASSERT_THROWS_EQUALS(
alg->execute(), const std::runtime_error &e, e.what(),
std::string(
"Some invalid Properties found: [ FirstSlitName SecondSlitName ]"))
TS_ASSERT(!alg->isExecuted())
}
......
......@@ -262,7 +262,8 @@ public:
TS_ASSERT_THROWS_NOTHING(alg.setProperty("FlatSample", true))
TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e,
e.what(),
std::string("Some invalid Properties found"))
std::string("Some invalid Properties found: [ "
"BeamCentre InputWorkspaceIndexSet ]"))
}
void test_monitorInIndexSetThrows() {
......@@ -284,7 +285,8 @@ public:
TS_ASSERT_THROWS_NOTHING(alg.setProperty("FlatSample", true))
TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e,
e.what(),
std::string("Some invalid Properties found"))
std::string("Some invalid Properties found: [ "
"BeamCentre InputWorkspaceIndexSet ]"))
}
void test_BeamCentreNotInIndexSetThrows() {
......@@ -302,9 +304,9 @@ public:
alg.setPropertyValue("OutputWorkspace", "_unused_for_child"))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("BeamCentre", 2.))
TS_ASSERT_THROWS_NOTHING(alg.setProperty("FlatSample", true))
TS_ASSERT_THROWS_EQUALS(alg.execute(), const std::runtime_error &e,
e.what(),
std::string("Some invalid Properties found"))
TS_ASSERT_THROWS_EQUALS(
alg.execute(), const std::runtime_error &e, e.what(),
std::string("Some invalid Properties found: [ BeamCentre ]"))
}
private:
......
.. algorithm::
.. summary::
.. relatedalgorithms::
.. properties::
Description
-----------
This is a workflow algorithm that rebins each spectrum of a workspace independently.
This is intended for workspaces with a relatively small number of spectra (e.g. <10), but places no restrictions on the input workspace.
The minimum and maximum values that are specified are interpreted as follows:
* One value per spectrum. If there is only one value overall, it is used for all of the spectra.
* ``numpy.nan``, ``math.nan``, and ``np.inf`` are interpreted to mean use the data's minimum or maximum x-value.
The ``Delta`` parameter is required and can either be a single number which is common to all, or one number per spectra.
Positive values are interpreted as constant step-size. Negative are logorithmic.
Usage
-----
.. include:: ../usagedata-note.txt
This is an example of how ``RebinRagged`` would be used near the end of a workflow to generate a real-space distribution of data after it had been reduced into a number of "banks" or "spectra."
As mentioned above, ``numpy.nan`` or ``math.nan`` can both be used.
This particular use-case, which uses the input workspace's binning, could be done with :ref:`CropWorkspaceRagged <algm-CropWorkspaceRagged>` with similar results.
.. code-block:: python
from numpy import nan
NOM_91796 = LoadNexusProcessed(Filename='NOM_91796_banks.nxs')
RebinRagged(InputWorkspace=NOM_91796, OutputWorkspace='cropped',
Xmin=[0.67, 1.20, 2.42, 3.70, 4.12, 0.39],
Delta=0.02,
Xmax=[10.20, 20.8, nan, nan, nan, 9.35])
.. categories::
.. sourcelink::
......@@ -11,6 +11,7 @@ Diffraction Changes
Powder Diffraction
------------------
- New algorithm :ref:`RebinRagged <algm-RebinRagged>` which can rebin a workspace with different binning parameters for each spectrum
Engineering Diffraction
-----------------------
......@@ -19,4 +20,4 @@ Single Crystal Diffraction
--------------------------
- New version of algorithm :ref:`SCDCalibratePanels <algm-SCDCalibratePanels-v2>` provides more accurate calibration results for CORELLI instrument.
:ref:`Release 6.1.0 <v6.1.0>`
\ No newline at end of file
:ref:`Release 6.1.0 <v6.1.0>`
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment