Skip to content

Commit c59b137

Browse files
Add PeakShapeDetectorBin to IntegratePeaks1DProfile peak integration algorithm (#39040)
* DetectorBin peak shape added * unit tests and release notes added
1 parent 7424656 commit c59b137

File tree

3 files changed

+59
-10
lines changed

3 files changed

+59
-10
lines changed

Framework/PythonInterface/plugins/algorithms/IntegratePeaks1DProfile.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
PropertyCriterion,
2929
StringArrayProperty,
3030
UnitParams,
31+
SpecialCoordinateSystem,
3132
)
3233
from mantid.dataobjects import Workspace2D
3334
from mantid.fitfunctions import FunctionWrapper, CompositeFunctionWrapper
@@ -42,6 +43,7 @@
4243
)
4344
from enum import Enum
4445
from typing import Sequence, Tuple
46+
from mantid.dataobjects import PeakShapeDetectorBin
4547

4648

4749
class PEAK_STATUS(Enum):
@@ -334,7 +336,7 @@ def PyExec(self):
334336
for ipk, peak in enumerate(peaks):
335337
prog_reporter.report("Integrating")
336338
peak_intens, peak_sigma = 0.0, 0.0
337-
status = PEAK_STATUS.NO_PEAK
339+
peak_status = PEAK_STATUS.NO_PEAK
338340

339341
detid = peak.getDetectorID()
340342
bank_name = peaks.column("BankName")[ipk]
@@ -375,7 +377,7 @@ def PyExec(self):
375377
continue # skip peak
376378

377379
# update peak mask based on I/sig from fit
378-
*_, i_over_sigma = calc_intens_and_sigma_arrays(fit_result, error_strategy)
380+
*_, i_over_sigma, _ = calc_intens_and_sigma_arrays(fit_result, error_strategy)
379381
non_bg_mask = np.zeros(peak_data.detids.shape, dtype=bool)
380382
non_bg_mask.flat[initial_peak_mask] = i_over_sigma > i_over_sig_threshold
381383
peak_mask = find_peak_cluster_in_window(non_bg_mask, (peak_data.irow, peak_data.icol))
@@ -384,7 +386,7 @@ def PyExec(self):
384386

385387
is_on_edge = np.any(np.logical_and(peak_mask, peak_data.det_edges))
386388
if is_on_edge:
387-
status = PEAK_STATUS.ON_EDGE
389+
peak_status = PEAK_STATUS.ON_EDGE
388390
if not integrate_on_edge:
389391
self.delete_fit_result_workspaces(fit_result)
390392
continue # skip peak
@@ -406,19 +408,21 @@ def PyExec(self):
406408
peak_mask.flat[initial_peak_mask] = fit_mask
407409

408410
# calculate intensity
409-
status = PEAK_STATUS.VALID
410-
intens, sigma, _ = calc_intens_and_sigma_arrays(fit_result, error_strategy)
411+
peak_status = PEAK_STATUS.VALID
412+
intens, sigma, _, peak_limits = calc_intens_and_sigma_arrays(fit_result, error_strategy)
411413
peak_intens = np.sum(intens[fit_mask])
412414
peak_sigma = np.sqrt(np.sum(sigma[fit_mask] ** 2))
413415

