From 7fcf9e7f8183cdbf968b078cf41b9fded499ece7 Mon Sep 17 00:00:00 2001 From: Michael Walsh Date: Thu, 31 Oct 2024 15:57:21 -0400 Subject: [PATCH 1/3] actually applies the artifical norm this time, bases the A.N. on reduction data as per Malcolm, makes A.N. for EACH group, smoothing field allows for appropriate value >1 --- .../dao/ingredients/ReductionIngredients.py | 4 +- .../backend/dao/request/ReductionRequest.py | 3 +- src/snapred/backend/data/LocalDataService.py | 2 +- .../backend/error/StateValidationException.py | 2 +- .../recipe/PreprocessReductionRecipe.py | 8 ++++ .../recipe/ReductionGroupProcessingRecipe.py | 7 +++ src/snapred/backend/recipe/ReductionRecipe.py | 28 ++++++++++- .../CreateArtificialNormalizationAlgo.py | 18 ++++++- .../backend/service/ReductionService.py | 48 +++++++++---------- src/snapred/resources/application.yml | 2 + .../reduction/ArtificialNormalizationView.py | 19 ++++++-- src/snapred/ui/workflow/ReductionWorkflow.py | 14 ++++-- tests/resources/application.yml | 3 ++ .../backend/data/test_LocalDataService.py | 2 +- .../backend/service/test_ReductionService.py | 4 +- tests/unit/meta/test_Decorators.py | 2 +- 16 files changed, 124 insertions(+), 42 deletions(-) diff --git a/src/snapred/backend/dao/ingredients/ReductionIngredients.py b/src/snapred/backend/dao/ingredients/ReductionIngredients.py index d1c01bb89..c25955881 100644 --- a/src/snapred/backend/dao/ingredients/ReductionIngredients.py +++ b/src/snapred/backend/dao/ingredients/ReductionIngredients.py @@ -3,10 +3,11 @@ from pydantic import BaseModel, ConfigDict from snapred.backend.dao.GroupPeakList import GroupPeakList +from snapred.backend.dao.ingredients.ApplyNormalizationIngredients import ApplyNormalizationIngredients # These are from the same `__init__` module, so for the moment, we require the full import specifications. # (That is, not just "from snapred.backend.dao.ingredients import ...".) -from snapred.backend.dao.ingredients.ApplyNormalizationIngredients import ApplyNormalizationIngredients +from snapred.backend.dao.ingredients.ArtificialNormalizationIngredients import ArtificialNormalizationIngredients from snapred.backend.dao.ingredients.GenerateFocussedVanadiumIngredients import GenerateFocussedVanadiumIngredients from snapred.backend.dao.ingredients.PreprocessReductionIngredients import PreprocessReductionIngredients from snapred.backend.dao.ingredients.ReductionGroupProcessingIngredients import ReductionGroupProcessingIngredients @@ -33,6 +34,7 @@ class ReductionIngredients(BaseModel): keepUnfocused: bool convertUnitsTo: str + artificialNormalizationIngredients: Optional[ArtificialNormalizationIngredients] = None # # FACTORY methods to create sub-recipe ingredients: diff --git a/src/snapred/backend/dao/request/ReductionRequest.py b/src/snapred/backend/dao/request/ReductionRequest.py index 2b77e8480..d5a7fffb4 100644 --- a/src/snapred/backend/dao/request/ReductionRequest.py +++ b/src/snapred/backend/dao/request/ReductionRequest.py @@ -2,6 +2,7 @@ from pydantic import BaseModel, ConfigDict, field_validator +from snapred.backend.dao.ingredients import ArtificialNormalizationIngredients from snapred.backend.dao.state.FocusGroup import FocusGroup from snapred.backend.error.ContinueWarning import ContinueWarning from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName @@ -23,7 +24,7 @@ class ReductionRequest(BaseModel): versions: Versions = Versions(None, None) pixelMasks: List[WorkspaceName] = [] - artificialNormalization: Optional[str] = None + artificialNormalizationIngredients: Optional[ArtificialNormalizationIngredients] = None # TODO: Move to SNAPRequest continueFlags: Optional[ContinueWarning.Type] = ContinueWarning.Type.UNSET diff --git a/src/snapred/backend/data/LocalDataService.py b/src/snapred/backend/data/LocalDataService.py index 2674642cd..9daddf1ce 100644 --- a/src/snapred/backend/data/LocalDataService.py +++ b/src/snapred/backend/data/LocalDataService.py @@ -762,7 +762,7 @@ def readCalibrationState(self, runId: str, useLiteMode: bool, version: Optional[ else: raise RuntimeError( "No calibration exists, and you lack permissions to create one." # fmt: skip - " Please contact your CIS." # fmt: skip + " Please contact your IS or CIS." # fmt: skip ) indexer = self.calibrationIndexer(runId, useLiteMode) diff --git a/src/snapred/backend/error/StateValidationException.py b/src/snapred/backend/error/StateValidationException.py index ba1b6f28e..b4a3ea6ad 100644 --- a/src/snapred/backend/error/StateValidationException.py +++ b/src/snapred/backend/error/StateValidationException.py @@ -29,7 +29,7 @@ def __init__(self, exception: Exception): doesFileExist, hasWritePermission = self._checkFileAndPermissions(filePath) if filePath and doesFileExist and hasWritePermission: - self.message = f"The following error occurred:{exceptionStr}\n\n" "Please contact your CIS." + self.message = f"The following error occurred:{exceptionStr}\n\n" "Please contact your IS or CIS." elif filePath and doesFileExist: self.message = f"You do not have write permissions: {filePath}" elif filePath: diff --git a/src/snapred/backend/recipe/PreprocessReductionRecipe.py b/src/snapred/backend/recipe/PreprocessReductionRecipe.py index 1db84f0b4..76da2a20f 100644 --- a/src/snapred/backend/recipe/PreprocessReductionRecipe.py +++ b/src/snapred/backend/recipe/PreprocessReductionRecipe.py @@ -58,6 +58,14 @@ def queueAlgos(self): "Applying diffcal..", InstrumentWorkspace=self.outputWs, CalibrationWorkspace=self.diffcalWs ) + # convert to tof if needed + self.mantidSnapper.ConvertUnits( + "Converting to TOF...", + InputWorkspace=self.outputWs, + Target="TOF", + OutputWorkspace=self.outputWs, + ) + def cook(self, ingredients: Ingredients, groceries: Dict[str, str]) -> Dict[str, Any]: """ Main interface method for the recipe. diff --git a/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py b/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py index f99b58335..c39016948 100644 --- a/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py +++ b/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py @@ -53,6 +53,13 @@ def queueAlgos(self): # ) # self.rawInput = self.geometryOutputWS + self.mantidSnapper.ConvertUnits( + "Converting to TOF...", + InputWorkspace=self.rawInput, + Target="TOF", + OutputWorkspace=self.rawInput, + ) + self.mantidSnapper.FocusSpectraAlgorithm( "Focusing Spectra...", InputWorkspace=self.rawInput, diff --git a/src/snapred/backend/recipe/ReductionRecipe.py b/src/snapred/backend/recipe/ReductionRecipe.py index 57b79160d..cc6253e22 100644 --- a/src/snapred/backend/recipe/ReductionRecipe.py +++ b/src/snapred/backend/recipe/ReductionRecipe.py @@ -4,6 +4,7 @@ from snapred.backend.log.logger import snapredLogger from snapred.backend.recipe.ApplyNormalizationRecipe import ApplyNormalizationRecipe from snapred.backend.recipe.GenerateFocussedVanadiumRecipe import GenerateFocussedVanadiumRecipe +from snapred.backend.recipe.GenericRecipe import ArtificialNormalizationRecipe from snapred.backend.recipe.PreprocessReductionRecipe import PreprocessReductionRecipe from snapred.backend.recipe.Recipe import Recipe, WorkspaceName from snapred.backend.recipe.ReductionGroupProcessingRecipe import ReductionGroupProcessingRecipe @@ -123,6 +124,22 @@ def _prepareUnfocusedData( self.mantidSnapper.executeQueue() return self.unfocWs + def _prepareArtificialNormalization(self, inputWorkspace: str, groupIndex: int) -> str: + """ + After the real data has been group processed, we can generate a fake normalization workspace + + :param inputWorkspace: The real data workspace that has been group processed + :return: The artificial normalization workspace + """ + normalizationWorkspace = self._getNormalizationWorkspaceName(groupIndex) + normalizationWorkspace = ArtificialNormalizationRecipe().executeRecipe( + InputWorkspace=inputWorkspace, + Ingredients=self.ingredients.artificialNormalizationIngredients, + OutputWorkspace=normalizationWorkspace, + ) + self.groceries["normalizationWorkspace"] = normalizationWorkspace + return normalizationWorkspace + def _applyRecipe(self, recipe: Type[Recipe], ingredients_, **kwargs): if "inputWorkspace" in kwargs: inputWorkspace = kwargs["inputWorkspace"] @@ -141,6 +158,9 @@ def _applyRecipe(self, recipe: Type[Recipe], ingredients_, **kwargs): ) ) + def _getNormalizationWorkspaceName(self, groupingIndex: int): + return f"reduced_normalization_{groupingIndex}_{wnvf.formatTimestamp(self.ingredients.timestamp)}" + def _prepGroupingWorkspaces(self, groupingIndex: int): # TODO: We need the wng to be able to deconstruct the workspace name # so that we can appropriately name the cloned workspaces @@ -156,7 +176,7 @@ def _prepGroupingWorkspaces(self, groupingIndex: int): if self.normalizationWs: normalizationClone = self._cloneWorkspace( self.normalizationWs, - f"reduced_normalization_{groupingName}_{wnvf.formatTimestamp(timestamp)}", + self._getNormalizationWorkspaceName(groupingIndex), ) self.groceries["normalizationWorkspace"] = normalizationClone return sampleClone, normalizationClone @@ -239,6 +259,12 @@ def execute(self): ) self._cloneIntermediateWorkspace(normalizationClone, f"normalization_FoocussedVanadium_{groupingIndex}") + # if there was no normalization and the user elected to use artificial normalization + # generate one given the params and the processed sample data + # Skipping the above steps as they are accounted for in generating the artificial normalization + if self.ingredients.artificialNormalizationIngredients: + normalizationClone = self._prepareArtificialNormalization(sampleClone, groupingIndex) + # 4. ApplyNormalizationRecipe self._applyRecipe( ApplyNormalizationRecipe, diff --git a/src/snapred/backend/recipe/algorithm/CreateArtificialNormalizationAlgo.py b/src/snapred/backend/recipe/algorithm/CreateArtificialNormalizationAlgo.py index 3d6263f14..741b99a33 100644 --- a/src/snapred/backend/recipe/algorithm/CreateArtificialNormalizationAlgo.py +++ b/src/snapred/backend/recipe/algorithm/CreateArtificialNormalizationAlgo.py @@ -3,6 +3,7 @@ import numpy as np from mantid.api import ( AlgorithmFactory, + IEventWorkspace, MatrixWorkspaceProperty, PropertyMode, PythonAlgorithm, @@ -27,7 +28,6 @@ def PyInit(self): "", Direction.Input, PropertyMode.Mandatory, - validator=WorkspaceUnitValidator("dSpacing"), ), doc="Workspace that contains calibrated and focused diffraction data.", ) @@ -120,6 +120,22 @@ def PyExec(self): InputWorkspace=self.inputWorkspaceName, OutputWorkspace=self.outputWorkspaceName, ) + # if input workspace is an eventworkspace, convert it to a histogram workspace + if isinstance(self.mantidSnapper.mtd[self.inputWorkspaceName], IEventWorkspace): + # convert it to a histogram + self.mantidSnapper.ConvertToMatrixWorkspace( + "Converting event workspace to histogram...", + InputWorkspace=self.outputWorkspaceName, + OutputWorkspace=self.outputWorkspaceName, + ) + + self.mantidSnapper.ConvertUnits( + "Converting to dSpacing...", + InputWorkspace=self.outputWorkspaceName, + Target="dSpacing", + OutputWorkspace=self.outputWorkspaceName, + ) + self.mantidSnapper.executeQueue() self.inputWorkspace = self.mantidSnapper.mtd[self.inputWorkspaceName] self.outputWorkspace = self.mantidSnapper.mtd[self.outputWorkspaceName] diff --git a/src/snapred/backend/service/ReductionService.py b/src/snapred/backend/service/ReductionService.py index 1f6632367..8aebb8c44 100644 --- a/src/snapred/backend/service/ReductionService.py +++ b/src/snapred/backend/service/ReductionService.py @@ -28,6 +28,7 @@ from snapred.backend.log.logger import snapredLogger from snapred.backend.recipe.algorithm.MantidSnapper import MantidSnapper from snapred.backend.recipe.GenericRecipe import ArtificialNormalizationRecipe +from snapred.backend.recipe.ReductionGroupProcessingRecipe import ReductionGroupProcessingRecipe from snapred.backend.recipe.ReductionRecipe import ReductionRecipe from snapred.backend.service.Service import Service from snapred.backend.service.SousChef import SousChef @@ -82,7 +83,7 @@ def __init__(self): self.registerPath("getStateIds", self.getStateIds) self.registerPath("validateReduction", self.validateReduction) self.registerPath("artificialNormalization", self.artificialNormalization) - self.registerPath("grabDiffractionWorkspaceforArtificialNorm", self.grabDiffractionWorkspaceforArtificialNorm) + self.registerPath("grabWorkspaceforArtificialNorm", self.grabWorkspaceforArtificialNorm) return @staticmethod @@ -189,6 +190,7 @@ def reduction(self, request: ReductionRequest): groupingResults = self.fetchReductionGroupings(request) request.focusGroups = groupingResults["focusGroups"] ingredients = self.prepReductionIngredients(request) + ingredients.artificialNormalizationIngredients = request.artificialNormalizationIngredients groceries = self.fetchReductionGroceries(request) # attach the list of grouping workspaces to the grocery dictionary @@ -520,27 +522,23 @@ def artificialNormalization(self, request: CreateArtificialNormalizationRequest) ) return artificialNormWorkspace - def grabDiffractionWorkspaceforArtificialNorm(self, request: ReductionRequest): - try: - calVersion = None - calVersion = self.dataFactoryService.getThisOrLatestCalibrationVersion( - request.runNumber, request.useLiteMode - ) - calRecord = self.dataFactoryService.getCalibrationRecord(request.runNumber, request.useLiteMode, calVersion) - filePath = self.dataFactoryService.getCalibrationDataPath( - request.runNumber, request.useLiteMode, calVersion - ) - diffCalOutput = calRecord.workspaces[wngt.DIFFCAL_OUTPUT][0] - diffcalOutputFilePath = str(filePath) + "/" + str(diffCalOutput) + ".nxs.h5" - - groceries = self.groceryService.fetchWorkspace(diffcalOutputFilePath, "diffractionWorkspace") - diffractionWorkspace = groceries.get("workspace") - except: # noqa: E722 - raise RuntimeError( - "This feature is not yet implemented. " - "Artificial normalization cannot currently be made for uncalibrated data as we are missing peak positions. " # noqa: E501 - "We are working on a solution to this problem.\n\n " - f"No calibration record found for run number: {request.runNumber}.\n" - "Please create calibration data for this run number and try again." - ) - return diffractionWorkspace + def grabWorkspaceforArtificialNorm(self, request: ReductionRequest): + # 1. Load raw run data + self.groceryClerk.name("inputWorkspace").neutron(request.runNumber).useLiteMode(request.useLiteMode).add() + runWorkspace = self.groceryService.fetchGroceryList(self.groceryClerk.buildList())[0] + # 2. Load Column group TODO: Future work to apply a more general approach + groups = self.loadAllGroupings(request.runNumber, request.useLiteMode) + # find column group + columnGroup = next((group for group in groups["focusGroups"] if "column" in group.name.lower()), None) + columnGroupWorkspace = next( + (group for group in groups["groupingWorkspaces"] if "column" in group.lower()), None + ) + request.focusGroups = [columnGroup] + # 2.5. get ingredients + ingredients = self.prepReductionIngredients(request) + groceries = { + "inputWorkspace": runWorkspace, + "groupingWorkspace": columnGroupWorkspace, + } + # 3. Diffraction Focus Spectra + return ReductionGroupProcessingRecipe().cook(ingredients.groupProcessing(0), groceries) diff --git a/src/snapred/resources/application.yml b/src/snapred/resources/application.yml index 2bddf5774..8d6c5ba56 100644 --- a/src/snapred/resources/application.yml +++ b/src/snapred/resources/application.yml @@ -226,5 +226,7 @@ metadata: ui: default: + reduction: + smoothing: 5 workflow: completionMessage: "‧₊‧₊The workflow has been completed successfully!‧₊‧₊" diff --git a/src/snapred/ui/view/reduction/ArtificialNormalizationView.py b/src/snapred/ui/view/reduction/ArtificialNormalizationView.py index f08c5a5b2..3f7732427 100644 --- a/src/snapred/ui/view/reduction/ArtificialNormalizationView.py +++ b/src/snapred/ui/view/reduction/ArtificialNormalizationView.py @@ -12,7 +12,6 @@ from snapred.meta.Config import Config from snapred.meta.decorators.Resettable import Resettable from snapred.ui.view.BackendRequestView import BackendRequestView -from snapred.ui.widget.SmoothingSlider import SmoothingSlider from workbench.plotting.figuremanager import MantidFigureCanvas from workbench.plotting.toolbar import WorkbenchNavigationToolbar @@ -44,7 +43,9 @@ def __init__(self, parent=None): x.setEnabled(False) # create the adjustment controls - self.smoothingSlider = self._labeledField("Smoothing", SmoothingSlider()) + self.smoothingSlider = self._labeledField("Smoothing", QLineEdit()) + self.smoothingSlider.field.setText(str(Config["ui.default.reduction.smoothing"])) + self.peakWindowClippingSize = self._labeledField( "Peak Window Clipping Size", QLineEdit(str(Config["constants.ArtificialNormalization.peakWindowClippingSize"])), @@ -102,7 +103,7 @@ def updateFields(self, smoothingParameter, lss, decreaseParameter): def emitValueChange(self): # verify the fields before recalculation try: - smoothingValue = self.smoothingSlider.field.value() + smoothingValue = float(self.smoothingSlider.field.text()) lss = self.lssDropdown.currentIndex() == "True" decreaseParameter = self.decreaseParameterDropdown.currentIndex == "True" peakWindowClippingSize = int(self.peakWindowClippingSize.field.text()) @@ -190,3 +191,15 @@ def clearView(self): widget = self.layout.itemAt(i).widget() if widget is not None and widget != self.messageLabel: widget.deleteLater() # Delete the widget + + def getPeakWindowClippingSize(self): + return int(self.peakWindowClippingSize.field.text()) + + def getSmoothingParameter(self): + return float(self.smoothingSlider.field.text()) + + def getLSS(self): + return self.lssDropdown.currentIndex() == 1 + + def getDecreaseParameter(self): + return self.decreaseParameterDropdown.currentIndex() == 1 diff --git a/src/snapred/ui/workflow/ReductionWorkflow.py b/src/snapred/ui/workflow/ReductionWorkflow.py index f3d4a3b51..8fc1a60aa 100644 --- a/src/snapred/ui/workflow/ReductionWorkflow.py +++ b/src/snapred/ui/workflow/ReductionWorkflow.py @@ -2,6 +2,7 @@ from qtpy.QtCore import Slot +from snapred.backend.dao.ingredients import ArtificialNormalizationIngredients from snapred.backend.dao.request import ( CreateArtificialNormalizationRequest, ReductionExportRequest, @@ -175,7 +176,7 @@ def _triggerReduction(self, workflowPresenter): response = self.request(path="reduction/validateReduction", payload=request_) if ContinueWarning.Type.MISSING_NORMALIZATION in self.continueAnywayFlags: self._artificialNormalizationView.updateRunNumber(runNumber) - response = self.request(path="reduction/grabDiffractionWorkspaceforArtificialNorm", payload=request_) + response = self.request(path="reduction/grabWorkspaceforArtificialNorm", payload=request_) self._artificialNormalization(workflowPresenter, response.data, runNumber) else: # Proceed with reduction if artificial normalization is not needed @@ -195,7 +196,7 @@ def _artificialNormalization(self, workflowPresenter, responseData, runNumber): runNumber=runNumber, useLiteMode=self._reductionRequestView.liteModeToggle.field.getState(), peakWindowClippingSize=int(self._artificialNormalizationView.peakWindowClippingSize.field.text()), - smoothingParameter=self._artificialNormalizationView.smoothingSlider.field.value(), + smoothingParameter=self._artificialNormalizationView.getSmoothingParameter(), decreaseParameter=self._artificialNormalizationView.decreaseParameterDropdown.currentIndex() == 1, lss=self._artificialNormalizationView.lssDropdown.currentIndex() == 1, diffractionWorkspace=responseData, @@ -232,7 +233,12 @@ def onArtificialNormalizationValueChange(self, smoothingValue, lss, decreasePara def _continueWithNormalization(self, workflowPresenter): # noqa: ARG002 """Continues the workflow using the artificial normalization workspace.""" - artificialNormWorkspace = self._artificialNormalizationView.artificialNormWorkspace + artificialNormIngredients = ArtificialNormalizationIngredients( + peakWindowClippingSize=self._artificialNormalizationView.getPeakWindowClippingSize(), + smoothingParameter=self._artificialNormalizationView.getSmoothingParameter(), + decreaseParameter=self._artificialNormalizationView.getDecreaseParameter(), + lss=self._artificialNormalizationView.getLSS(), + ) pixelMasks = self._reconstructPixelMaskNames(self._reductionRequestView.getPixelMasks()) timestamp = self.request(path="reduction/getUniqueTimestamp").data @@ -244,7 +250,7 @@ def _continueWithNormalization(self, workflowPresenter): # noqa: ARG002 pixelMasks=pixelMasks, keepUnfocused=self._reductionRequestView.retainUnfocusedDataCheckbox.isChecked(), convertUnitsTo=self._reductionRequestView.convertUnitsDropdown.currentText(), - artificialNormalization=artificialNormWorkspace, + artificialNormalizationIngredients=artificialNormIngredients, ) response = self.request(path="reduction/", payload=request_) diff --git a/tests/resources/application.yml b/tests/resources/application.yml index 5391a070c..9517ed290 100644 --- a/tests/resources/application.yml +++ b/tests/resources/application.yml @@ -103,6 +103,7 @@ calibration: fitting: minSignal2Noise: 10 + mantid: workspace: nameTemplate: @@ -240,5 +241,7 @@ metadata: ui: default: + reduction: + smoothing: 5 workflow: completionMessage: "‧₊‧₊The workflow has been completed successfully!‧₊‧₊" diff --git a/tests/unit/backend/data/test_LocalDataService.py b/tests/unit/backend/data/test_LocalDataService.py index 5765021f5..d74adaff4 100644 --- a/tests/unit/backend/data/test_LocalDataService.py +++ b/tests/unit/backend/data/test_LocalDataService.py @@ -1880,7 +1880,7 @@ def test_readWriteCalibrationState_noWritePermissions(): with pytest.raises( RuntimeError, - match=r".*No calibration exists, and you lack permissions to create one. Please contact your CIS.*", + match=r".*No calibration exists, and you lack permissions to create one. Please contact your IS or CIS.*", ): localDataService.readCalibrationState("123", True) diff --git a/tests/unit/backend/service/test_ReductionService.py b/tests/unit/backend/service/test_ReductionService.py index efaf0661c..d11a0aa6e 100644 --- a/tests/unit/backend/service/test_ReductionService.py +++ b/tests/unit/backend/service/test_ReductionService.py @@ -420,7 +420,7 @@ def test_artificialNormalization(self, mockArtificialNormalizationRecipe): @mock.patch(thisService + "GroceryService") @mock.patch(thisService + "DataFactoryService") - def test_grabDiffractionWorkspaceforArtificialNorm(self, mockDataFactoryService, mockGroceryService): + def test_grabWorkspaceforArtificialNorm(self, mockDataFactoryService, mockGroceryService): self.instance.groceryService = mockGroceryService self.instance.dataFactoryService = mockDataFactoryService @@ -445,7 +445,7 @@ def test_grabDiffractionWorkspaceforArtificialNorm(self, mockDataFactoryService, mockGroceryService.fetchWorkspace = mock.Mock(return_value={"workspace": "mock_diffraction_workspace"}) - result = self.instance.grabDiffractionWorkspaceforArtificialNorm(request) + result = self.instance.grabWorkspaceforArtificialNorm(request) expected_file_path = "mock/path/to/calibration/mock_diffraction_workspace.nxs.h5" mockGroceryService.fetchWorkspace.assert_called_once_with(expected_file_path, "diffractionWorkspace") diff --git a/tests/unit/meta/test_Decorators.py b/tests/unit/meta/test_Decorators.py index 551a73e65..09c111c54 100644 --- a/tests/unit/meta/test_Decorators.py +++ b/tests/unit/meta/test_Decorators.py @@ -131,7 +131,7 @@ def test_stateValidationExceptionWritePerms(): raise StateValidationException(exception) # Asserting that the error message is as expected - assert "The following error occurred:Test Exception\n\nPlease contact your CIS." in str(excinfo.value) + assert "The following error occurred:Test Exception\n\nPlease contact your IS or CIS." in str(excinfo.value) @ExceptionHandler(StateValidationException) From 200023c5408c965c621d3d91d6ca3ae87e98ca57 Mon Sep 17 00:00:00 2001 From: Michael Walsh Date: Fri, 1 Nov 2024 15:31:35 -0400 Subject: [PATCH 2/3] fix failing tests --- .../recipe/test_ReductionGroupProcessingRecipe.py | 11 +++++++++-- tests/unit/backend/recipe/test_ReductionRecipe.py | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py b/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py index ef4ff10cb..bb7325a8e 100644 --- a/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py +++ b/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py @@ -48,13 +48,20 @@ def test_queueAlgos(self): recipe.queueAlgos() queuedAlgos = recipe.mantidSnapper._algorithmQueue - diffFoc = queuedAlgos[0] - normCurr = queuedAlgos[1] + i = 0 + convertUnits = queuedAlgos[i] + diffFoc = queuedAlgos[i := i + 1] + normCurr = queuedAlgos[i := i + 1] + assert convertUnits[0] == "ConvertUnits" assert diffFoc[0] == "FocusSpectraAlgorithm" assert normCurr[0] == "NormalizeByCurrentButTheCorrectWay" + assert convertUnits[1] == "Converting to TOF..." assert diffFoc[1] == "Focusing Spectra..." assert normCurr[1] == "Normalizing Current ... but the correct way!" + assert convertUnits[2]["InputWorkspace"] == groceries["inputWorkspace"] + assert convertUnits[2]["OutputWorkspace"] == groceries["inputWorkspace"] + assert convertUnits[2]["Target"] == "TOF" assert diffFoc[2]["InputWorkspace"] == groceries["inputWorkspace"] assert diffFoc[2]["GroupingWorkspace"] == groceries["groupingWorkspace"] assert diffFoc[2]["OutputWorkspace"] == groceries["inputWorkspace"] diff --git a/tests/unit/backend/recipe/test_ReductionRecipe.py b/tests/unit/backend/recipe/test_ReductionRecipe.py index a55af1a2e..e9f095b30 100644 --- a/tests/unit/backend/recipe/test_ReductionRecipe.py +++ b/tests/unit/backend/recipe/test_ReductionRecipe.py @@ -177,6 +177,7 @@ def test_keepUnfocusedData(self, mockMtd): # Set up ingredients and other variables for the recipe recipe.groceries = {} recipe.ingredients = mock.Mock() + recipe.ingredients.artificialNormalizationIngredients = None recipe.ingredients.groupProcessing = mock.Mock( return_value=lambda groupingIndex: f"groupProcessing_{groupingIndex}" ) @@ -369,6 +370,7 @@ def test_execute(self, mockMtd): # Set up ingredients and other variables for the recipe recipe.groceries = {} recipe.ingredients = mock.Mock() + recipe.ingredients.artificialNormalizationIngredients = None recipe.ingredients.groupProcessing = mock.Mock( return_value=lambda groupingIndex: f"groupProcessing_{groupingIndex}" ) From 688cd89eb34e5cbed9ea59c47a1a18d8f3643080 Mon Sep 17 00:00:00 2001 From: Michael Walsh Date: Fri, 1 Nov 2024 16:10:11 -0400 Subject: [PATCH 3/3] fix grabWorkspaceForArtificialNorm --- .../backend/service/test_ReductionService.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/tests/unit/backend/service/test_ReductionService.py b/tests/unit/backend/service/test_ReductionService.py index d11a0aa6e..4702ec07e 100644 --- a/tests/unit/backend/service/test_ReductionService.py +++ b/tests/unit/backend/service/test_ReductionService.py @@ -418,12 +418,15 @@ def test_artificialNormalization(self, mockArtificialNormalizationRecipe): ) assert result == mockResult + @mock.patch(thisService + "ReductionGroupProcessingRecipe") @mock.patch(thisService + "GroceryService") @mock.patch(thisService + "DataFactoryService") - def test_grabWorkspaceforArtificialNorm(self, mockDataFactoryService, mockGroceryService): + def test_grabWorkspaceforArtificialNorm( + self, mockDataFactoryService, mockGroceryService, mockReductionGroupProcessingRecipe + ): self.instance.groceryService = mockGroceryService self.instance.dataFactoryService = mockDataFactoryService - + self.instance.groceryClerk = mock.Mock() request = ReductionRequest( runNumber="123", useLiteMode=False, @@ -432,26 +435,26 @@ def test_grabWorkspaceforArtificialNorm(self, mockDataFactoryService, mockGrocer pixelMasks=[], focusGroups=[FocusGroup(name="apple", definition="path/to/grouping")], ) + mockIngredients = mock.Mock() + runWorkspaceName = "runworkspace" + columnGroupingWS = "columnGroupingWS" + self.instance.groceryService.fetchGroceryList.return_value = [runWorkspaceName] + self.instance.loadAllGroupings = mock.Mock( + return_value={ + "groupingWorkspaces": [columnGroupingWS], + "focusGroups": [mock.MagicMock(name="columnFocusGroup")], + } + ) + self.instance.prepReductionIngredients = mock.Mock(return_value=mockIngredients) - mockCalVersion = 1 - mockDataFactoryService.getThisOrLatestCalibrationVersion = mock.Mock(return_value=mockCalVersion) - - mockCalRecord = mock.Mock() - mockCalRecord.workspaces = {"diffCalOutput": ["mock_diffraction_workspace"]} - - mockDataFactoryService.getCalibrationRecord = mock.Mock(return_value=mockCalRecord) - - mockDataFactoryService.getCalibrationDataPath = mock.Mock(return_value="mock/path/to/calibration") - - mockGroceryService.fetchWorkspace = mock.Mock(return_value={"workspace": "mock_diffraction_workspace"}) - - result = self.instance.grabWorkspaceforArtificialNorm(request) + self.instance.grabWorkspaceforArtificialNorm(request) - expected_file_path = "mock/path/to/calibration/mock_diffraction_workspace.nxs.h5" - mockGroceryService.fetchWorkspace.assert_called_once_with(expected_file_path, "diffractionWorkspace") + groceries = { + "inputWorkspace": runWorkspaceName, + "groupingWorkspace": columnGroupingWS, + } - # Verify the result - assert result == "mock_diffraction_workspace" + mockReductionGroupProcessingRecipe().cook.assert_called_once_with(mockIngredients.groupProcessing(0), groceries) class TestReductionServiceMasks: