Skip to content

Commit a281a3a

Browse files
Ewm3645 mask workspace diffcal (#658)
* impl: add mask workspace to diffcal * squash fix import path apparently the auto import logic has changed for the worse fix fetch diffcal groceries unit test odd update to try and get github to kick off ci add test for the combining of masks for diffcal actually pass the maskworkspace to be used by the backend update tests to accept maskworkspaces from different states as valid fix integration tests added a test to confirm resident masks are filtered if not compatible change exception type to something not pydantic up the last couple lines of coverage update precommit, remove commented out code fix mock method definition * wip commit to squash later * enable masking in diffcal workflow, including fully masked groups * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update pixi lock file * update tests * fix maskless flow of calibration workflow * update pixi lockfile * add request validation for ingredients and groceries so state can init * fix some other maskworkspace handling * fix integration tests * fix existing unit tests * refactor diffcal happy path integration tests, add one for user mask * update pixi lock file * move combine mask logic to grocery service, make some updates according to comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix segfaults in test_reductionservice tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix last failing test, make more updates as per comments * fix issue with mismatched peaklists at assessment time * update pixi lockfile * move back pin on ruamel.yaml, seems to be causing doc issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * syntax error * fix precommit checks * add workaround for ConjoinWorkspaces mantid defect. require outputws of combinePixelMasks already exist * fix up 2 failining unit tests * move some mocks to patches * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix boundry condition in pixeldiffcal assuming index 0 would always be the initial host for offsets * add exception if the mask fully masks the instrument * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests * update pixi lock file * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix precommit errors * correctly label group id's on the ui * account for the additional masking that pixel calibration introduces * add missing method to test souschef --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent f63c622 commit a281a3a

29 files changed

+2147
-1843
lines changed

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ build:
2121
# Install the package in editable mode using pixi
2222
- $HOME/.pixi/bin/pixi run --environment docs pip install -e .
2323
# Install docs dependencies in the RTD Python environment too
24-
- pip install erdantic versioningit sphinx_rtd_theme sphinxcontrib-mermaid types-pyyaml h5py numpy matplotlib ruamel.yaml
24+
- pip install erdantic versioningit sphinx_rtd_theme sphinxcontrib-mermaid types-pyyaml h5py numpy matplotlib ruamel.yaml==0.18.16
2525

2626
sphinx:
2727
builder: html

pixi.lock

Lines changed: 1577 additions & 1535 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/snapred/backend/dao/request/CalibrationAssessmentRequest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class CalibrationAssessmentRequest(BaseModel):
3737
default_factory=lambda: Pair.model_validate(Config["calibration.parameters.default.FWHMMultiplier"])
3838
)
3939
maxChiSq: float = Field(default_factory=lambda: Config["constants.GroupDiffractionCalibration.MaxChiSq"])
40+
combinedPixelMask: WorkspaceName | None = None
4041

4142
@field_validator("fwhmMultipliers", mode="before")
4243
@classmethod

src/snapred/backend/dao/request/DiffractionCalibrationRequest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class DiffractionCalibrationRequest(BaseModel):
4343
maxChiSq: float = Field(default_factory=lambda: Config["constants.GroupDiffractionCalibration.MaxChiSq"])
4444
removeBackground: bool = False
4545
pixelMasks: List[WorkspaceName] = []
46+
combinedPixelMask: WorkspaceName | None = None
4647

4748
continueFlags: Optional[ContinueWarning.Type] = ContinueWarning.Type.UNSET
4849

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from typing import Optional
2-
3-
from pydantic import BaseModel
1+
from pydantic import BaseModel, ConfigDict
42

53
from snapred.backend.dao.state.FocusGroup import FocusGroup
4+
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName
65

76

87
class FocusSpectraRequest(BaseModel):
@@ -13,4 +12,7 @@ class FocusSpectraRequest(BaseModel):
1312

1413
inputWorkspace: str
1514
groupingWorkspace: str
16-
outputWorkspace: Optional[str] = None
15+
maskWorkspace: WorkspaceName | None = None
16+
outputWorkspace: WorkspaceName | None = None
17+
18+
model_config = ConfigDict(arbitrary_types_allowed=True)

