Skip to content

Commit 8eab7f1

Browse files
38254 add PeakShapeDetectorbin to integration algorithms (#38452)
* Unit tests added for equal opeartor in python and c++ * early return added and cppcheck fix * reverted exposing opearator== into python for consistency * PeakShapeDetectorBin added to IntegratePeaksSkew * PeakShapeDetectorBin added to IntegratePeaksShoeboxTOF * PeakShapeDetectorBin added to IntegratePeaksSkew * null check added for ipos data * updated for review comments. Q plottting yet to do * Unit tests added * integratepeaksshoebox xlimits and det ids updated * release note updated * updated note
1 parent 24dcde3 commit 8eab7f1

File tree

10 files changed

+141
-10
lines changed

10 files changed

+141
-10
lines changed

Framework/DataObjects/src/PeakShapeDetectorBin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ Mantid::Geometry::PeakShape *PeakShapeDetectorBin::clone() const { return new Pe
5555

5656
std::string PeakShapeDetectorBin::shapeName() const { return PeakShapeDetectorBin::detectorBinShapeName(); }
5757

58-
const std::string PeakShapeDetectorBin::detectorBinShapeName() { return "PeakShapeDetectorBin"; }
58+
const std::string PeakShapeDetectorBin::detectorBinShapeName() { return "detectorbin"; }
5959

6060
} // namespace Mantid::DataObjects

Framework/DataObjects/test/PeakShapeDetectorBinTest.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class PeakShapeDetectorBinTest : public CxxTest::TestSuite {
3939
TS_ASSERT_EQUALS(algorithmName, peakShape->algorithmName());
4040
TS_ASSERT_EQUALS(version, peakShape->algorithmVersion());
4141
TS_ASSERT_EQUALS(coordinateSys, peakShape->frame());
42-
TS_ASSERT_EQUALS("PeakShapeDetectorBin", peakShape->shapeName());
42+
TS_ASSERT_EQUALS("detectorbin", peakShape->shapeName());
4343
TS_ASSERT_EQUALS(std::nullopt, peakShape->radius(Mantid::Geometry::PeakShape::RadiusType::Radius));
4444
TS_ASSERT_EQUALS(std::dynamic_pointer_cast<PeakShapeDetectorBin>(peakShape)->getDetectorBinList(), detPeakBinList);
4545

@@ -48,7 +48,7 @@ class PeakShapeDetectorBinTest : public CxxTest::TestSuite {
4848
TS_ASSERT_EQUALS(algorithmName, cloneShape->algorithmName());
4949
TS_ASSERT_EQUALS(version, cloneShape->algorithmVersion());
5050
TS_ASSERT_EQUALS(coordinateSys, cloneShape->frame());
51-
TS_ASSERT_EQUALS("PeakShapeDetectorBin", cloneShape->shapeName());
51+
TS_ASSERT_EQUALS("detectorbin", cloneShape->shapeName());
5252
TS_ASSERT_EQUALS(std::nullopt, cloneShape->radius(Mantid::Geometry::PeakShape::RadiusType::Radius));
5353
TS_ASSERT_EQUALS(std::dynamic_pointer_cast<PeakShapeDetectorBin>(cloneShape)->getDetectorBinList(), detPeakBinList);
5454
}
@@ -62,7 +62,7 @@ class PeakShapeDetectorBinTest : public CxxTest::TestSuite {
6262
std::string jsonStr = peakShape->toJSON();
6363
Json::Value output;
6464
TSM_ASSERT("Should parse as JSON", Mantid::JsonHelpers::parse(jsonStr, &output));
65-
TS_ASSERT_EQUALS("PeakShapeDetectorBin", output["shape"].asString());
65+
TS_ASSERT_EQUALS("detectorbin", output["shape"].asString());
6666
TS_ASSERT_EQUALS("TestSuite", output["algorithm_name"].asString());
6767
TS_ASSERT_EQUALS(1, output["algorithm_version"].asInt());
6868
TS_ASSERT_EQUALS(0, output["frame"].asInt());

Framework/PythonInterface/plugins/algorithms/IntegratePeaksShoeboxTOF.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
StringListValidator,
2222
EnabledWhenProperty,
2323
PropertyCriterion,
24+
SpecialCoordinateSystem,
2425
)
2526
from dataclasses import dataclass
2627
import numpy as np
@@ -29,6 +30,7 @@
2930
from plugins.algorithms.IntegratePeaksSkew import InstrumentArrayConverter, get_fwhm_from_back_to_back_params
3031
from plugins.algorithms.FindSXPeaksConvolve import make_kernel, get_kernel_shape
3132
from enum import Enum
33+
from mantid.dataobjects import PeakShapeDetectorBin
3234

3335

3436
class PEAK_STATUS(Enum):
@@ -61,6 +63,7 @@ def __init__(self, ipk, pk, x, y, peak_shape, ipos, ipk_pos, intens_over_sig, st
6163
self.ipos_pk = list(ipk_pos)
6264
self.labels = ["Row", "Col", "TOF"]
6365
self.ysum = []
66+
self.x = x
6467
self.xmin, self.xmax = np.round(x[0], 1), np.round(x[-1], 1)
6568
self.status = status
6669
# integrate y over each dim
@@ -332,6 +335,7 @@ def PyExec(self):
332335
weak_peaks_list = []
333336
ipks_strong = []
334337
results = np.full(peaks.getNumberPeaks(), None)
338+
peaks_det_ids = np.full(peaks.getNumberPeaks(), None)
335339
prog_reporter = Progress(self, start=0.0, end=1.0, nreports=peaks.getNumberPeaks())
336340
for ipk, peak in enumerate(peaks):
337341
prog_reporter.report("Integrating")
@@ -365,6 +369,7 @@ def PyExec(self):
365369
ix = np.argmin(abs(x - pk_tof))
366370
ipos_predicted = [peak_data.irow, peak_data.icol, ix]
367371
det_edges = peak_data.det_edges if not integrate_on_edge else None
372+
peaks_det_ids[ipk] = peak_data.detids
368373

369374
intens, sigma, i_over_sig, status, ipos, nrows, ncols, nbins = integrate_peak(
370375
ws,
@@ -383,6 +388,7 @@ def PyExec(self):
383388
weak_peak_threshold,
384389
do_optimise_shoebox,
385390
)
391+
386392
if status == PEAK_STATUS.WEAK and do_optimise_shoebox and weak_peak_strategy == "NearestStrongPeak":
387393
# look for possible strong peaks at any TOF in the window (won't know if strong until all pks integrated)
388394
ipks_near, _ = find_ipks_in_window(ws, peaks, ispecs, ipk)
@@ -432,6 +438,8 @@ def PyExec(self):
432438
x, y, esq, ispecs = get_and_clip_data_arrays(ws, peak_data, pk_tof, kernel, nshoebox)
433439
# integrate at previously found ipos
434440
ipos = [*np.argwhere(ispecs == weak_pk.ispec)[0], np.argmin(abs(x - weak_pk.tof))]
441+
peaks_det_ids[ipk] = peak_data.detids
442+
435443
det_edges = peak_data.det_edges if not integrate_on_edge else None
436444
intens, sigma, i_over_sig, status = integrate_shoebox_at_pos(y, esq, kernel, ipos, weak_peak_threshold, det_edges)
437445
# scale summed intensity by bin width to get integrated area
@@ -452,6 +460,9 @@ def PyExec(self):
452460
f"WeakPeakStrategy to Fix"
453461
)
454462

463+
# Sets PeakShapeDetectorBin shapes for successfully integrated peaks
464+
self._set_peak_shapes(results, peaks_det_ids, peaks)
465+
455466
# plot output
456467
if output_file:
457468
prog_reporter.resetNumSteps(int(len(results) - np.sum(results is None)), start=0.0, end=1.0)
@@ -460,6 +471,28 @@ def PyExec(self):
460471
# assign output
461472
self.setProperty("OutputWorkspace", peaks)
462473

474+
def _set_peak_shapes(self, shoebox_results, peaks_det_ids, peaks_ws):
475+
"""
476+
Sets PeakShapeDetectorBin shapes for the successfully integrated peaks
477+
@param shoebox_results - ShoeboxResult array for each peak
478+
@param peaks_det_ids - detector ids related to each peak
479+
@param peaks_ws - peaks workspace
480+
"""
481+
for ipk, (shoebox_res, peak_detids) in enumerate(zip(shoebox_results, peaks_det_ids)):
482+
if shoebox_res is not None and peak_detids is not None:
483+
if shoebox_res.status != PEAK_STATUS.ON_EDGE and shoebox_res.status != PEAK_STATUS.NO_PEAK:
484+
det_id_row_start = shoebox_res.ipos[0] - shoebox_res.peak_shape[0] // 2
485+
det_id_row_end = shoebox_res.ipos[0] + shoebox_res.peak_shape[0] // 2 + 1
486+
det_id_col_start = shoebox_res.ipos[1] - shoebox_res.peak_shape[1] // 2
487+
det_id_col_end = shoebox_res.ipos[1] + shoebox_res.peak_shape[1] // 2 + 1
488+
selected_det_ids = peak_detids[det_id_row_start:det_id_row_end, det_id_col_start:det_id_col_end]
489+
xstart = shoebox_res.x[shoebox_res.ipos[2] - shoebox_res.peak_shape[2] // 2]
490+
xend = shoebox_res.x[shoebox_res.ipos[2] + shoebox_res.peak_shape[2] // 2]
491+
det_bin_list = [(int(det_id), xstart, xend) for det_id in selected_det_ids.ravel()]
492+
peak_shape = PeakShapeDetectorBin(det_bin_list, SpecialCoordinateSystem.NONE, self.name(), self.version())
493+
peak = peaks_ws.getPeak(ipk)
494+
peak.setPeakShape(peak_shape)
495+
463496
def exec_child_alg(self, alg_name, **kwargs):
464497
alg = self.createChildAlgorithm(alg_name, enableLogging=False)
465498
alg.initialize()

Framework/PythonInterface/plugins/algorithms/IntegratePeaksSkew.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
logger,
2727
CompositeValidator,
2828
CompositeRelation,
29+
SpecialCoordinateSystem,
2930
)
3031
import numpy as np
3132
from scipy.signal import convolve2d
@@ -34,6 +35,7 @@
3435
from mantid.geometry import RectangularDetector, GridDetector
3536
import re
3637
from enum import Enum
38+
from mantid.dataobjects import PeakShapeDetectorBin
3739

3840

3941
class PEAK_MASK_STATUS(Enum):
@@ -1027,7 +1029,6 @@ def PyExec(self):
10271029
# check that peak is in a valid detector
10281030
detid = detids[ipk]
10291031
detector_info = ws.detectorInfo()
1030-
invalid_detector = False
10311032
try:
10321033
det_idx = detector_info.indexOf(detid)
10331034
invalid_detector = detector_info.isMonitor(det_idx) or detector_info.isMasked(det_idx)
@@ -1090,6 +1091,9 @@ def PyExec(self):
10901091
# set peak object intensity
10911092
pk.setIntensity(L * peak_data.intens)
10921093
pk.setSigmaIntensity(L * peak_data.sig)
1094+
1095+
# Set PeakShapeDetectorBin shape for valid peaks
1096+
self._set_peak_shapes(ws, pk, peak_data)
10931097
else:
10941098
pk.setIntensity(0.0)
10951099
pk.setSigmaIntensity(0.0)
@@ -1174,6 +1178,25 @@ def PyExec(self):
11741178
# assign output
11751179
self.setProperty("OutputWorkspace", pk_ws_int)
11761180

1181+
def _set_peak_shapes(self, ws, peak, peak_data):
1182+
"""
1183+
Sets PeakShapeDetectorBin shape for a peak
1184+
@param ws - Input workspace
1185+
@param peak - peak to add the shape
1186+
@param peak_data - PeakData object containing details of the integrated peak
1187+
"""
1188+
if not peak_data.peak_mask.any():
1189+
return
1190+
det_bin_list = []
1191+
for det in peak_data.detids[peak_data.peak_mask]:
1192+
ispec = ws.getIndicesFromDetectorIDs([int(det)])[0]
1193+
x_start = ws.readX(ispec)[peak_data.ixmin_opt]
1194+
x_end = ws.readX(ispec)[peak_data.ixmax_opt]
1195+
det_bin_list.append((int(det), x_start, x_end))
1196+
if len(det_bin_list) > 0:
1197+
peak_shape = PeakShapeDetectorBin(det_bin_list, SpecialCoordinateSystem.NONE, self.name(), self.version())
1198+
peak.setPeakShape(peak_shape)
1199+
11771200
@staticmethod
11781201
def estimate_linear_params(x, y):
11791202
# adapted from scipy.stats.siegelslopes with added vectorisation

Framework/PythonInterface/test/python/mantid/geometry/IPeakTest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def test_set_peak_shape(self):
189189
self.assertEqual(self._peak.getPeakShape().shapeName(), "none")
190190

191191
self._peak.setPeakShape(detector_bin)
192-
self.assertEqual(self._peak.getPeakShape().shapeName(), "PeakShapeDetectorBin")
192+
self.assertEqual(self._peak.getPeakShape().shapeName(), "detectorbin")
193193
self.assertEqual(self._peak.getPeakShape().algorithmVersion(), 1)
194194
self.assertEqual(self._peak.getPeakShape().algorithmName(), "test")
195195
det_bin_dict = json.loads(self._peak.getPeakShape().toJSON())

Framework/PythonInterface/test/python/plugins/algorithms/IntegratePeaksShoeboxTOFTest.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import tempfile
2121
import shutil
2222
from os import path
23-
23+
import json
2424

2525
XML_PARAMS = """
2626
<?xml version="1.0" encoding="UTF-8" ?>
@@ -40,7 +40,7 @@
4040
"""
4141

4242

43-
class FindSXPeaksConvolveTest(unittest.TestCase):
43+
class IntegratePeaksShoeboxTOFTest(unittest.TestCase):
4444
@classmethod
4545
def setUpClass(cls):
4646
# load empty instrument with RectangularDetector banks and create a peak table
@@ -227,6 +227,52 @@ def test_exec_no_peak(self, mock_find_ipos):
227227
)
228228
self._assert_found_correct_peaks(out, i_over_sigs=2 * [0.0])
229229

230+
def test_exec_peak_shape_when_IntegrateIfOnEdge_False(self):
231+
out = IntegratePeaksShoeboxTOF(
232+
InputWorkspace=self.ws,
233+
PeaksWorkspace=self.peaks,
234+
OutputWorkspace="peaks1",
235+
GetNBinsFromBackToBackParams=False,
236+
NRows=3,
237+
NCols=3,
238+
NBins=3,
239+
WeakPeakThreshold=0.0,
240+
OptimiseShoebox=False,
241+
IntegrateIfOnEdge=False,
242+
)
243+
self.assertEqual(out.getNumberPeaks(), 2)
244+
self.assertEqual(out.getPeak(0).getPeakShape().shapeName(), "none")
245+
self.assertEqual(out.getPeak(1).getPeakShape().shapeName(), "none")
246+
247+
def test_exec_OptimiseShoebox_peak_shape(self):
248+
# make kernel larger than optimum
249+
out = IntegratePeaksShoeboxTOF(
250+
InputWorkspace=self.ws,
251+
PeaksWorkspace=self.peaks,
252+
OutputWorkspace="peaks4",
253+
GetNBinsFromBackToBackParams=False,
254+
NRows=5,
255+
NCols=5,
256+
NBins=3,
257+
WeakPeakThreshold=0.0,
258+
OptimiseShoebox=True,
259+
IntegrateIfOnEdge=True,
260+
)
261+
262+
self.assertEqual(out.getNumberPeaks(), 2)
263+
264+
def _test_shapes(peaksws, start_end_points):
265+
for i_pk, pk in enumerate(peaksws):
266+
self.assertEqual(pk.getPeakShape().shapeName(), "detectorbin")
267+
pk_shape_dict = json.loads(pk.getPeakShape().toJSON())
268+
self.assertEqual(len(pk_shape_dict["detectors"]), 9)
269+
self.assertEqual(pk_shape_dict["algorithm_name"], "IntegratePeaksShoeboxTOF")
270+
for det in pk_shape_dict["detectors"]:
271+
self.assertEqual(det["startX"], start_end_points[i_pk][0])
272+
self.assertEqual(det["endX"], start_end_points[i_pk][1])
273+
274+
_test_shapes(out, ((2.5, 4.5), (8.5, 10.5)))
275+
230276

231277
if __name__ == "__main__":
232278
unittest.main()

Framework/PythonInterface/test/python/plugins/algorithms/IntegratePeaksSkewTest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from plugins.algorithms.IntegratePeaksSkew import InstrumentArrayConverter
2222
from testhelpers import WorkspaceCreationHelper
2323
from numpy import array, sqrt, arange, ones, zeros
24+
import json
2425

2526

2627
class IntegratePeaksSkewTest(unittest.TestCase):
@@ -77,6 +78,7 @@ def test_integrate_on_edge_option(self):
7778
# check peaks in bank 1 were not integrated (mask touches masked pixel)
7879
for ipk, pk in enumerate(out):
7980
self.assertEqual(pk.getIntensity(), 0)
81+
self.assertEqual(pk.getPeakShape().shapeName(), "none")
8082

8183
def test_integrate_on_edge_option_respects_detector_masking(self):
8284
ws_masked = CloneWorkspace(InputWorkspace=self.ws)
@@ -513,6 +515,30 @@ def test_nrows_edge_ncols_edge_in_array_converter_component_array(self):
513515
det_edges_expected[:, -1] = True # last tube in window is second from end of bank and ncols_edge=2
514516
self.assertTrue((peak_data.det_edges == det_edges_expected).all())
515517

518+
def test_shapeof_valid_peaks(self):
519+
out = IntegratePeaksSkew(
520+
InputWorkspace=self.ws,
521+
PeaksWorkspace=self.peaks,
522+
ThetaWidth=0,
523+
BackscatteringTOFResolution=0.3,
524+
IntegrateIfOnEdge=True,
525+
UseNearestPeak=False,
526+
UpdatePeakPosition=False,
527+
LorentzCorrection=False,
528+
OutputWorkspace="out10",
529+
)
530+
# check shape of the only valid peak
531+
pk = out.getPeak(0)
532+
self.assertEqual(pk.getPeakShape().shapeName(), "detectorbin")
533+
self.assertEqual(pk.getPeakShape().algorithmName(), "IntegratePeaksSkew")
534+
pk_shape_dict = json.loads(pk.getPeakShape().toJSON())
535+
self.assertEqual(len(pk_shape_dict["detectors"]), 6)
536+
for det in pk_shape_dict["detectors"]:
537+
self.assertEqual(det["startX"], 3)
538+
self.assertEqual(det["endX"], 8)
539+
for i in [1, 2]:
540+
self.assertEqual(out.getPeak(i).getPeakShape().shapeName(), "none")
541+
516542

517543
if __name__ == "__main__":
518544
unittest.main()
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
- A new peak shape named PeakShapeDetectorBin was introduced to save the detector IDs and bin indices of either TOF or dSpacing domains. This peak shape could be used for Overlap detection, Two-step integration and Eventual visualisation on instrument view.
1+
- A new peak shape named `detectorbin` was introduced to save the detector IDs and bin indices of either TOF or dSpacing domains. This peak shape could be used for Overlap detection, Two-step integration and Eventual visualisation on instrument view.
2+
- The new peak shape has been associated with the peaks integrated with :ref:`algm-IntegratePeaksShoeboxTOF` and :ref:`IntegratePeaksSkew <algm-IntegratePeaksSkew>` integration algorithms.
3+
- By accessing the `detectorbin` peak shape users now can view the detector ids associated for each integrated peak from the above algorithms.

qt/python/mantidqt/mantidqt/widgets/sliceviewer/peaksviewer/representation/draw.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"none": NonIntegratedPeakRepresentation,
2424
"spherical": EllipsoidalIntegratedPeakRepresentation,
2525
"ellipsoid": EllipsoidalIntegratedPeakRepresentation,
26+
"detectorbin": NonIntegratedPeakRepresentation,
2627
}
2728

2829

scripts/Diffraction/single_crystal/base_sx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ def plot_integrated_peaks_MD(
692692
with PdfPages(filename) as pdf:
693693
for ipk, pk in enumerate(peaks):
694694
peak_shape = pk.getPeakShape()
695-
if peak_shape.shapeName().lower() == "none":
695+
if peak_shape.shapeName().lower() == "none" or peak_shape.shapeName().lower() == "detectorbin":
696696
continue
697697
ws_cut, radii, bg_inner_radii, bg_outer_radii, box_lengths, imax = BaseSX._bin_MD_around_peak(
698698
wsMD, pk, peak_shape, nbins_max, extent, frame_to_peak_centre_attr

0 commit comments

Comments
 (0)