416+
self._set_peak_shape(peak, peak_data.detids[peak_mask], peak_limits[fit_mask])
417+
414418
if output_file:
415419
intens_over_sig = peak_intens / peak_sigma if peak_sigma > 0 else 0.0
416420
results.append(
417421
LineProfileResult(
418422
ipk,
419423
peak,
420424
intens_over_sig,
421-
status,
425+
peak_status,
422426
peak_mask,
423427
fit_mask,
424428
func_generator.ysum,
@@ -439,6 +443,15 @@ def PyExec(self):
439443
# assign output
440444
self.setProperty("OutputWorkspace", peaks)
441445

446+
def _set_peak_shape(self, peak, det_ids, fit_limits):
447+
det_bin_list = []
448+
for detid, limits in zip(det_ids, fit_limits):
449+
if limits:
450+
det_bin_list.append((int(detid), limits[0], limits[-1]))
451+
if len(det_bin_list) > 0:
452+
peak_shape = PeakShapeDetectorBin(det_bin_list, SpecialCoordinateSystem.NONE, self.name(), self.version())
453+
peak.setPeakShape(peak_shape)
454+
442455
def exec_child_alg(self, alg_name, **kwargs):
443456
alg = self.createChildAlgorithm(alg_name, enableLogging=False)
444457
alg.initialize()
@@ -478,7 +491,7 @@ def __init__(self, peak_params_to_fix: Sequence[str]):
478491
self.cen_par_name: str = None
479492
self.intens_par_name: str = None
480493
self.width_par_name: str = None
481-
self.width_max: float = None
494+
self.width_min: float = None
482495
self.width_max: float = None
483496
self.peak_params_to_fix: Sequence[str] = peak_params_to_fix
484497
self.peak_mask: np.ndarray[float] = None
@@ -732,7 +745,7 @@ def calc_sigma_from_summation(xdat, edat_sq, ypeak, cutoff=0.025):
732745
ilo = np.clip(np.argmin(abs(ypeak_cumsum - cutoff)), a_min=0, a_max=nbins // 2)
733746
ihi = np.clip(np.argmin(abs(ypeak_cumsum - (1 - cutoff))), a_min=nbins // 2, a_max=nbins - 1) + 1
734747
bin_width = np.diff(xdat[ilo : ihi + 1])
735-
return np.sqrt(np.sum(edat_sq[ilo:ihi] * (bin_width**2)))
748+
return np.sqrt(np.sum(edat_sq[ilo:ihi] * (bin_width**2))), (xdat[ilo], xdat[ihi])
736749

737750

738751
def calc_intens_and_sigma_arrays(fit_result, error_strategy):
@@ -741,6 +754,7 @@ def calc_intens_and_sigma_arrays(fit_result, error_strategy):
741754
sigma = np.zeros(intens.shape)
742755
intens_over_sig = np.zeros(intens.shape)
743756
peak_func = FunctionFactory.Instance().createPeakFunction(function[0][0].name())
757+
peak_limits = np.full(intens.shape, None)
744758
for idom, comp_func in enumerate(function):
745759
[peak_func.setParameter(iparam, comp_func.getParameterValue(iparam)) for iparam in range(peak_func.nParams())]
746760
intens[idom] = peak_func.intensity()
@@ -749,10 +763,10 @@ def calc_intens_and_sigma_arrays(fit_result, error_strategy):
749763
sigma[idom] = peak_func.intensityError()
750764
else:
751765
ws_fit = get_eval_ws(fit_result["OutputWorkspace"], idom)
752-
sigma[idom] = calc_sigma_from_summation(ws_fit.readX(0), ws_fit.readE(0) ** 2, ws_fit.readY(3))
766+
sigma[idom], peak_limits[idom] = calc_sigma_from_summation(ws_fit.readX(0), ws_fit.readE(0) ** 2, ws_fit.readY(3))
753767
ivalid = ~np.isclose(sigma, 0)
754768
intens_over_sig[ivalid] = intens[ivalid] / sigma[ivalid]
755-
return intens, sigma, intens_over_sig
769+
return intens, sigma, intens_over_sig, peak_limits
756770

757771

758772
def get_eval_ws(out_ws_name, idom):

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
66
# SPDX - License - Identifier: GPL - 3.0 +
77
import unittest
8+
from unittest import mock
9+
from unittest.mock import patch
10+
811
from mantid.simpleapi import (
912
IntegratePeaks1DProfile,
1013
CreatePeaksWorkspace,
@@ -20,6 +23,7 @@
2023
import tempfile
2124
import shutil
2225
from os import path
26+
import json
2327

2428

2529
class IntegratePeaks1DProfileTest(unittest.TestCase):
@@ -86,6 +90,35 @@ def test_exec_IntegrateIfOnEdge_True(self):
8690
self.assertAlmostEqual(out.column("Intens/SigInt")[0], self.default_intens_over_sigma, delta=1e-1)
8791
self.assertAlmostEqual(out.column("Intens/SigInt")[1], self.default_intens_over_sigma, delta=1e-1)
8892

93+
def test_peak_shapes_when_fit_successful(self):
94+
out = IntegratePeaks1DProfile(
95+
InputWorkspace=self.ws, PeaksWorkspace=self.peaks_edge, OutputWorkspace="peaks_int_1", **self.profile_kwargs
96+
)
97+
self.assertEqual(len(out), 2)
98+
for pk in out:
99+
self.assertEqual(pk.getPeakShape().shapeName(), "detectorbin")
100+
self.assertEqual(pk.getPeakShape().algorithmName(), "IntegratePeaks1DProfile")
101+
self.assertEqual(pk.getPeakShape().algorithmVersion(), 1)
102+
pk_shape_dict = json.loads(pk.getPeakShape().toJSON())
103+
self.assertEqual(len(pk_shape_dict["detectors"]), 2)
104+
for det in pk_shape_dict["detectors"]:
105+
self.assertTrue(det["startX"] < det["endX"])
106+
107+
@patch("plugins.algorithms.IntegratePeaks1DProfile.IntegratePeaks1DProfile.delete_fit_result_workspaces")
108+
@patch(
109+
"plugins.algorithms.IntegratePeaks1DProfile.IntegratePeaks1DProfile.exec_fit",
110+
)
111+
def test_peak_shapes_when_fit_failed(self, mock_fit, mock_fit_result_workspaces):
112+
mock_fit_return = {"success": False}
113+
mock_fit.return_value = mock_fit_return
114+
out = IntegratePeaks1DProfile(
115+
InputWorkspace=self.ws, PeaksWorkspace=self.peaks_edge, OutputWorkspace="peaks_int_1", **self.profile_kwargs
116+
)
117+
self.assertEqual(len(out), 2)
118+
for pk in out:
119+
self.assertEqual(pk.getPeakShape().shapeName(), "none")
120+
mock_fit_result_workspaces.assert_has_calls([mock.call(mock_fit_return), mock.call(mock_fit_return)])
121+
89122
def test_exec_IntegrateIfOnEdge_False(self):
90123
kwargs = self.profile_kwargs.copy()
91124
kwargs["IntegrateIfOnEdge"] = False
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Added ``detectorbin`` peak shape for the peaks integrated with :ref:`IntegratePeaks1DProfile <algm-IntegratePeaks1DProfile>` integration algorithm.
2+
- By accessing the detectorbin peak shape, users can now view the detector IDs and the corresponding range in the X dimension associated with each detector for each successfully integrated peak from the algorithm.

0 commit comments

Comments
 (0)