src/snapred/backend/data/GroceryService.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,36 @@ def fetchGroceryList(self, groceryList: Iterable[GroceryListItem]) -> List[Works
15151515

15161516
return groceries
15171517

1518+
def combinePixelMasks(self, outputMaskWsName: WorkspaceName, masks2Combine: List[WorkspaceName]):
1519+
if not masks2Combine:
1520+
raise ValueError("Internal Error: Lists of masks to combine is empty")
1521+
1522+
if not self.mantidSnapper.mtd.doesExist(outputMaskWsName):
1523+
raise ValueError(
1524+
(
1525+
"Internal Error: outputMaskWs should exist before attempting to combine masks with it. "
1526+
"Consider using fetchCompatiblePixelMask to generate it."
1527+
)
1528+
)
1529+
1530+
for maskWsName in masks2Combine:
1531+
if maskWsName == outputMaskWsName:
1532+
continue
1533+
if not self.mantidSnapper.mtd.doesExist(maskWsName):
1534+
raise ValueError(
1535+
f"Mask {maskWsName} of mask set {masks2Combine} does not exist, cannot combine into pixel mask."
1536+
)
1537+
self.mantidSnapper.BinaryOperateMasks(
1538+
f"combine from pixel mask: '{maskWsName}'...",
1539+
InputWorkspace1=outputMaskWsName,
1540+
InputWorkspace2=maskWsName,
1541+
OperationType="OR",
1542+
OutputWorkspace=outputMaskWsName,
1543+
)
1544+
1545+
self.mantidSnapper.executeQueue()
1546+
return outputMaskWsName
1547+
15181548
def fetchGroceryDict(self, groceryDict: Dict[str, GroceryListItem], **kwargs) -> Dict[str, WorkspaceName]:
15191549
"""
15201550
This is the primary method you should use for fetching groceries, in almost all cases.

src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def queueAlgos(self):
6666
# Step 2: Check for overlapping spectra and manage them
6767
fitPeaksWorkspace = self.mantidSnapper.mtd[self.fitPeaksDiagnosticWorkspaceName]
6868
numHistograms = fitPeaksWorkspace.getNumberHistograms()
69-
processedSpectra = []
7069
spectrumDict = {}
7170

7271
for i in range(numHistograms):
@@ -102,9 +101,7 @@ def queueAlgos(self):
102101

103102
spectrumDict[spectrumId] = singleSpectrumName
104103

105-
# Append the processed spectrum to the list
106-
processedSpectra.append(singleSpectrumName)
107-
104+
processedSpectra = list(spectrumDict.values())
108105
# Step 3: Combine all processed spectra into a single workspace
109106
combinedWorkspace = processedSpectra[0]
110107
for spectrum in processedSpectra[1:]:

src/snapred/backend/recipe/GroupDiffCalRecipe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, Workspac
7777
)
7878

7979
diffocWS = self.mantidSnapper.mtd[groceries["groupingWorkspace"]]
80-
if groupIDs != diffocWS.getGroupIDs().tolist():
80+
if not set(groupIDs).issubset(set(diffocWS.getGroupIDs().tolist())):
8181
raise RuntimeError(
8282
f"Group IDs do not match between peak list and focus WS: '{groupIDs}' vs. '{diffocWS.getGroupIDs()}'"
8383
)

src/snapred/backend/recipe/PixelDiffCalRecipe.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from typing import Any, Dict, List, Set
22

33
import numpy as np
4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, ConfigDict
55

66
from snapred.backend.dao.ingredients import DiffractionCalibrationIngredients as Ingredients
77
from snapred.backend.log.logger import snapredLogger
88
from snapred.backend.profiling.ProgressRecorder import ComputationalOrder, WallClockTime
99
from snapred.backend.recipe.algorithm.Utensils import Utensils
10-
from snapred.backend.recipe.Recipe import Recipe, WorkspaceName
10+
from snapred.backend.recipe.Recipe import Recipe
1111
from snapred.meta.Config import Config
1212
from snapred.meta.decorators.classproperty import classproperty
13+
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName
1314
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceNameGenerator as wng
1415

1516
logger = snapredLogger.getLogger(__name__)
@@ -18,10 +19,12 @@
1819
class PixelDiffCalServing(BaseModel):
1920
result: bool
2021
medianOffsets: List[float]
21-
maskWorkspace: str
22+
maskWorkspace: WorkspaceName | None = None
2223
calibrationTable: str
2324
outputWorkspace: str
2425

26+
model_config = ConfigDict(arbitrary_types_allowed=True)
27+
2528

2629
# Decorating with the `WallClockTime` profiler here somewhat duplicates the objective of the decoration
2730
# at `CalibrationService.pixelCalibration`. However, by default, there will be no logging output from this instance.
@@ -101,11 +104,20 @@ def unbagGroceries(self, groceries: Dict[str, WorkspaceName]) -> None: # noqa A
101104
"""
102105
self.wsTOF = groceries["inputWorkspace"]
103106
self.groupingWS = groceries["groupingWorkspace"]
104-
self.maskWS = groceries["maskWorkspace"]
107+
self.maskWS = groceries.get("maskWorkspace")
105108
# the name of the output calibration table
106109
self.DIFCpixel = groceries["calibrationTable"]
107110
self.DIFCprev = groceries.get("previousCalibration", "")
108111
# the input data converted to d-spacing
112+
113+
if self.maskWS and self.mantidSnapper.mtd.doesExist(self.maskWS):
114+
# if user supplied mask exists, apply it
115+
# A user mask may not exist, but the maskws name is used to store
116+
# the mask generated as part of PixelDiffCalRecipe
117+
self.mantidSnapper.MaskDetectors(
118+
"applying user generated mask", Workspace=self.wsTOF, MaskedWorkspace=self.maskWS
119+
)
120+
109121
self.wsDSP = wng.diffCalInputDSP().runNumber(self.runNumber).build()
110122
self.convertUnitsAndRebin(self.wsTOF, self.wsDSP)
111123
self.mantidSnapper.CloneWorkspace(
@@ -215,11 +227,14 @@ def queueAlgos(self):
215227
wscc: str = f"__{self.runNumber}_tmp_group_CC_{self._counts}"
216228

217229
for i, (groupID, workspaceIndices) in enumerate(self.groupWorkspaceIndices.items()):
230+
if groupID not in self.maxDSpaceShifts:
231+
# group has been fully masked.
232+
continue
218233
workspaceIndices = list(workspaceIndices)
219234
refID: int = self.getRefID(workspaceIndices)
220235

221236
self.mantidSnapper.CrossCorrelate(
222-
f"Cross-Correlating spectra for {wscc}",
237+
f"Cross-Correlating spectra for {wscc}, subgroup {groupID}",
223238
InputWorkspace=self.wsDSP,
224239
OutputWorkspace=wscc + f"_group{groupID}",
225240
ReferenceSpectra=refID,
@@ -230,9 +245,10 @@ def queueAlgos(self):
230245
)
231246

232247
self.mantidSnapper.GetDetectorOffsets(
233-
f"Calculate offset workspace {wsoff}",
248+
f"Calculate offset workspace {wsoff}, group {groupID}",
234249
InputWorkspace=wscc + f"_group{groupID}",
235250
OutputWorkspace=wsoff,
251+
# MasKWs is an in/out, this algo will modify it
236252
MaskWorkspace=self.maskWS,
237253
# Scale the fitting ROI using the expected peak width (including a few possible peaks):
238254
XMin=-(self.maxDSpaceShifts[groupID] / self.dBin) * 2.0,
@@ -243,7 +259,7 @@ def queueAlgos(self):
243259

244260
# add in group offsets to total, or begin the sum if none
245261
# NOTE wsoff has all spectra, with value 0 in those not used in CrossCorrelate
246-
if i == 0:
262+
if not self.mantidSnapper.mtd.doesExist(self.totalOffsetWS):
247263
self.mantidSnapper.CloneWorkspace(
248264
f"Starting summation with offset workspace {wsoff}",
249265
InputWorkspace=wsoff,
@@ -308,6 +324,12 @@ def execute(self):
308324
InstrumentWorkspace=self.wsTOF,
309325
CalibrationWorkspace=self.DIFCpixel,
310326
)
327+
self.mantidSnapper.MaskDetectors(
328+
"reapplying user generated + get detector offsets mask",
329+
Workspace=self.wsTOF,
330+
MaskedWorkspace=self.maskWS,
331+
)
332+
311333
else:
312334
logger.warning("Offsets failed to converge monotonically")
313335

src/snapred/backend/recipe/algorithm/ConjoinDiagnosticWorkspaces.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
mtd,
1616
)
1717

18+
from snapred.backend.log.logger import snapredLogger
1819
from snapred.meta.mantid.FitPeaksOutput import FIT_PEAK_DIAG_SUFFIX, FitOutputEnum
1920

21+
logger = snapredLogger.getLogger(__name__)
22+
2023

2124
class ConjoinDiagnosticWorkspaces(PythonAlgorithm):
2225
"""
@@ -127,12 +130,24 @@ def conjoinMatrixWorkspaces(self, inws, outws, index):
127130
InputWorkspace=inws,
128131
OutputWorkspace=tmpws,
129132
)
133+
logger.debug(f"{outws} already has spectrum numbers of {mtd[outws].getSpectrumNumbers()}")
134+
logger.debug(f"Conjoining {tmpws} with {outws}, adding spectrum numbers of {mtd[tmpws].getSpectrumNumbers()}")
135+
specNumbers = list(mtd[outws].getSpectrumNumbers())
136+
specNumbers.extend(list(mtd[tmpws].getSpectrumNumbers()))
130137
ConjoinWorkspaces(
131138
InputWorkspace1=outws,
132139
InputWorkspace2=tmpws,
133140
CheckOverlapping=False,
134141
CheckMatchingBins=False, # Not available in 6.11.0.3rc2
135142
)
143+
144+
# TODO: Remove when Defect 14460 is resolved.
145+
# There is a defect in ConjoinWorkspaces that incorrectly determines
146+
# if spectrum numbers need to be remapped.
147+
for i, specNum in enumerate(specNumbers):
148+
mtd[outws].getSpectrum(i).setSpectrumNo(specNum)
149+
150+
logger.debug(f"resulting spectrum numbers: {mtd[outws].getSpectrumNumbers()}")
136151
if self.autoDelete and inws in mtd:
137152
DeleteWorkspace(inws)
138153
assert outws in mtd

0 commit comments

Comments
 (0)