Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

actually applies the artifical norm this time, bases the A.N. on redu… #489

Draft
wants to merge 3 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/snapred/backend/dao/ingredients/ReductionIngredients.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +34,7 @@ class ReductionIngredients(BaseModel):

keepUnfocused: bool
convertUnitsTo: str
artificialNormalizationIngredients: Optional[ArtificialNormalizationIngredients] = None

#
# FACTORY methods to create sub-recipe ingredients:
Expand Down
3 changes: 2 additions & 1 deletion src/snapred/backend/dao/request/ReductionRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/snapred/backend/data/LocalDataService.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/snapred/backend/error/StateValidationException.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions src/snapred/backend/recipe/PreprocessReductionRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 27 additions & 1 deletion src/snapred/backend/recipe/ReductionRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -123,6 +124,22 @@
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(

Check warning on line 135 in src/snapred/backend/recipe/ReductionRecipe.py

View check run for this annotation

Codecov / codecov/patch

src/snapred/backend/recipe/ReductionRecipe.py#L134-L135

Added lines #L134 - L135 were not covered by tests
InputWorkspace=inputWorkspace,
Ingredients=self.ingredients.artificialNormalizationIngredients,
OutputWorkspace=normalizationWorkspace,
)
self.groceries["normalizationWorkspace"] = normalizationWorkspace
return normalizationWorkspace

Check warning on line 141 in src/snapred/backend/recipe/ReductionRecipe.py

View check run for this annotation

Codecov / codecov/patch

src/snapred/backend/recipe/ReductionRecipe.py#L140-L141

Added lines #L140 - L141 were not covered by tests

def _applyRecipe(self, recipe: Type[Recipe], ingredients_, **kwargs):
if "inputWorkspace" in kwargs:
inputWorkspace = kwargs["inputWorkspace"]
Expand All @@ -141,6 +158,9 @@
)
)

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
Expand All @@ -156,7 +176,7 @@
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
Expand Down Expand Up @@ -239,6 +259,12 @@
)
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)

Check warning on line 266 in src/snapred/backend/recipe/ReductionRecipe.py

View check run for this annotation

Codecov / codecov/patch

src/snapred/backend/recipe/ReductionRecipe.py#L266

Added line #L266 was not covered by tests

# 4. ApplyNormalizationRecipe
self._applyRecipe(
ApplyNormalizationRecipe,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np
from mantid.api import (
AlgorithmFactory,
IEventWorkspace,
MatrixWorkspaceProperty,
PropertyMode,
PythonAlgorithm,
Expand All @@ -27,7 +28,6 @@ def PyInit(self):
"",
Direction.Input,
PropertyMode.Mandatory,
validator=WorkspaceUnitValidator("dSpacing"),
),
doc="Workspace that contains calibrated and focused diffraction data.",
)
Expand Down Expand Up @@ -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]
Expand Down
48 changes: 23 additions & 25 deletions src/snapred/backend/service/ReductionService.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 2 additions & 0 deletions src/snapred/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,7 @@ metadata:

ui:
default:
reduction:
smoothing: 5
workflow:
completionMessage: "‧₊‧₊The workflow has been completed successfully!‧₊‧₊"
19 changes: 16 additions & 3 deletions src/snapred/ui/view/reduction/ArtificialNormalizationView.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"])),
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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
14 changes: 10 additions & 4 deletions src/snapred/ui/workflow/ReductionWorkflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from qtpy.QtCore import Slot

from snapred.backend.dao.ingredients import ArtificialNormalizationIngredients
from snapred.backend.dao.request import (
CreateArtificialNormalizationRequest,
ReductionExportRequest,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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

Expand All @@ -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_)
Expand Down
3 changes: 3 additions & 0 deletions tests/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ calibration:
fitting:
minSignal2Noise: 10


mantid:
workspace:
nameTemplate:
Expand Down Expand Up @@ -240,5 +241,7 @@ metadata:

ui:
default:
reduction:
smoothing: 5
workflow:
completionMessage: "‧₊‧₊The workflow has been completed successfully!‧₊‧₊"
2 changes: 1 addition & 1 deletion tests/unit/backend/data/test_LocalDataService.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading