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

Ewm7121 refactor art norm #487

Merged
merged 13 commits into from
Oct 30, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pydantic import BaseModel, root_validator

from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName


class CreateArtificialNormalizationRequest(BaseModel):
runNumber: str
useLiteMode: bool
peakWindowClippingSize: int
smoothingParameter: float
decreaseParameter: bool = True
lss: bool = True
diffractionWorkspace: WorkspaceName
outputWorkspace: WorkspaceName = None

@root_validator(pre=True)
def set_output_workspace(cls, values):
if values.get("diffractionWorkspace") and not values.get("outputWorkspace"):
values["outputWorkspace"] = WorkspaceName(f"{values['diffractionWorkspace']}_artificialNorm")
return values

class Config:
arbitrary_types_allowed = True # Allow arbitrary types like WorkspaceName
extra = "forbid" # Forbid extra fields
validate_assignment = True # Enable dynamic validation
1 change: 1 addition & 0 deletions src/snapred/backend/dao/request/ReductionRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ReductionRequest(BaseModel):
versions: Versions = Versions(None, None)

pixelMasks: List[WorkspaceName] = []
artificialNormalization: Optional[str] = None

# TODO: Move to SNAPRequest
continueFlags: Optional[ContinueWarning.Type] = ContinueWarning.Type.UNSET
Expand Down
5 changes: 5 additions & 0 deletions src/snapred/backend/recipe/GenericRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from snapred.backend.log.logger import snapredLogger
from snapred.backend.recipe.algorithm.BufferMissingColumnsAlgo import BufferMissingColumnsAlgo
from snapred.backend.recipe.algorithm.CalibrationMetricExtractionAlgorithm import CalibrationMetricExtractionAlgorithm
from snapred.backend.recipe.algorithm.CreateArtificialNormalizationAlgo import CreateArtificialNormalizationAlgo
from snapred.backend.recipe.algorithm.DetectorPeakPredictor import DetectorPeakPredictor
from snapred.backend.recipe.algorithm.FitMultiplePeaksAlgorithm import FitMultiplePeaksAlgorithm
from snapred.backend.recipe.algorithm.FocusSpectraAlgorithm import FocusSpectraAlgorithm
Expand Down Expand Up @@ -104,3 +105,7 @@ class ConvertTableToMatrixWorkspaceRecipe(GenericRecipe[ConvertTableToMatrixWork

class BufferMissingColumnsRecipe(GenericRecipe[BufferMissingColumnsAlgo]):
pass


class ArtificialNormalizationRecipe(GenericRecipe[CreateArtificialNormalizationAlgo]):
pass
103 changes: 83 additions & 20 deletions src/snapred/backend/service/ReductionService.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
from pathlib import Path
from typing import Any, Dict, List

from snapred.backend.dao.ingredients import GroceryListItem, ReductionIngredients
from snapred.backend.dao.ingredients import (
ArtificialNormalizationIngredients,
GroceryListItem,
ReductionIngredients,
)
from snapred.backend.dao.reduction.ReductionRecord import ReductionRecord
from snapred.backend.dao.request import (
CreateArtificialNormalizationRequest,
FarmFreshIngredients,
ReductionExportRequest,
ReductionRequest,
Expand All @@ -20,6 +25,7 @@
from snapred.backend.error.StateValidationException import StateValidationException
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.ReductionRecipe import ReductionRecipe
from snapred.backend.service.Service import Service
from snapred.backend.service.SousChef import SousChef
Expand Down Expand Up @@ -72,6 +78,9 @@ def __init__(self):
self.registerPath("checkWritePermissions", self.checkWritePermissions)
self.registerPath("getSavePath", self.getSavePath)
self.registerPath("getStateIds", self.getStateIds)
self.registerPath("validateReduction", self.validateReduction)
self.registerPath("artificialNormalization", self.artificialNormalization)
self.registerPath("grabDiffractionWorkspaceforArtificialNorm", self.grabDiffractionWorkspaceforArtificialNorm)
return

@staticmethod
Expand All @@ -80,45 +89,74 @@ def name():

def validateReduction(self, request: ReductionRequest):
"""
Validate the reduction request.
Validate the reduction request, providing specific messages if normalization
or calibration data is missing. Notify the user if artificial normalization
will be created when normalization is absent.

:param request: a reduction request
:type request: ReductionRequest
"""
continueFlags = ContinueWarning.Type.UNSET
# check if a normalization is present
if not self.dataFactoryService.normalizationExists(request.runNumber, request.useLiteMode):
continueFlags |= ContinueWarning.Type.MISSING_NORMALIZATION
# check if a diffraction calibration is present
if not self.dataFactoryService.calibrationExists(request.runNumber, request.useLiteMode):
message = ""

# Check if a normalization is present
normalizationExists = self.dataFactoryService.normalizationExists(request.runNumber, request.useLiteMode)
# Check if a diffraction calibration is present
calibrationExists = self.dataFactoryService.calibrationExists(request.runNumber, request.useLiteMode)

# Determine the action based on missing components
if not calibrationExists and normalizationExists:
# Case: No calibration but normalization exists
continueFlags |= ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION
message = (
"Warning: diffraction calibration is missing."
"If you continue, default instrument geometry will be used."
)
elif calibrationExists and not normalizationExists:
# Case: Calibration exists but normalization is missing
continueFlags |= ContinueWarning.Type.MISSING_NORMALIZATION
message = (
"Warning: Reduction is missing normalization data. "
"Artificial normalization will be created in place of actual normalization. "
"Would you like to continue?"
)
elif not calibrationExists and not normalizationExists:
# Case: No calibration and no normalization
continueFlags |= (
ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION | ContinueWarning.Type.MISSING_NORMALIZATION
)
message = (
"Warning: Reduction is missing both normalization and calibration data. "
"If you continue, default instrument geometry will be used and data will not be normalized."
"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."
)

# remove any continue flags that are present in the request by xor-ing with the flags
# Remove any continue flags that are present in the request by XOR-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)
continueFlags ^= request.continueFlags & continueFlags

if continueFlags:
raise ContinueWarning(
"Reduction is missing calibration data, continue in uncalibrated mode?", continueFlags
)
# If there are any continue flags set, raise a ContinueWarning with the appropriate message
if continueFlags and message:
raise ContinueWarning(message, continueFlags)

# ... ensure separate continue warnings ...
# Ensure separate continue warnings for permission check
continueFlags = ContinueWarning.Type.UNSET

# check that the user has write permissions to the save directory
# Check that the user has write permissions to the save directory
if not self.checkWritePermissions(request.runNumber):
continueFlags |= ContinueWarning.Type.NO_WRITE_PERMISSIONS

# remove any continue flags that are present in the request by xor-ing with the flags
# Remove any continue flags that are present in the request by XOR-ing with the flags
if request.continueFlags:
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)
continueFlags ^= request.continueFlags & continueFlags

if continueFlags:
raise ContinueWarning(
f"<p>It looks like you don't have permissions to write to "
f"<br><b>{self.getSavePath(request.runNumber)}</b>,<br>"
+ "but you can still save using the workbench tools.</p>"
+ "<p>Would you like to continue anyway?</p>",
"but you can still save using the workbench tools.</p>"
"<p>Would you like to continue anyway?</p>",
continueFlags,
)

Expand All @@ -130,7 +168,6 @@ def reduction(self, request: ReductionRequest):
:param request: a ReductionRequest object holding needed information
:type request: ReductionRequest
"""
self.validateReduction(request)

groupingResults = self.fetchReductionGroupings(request)
request.focusGroups = groupingResults["focusGroups"]
Expand Down Expand Up @@ -424,3 +461,29 @@ def _groupByVanadiumVersion(self, requests: List[SNAPRequest]):
def getCompatibleMasks(self, request: ReductionRequest) -> List[WorkspaceName]:
runNumber, useLiteMode = request.runNumber, request.useLiteMode
return self.dataFactoryService.getCompatibleReductionMasks(runNumber, useLiteMode)

def artificialNormalization(self, request: CreateArtificialNormalizationRequest):
ingredients = ArtificialNormalizationIngredients(
peakWindowClippingSize=request.peakWindowClippingSize,
smoothingParameter=request.smoothingParameter,
decreaseParameter=request.decreaseParameter,
lss=request.lss,
)
artificialNormWorkspace = ArtificialNormalizationRecipe().executeRecipe(
InputWorkspace=request.diffractionWorkspace,
Ingredients=ingredients,
OutputWorkspace=request.outputWorkspace,
)
return artificialNormWorkspace

def grabDiffractionWorkspaceforArtificialNorm(self, request: ReductionRequest):
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")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this logic to a method on groceryService instead? I can imagine it would be very useful elsewhere to be able to read a calibration's run data with a simple (runnumber, litemode, calversion).

It would also be more in line with the rest of the architecture.

diffractionWorkspace = groceries.get("workspace")
return diffractionWorkspace
16 changes: 10 additions & 6 deletions src/snapred/ui/presenter/WorkflowPresenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,7 @@ def handleSkipButtonClicked(self):

def advanceWorkflow(self):
if self.view.currentTab >= self.view.totalNodes - 1:
QMessageBox.information(
self.view,
"‧₊Workflow Complete‧₊",
self.completionMessageLambda(),
)
self.reset()
self.completeWorkflow()
else:
self.view.advanceWorkflow()

Expand Down Expand Up @@ -196,3 +191,12 @@ def continueAnyway(self, continueInfo: ContinueWarning.Model):
else:
raise NotImplementedError(f"Continue anyway handler not implemented: {self.view.tabModel}")
self.handleContinueButtonClicked(self.view.tabModel)

def completeWorkflow(self):
# Directly show the completion message and reset the workflow
QMessageBox.information(
self.view,
"‧₊Workflow Complete‧₊",
self.completionMessageLambda(),
)
self.reset()
4 changes: 4 additions & 0 deletions src/snapred/ui/view/BackendRequestView.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from snapred.ui.widget.LabeledField import LabeledField
from snapred.ui.widget.MultiSelectDropDown import MultiSelectDropDown
from snapred.ui.widget.SampleDropDown import SampleDropDown
from snapred.ui.widget.TrueFalseDropDown import TrueFalseDropDown


class BackendRequestView(QWidget):
Expand Down Expand Up @@ -33,6 +34,9 @@ def _labeledCheckBox(self, label):
def _sampleDropDown(self, label, items=[]):
return SampleDropDown(label, items, self)

def _trueFalseDropDown(self, label):
return TrueFalseDropDown(label, self)

def _multiSelectDropDown(self, label, items=[]):
return MultiSelectDropDown(label, items, self)

Expand Down
Loading