Skip to content

Commit 01bad8b

Browse files
authored
Merge pull request #623 from neutrons/EWM12331_notify_default_diffcal
ContinueWarning for DEFAULT_DIFFRACTION_CALIBRATION
2 parents 9b2de3e + 90a4725 commit 01bad8b

19 files changed

+488
-293
lines changed

pixi.lock

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

src/snapred/backend/dao/WorkspaceMetadata.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ class DiffcalStateMetadata(StrEnum):
1111
"""Class to hold tags related to a workspace"""
1212

1313
UNSET: str = UNSET # the default condition before any settings
14-
DEFAULT: str = "default" # the default condition before any settings
15-
EXISTS: str = "exists" # the state exists and the corresponding .h5 file located
14+
EXISTS: str = "exists" # the state exists and the corresponding .h5 file has been located
1615
ALTERNATE: str = "alternate" # the user supplied an alternate .h5 that they believe is usable.
17-
NONE: str = "none" # proceed using the defaul (IDF) geometry.
16+
# Note: `MISSING_DIFFRACTION_CALIBRATION` is now the same as the former `DEFAULT_DIFFRACTION_CALIBRATION`.
17+
NONE: str = "none" # proceed using the default (IDF + parameter values) geometry.
1818

1919

2020
class NormalizationStateMetadata(StrEnum):
2121
"""Class to hold tags related to a workspace"""
2222

2323
UNSET: str = UNSET # the default condition before any settings
24-
EXISTS: str = "exists" # the state exists and the corresponding .h5 file located
24+
EXISTS: str = "exists" # the state exists and the corresponding .h5 file has been located
2525
ALTERNATE: str = "alternate" # the user supplied an alternate .h5 that they believe is usable
2626
NONE: str = "none" # proceed without applying any normalization
2727
FAKE: str = "fake" # proceed by creating a "fake vanadium"
@@ -31,7 +31,7 @@ class ParticleNormalizationMethod(StrEnum):
3131
"""Class to hold tags related to a workspace"""
3232

3333
UNSET: str = UNSET # the default condition before any settings
34-
PROTON_CHARGE: str = "proton_charge" # the state exists and the corresponding .h5 file located
34+
PROTON_CHARGE: str = "proton_charge" # the state exists and the corresponding .h5 file has been located
3535
MONITOR_COUNTS: str = "monitor_counts" # the user supplied an alternate .h5 that they believe is usable
3636
NONE: str = "none" # proceed without applying any normalization
3737

src/snapred/backend/dao/indexing/Versioning.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
from snapred.meta.Enum import StrEnum
33

44

5-
# NOTE: This should probably not be reconfigurable at runtime.
6-
# This would be liable to only cause indexing issues.
5+
# NOTES:
6+
# * This should probably not be reconfigurable at runtime.
7+
# This would be liable to only cause indexing issues.
8+
# * `VERSION_START` is used as the DEFAULT version number.
9+
# For example, when diffraction calibration would otherwise
10+
# be missing and the default diffraction calibration is used.
711
def VERSION_START():
812
return Config["version.start"]
913

@@ -16,7 +20,9 @@ class VersionState(StrEnum):
1620
LEGACY_INST_PRM = "1.4"
1721

1822

23+
""" TODO: remove this.
1924
# I'm not sure why ci is failing without this, it doesn't seem to be used anywhere
2025
VERSION_DEFAULT = VersionState.DEFAULT
26+
"""
2127

2228
Version = int | VersionState

src/snapred/backend/data/LocalDataService.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,8 @@ def _writeDefaultDiffCalTable(self, runNumber: str, useLiteMode: bool):
10901090
grocer.deleteWorkspaceUnconditional(outWS)
10911091

10921092
def generateInstrumentState(self, runId: str):
1093+
# Generate a default instrument state.
1094+
10931095
# Read the detector state from the PV data file,
10941096
# and generate the stateID SHA.
10951097
stateId, detectorState = self.generateStateId(runId)

src/snapred/backend/error/ContinueWarning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ class ContinueWarning(Exception):
1313

1414
class Type(Flag):
1515
UNSET = 0
16+
# Note: `MISSING_DIFFRACTION_CALIBRATION` is now the same as the former `DEFAULT_DIFFRACTION_CALIBRATION`.
1617
MISSING_DIFFRACTION_CALIBRATION = auto()
1718
ALTERNATE_DIFFRACTION_CALIBRATION = auto()
18-
DEFAULT_DIFFRACTION_CALIBRATION = auto()
1919
MISSING_NORMALIZATION = auto()
2020
LOW_PEAK_COUNT = auto()
2121
NO_WRITE_PERMISSIONS = auto()

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,17 @@ def PyExec(self):
149149
)
150150

151151
self.mantidSnapper.executeQueue()
152-
self.outputWorkspace = self.mantidSnapper.mtd[self.outputWorkspaceName]
153152

154-
# Apply peak clipping to each histogram in the workspace
155-
for i in range(self.outputWorkspace.getNumberHistograms()):
156-
dataY = self.outputWorkspace.readY(i)
153+
for i in range(self.mantidSnapper.mtd[self.outputWorkspaceName].getNumberHistograms()):
154+
dataY = self.mantidSnapper.mtd[self.outputWorkspaceName].readY(i)
157155
clippedData = self.peakClip(
158156
data=dataY,
159157
winSize=self.peakWindowClippingSize,
160158
decrese=self.decreaseParameter,
161159
LLS=self.LSS,
162160
smoothing=self.smoothingParameter,
163161
)
164-
self.outputWorkspace.setY(i, clippedData)
162+
self.mantidSnapper.mtd[self.outputWorkspaceName].setY(i, clippedData)
165163

166164
# Set the output workspace property
167165
self.setProperty("OutputWorkspace", self.outputWorkspaceName)

src/snapred/backend/service/NormalizationService.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from snapred.backend.dao import Limit
55
from snapred.backend.dao.indexing.IndexEntry import IndexEntry
6+
from snapred.backend.dao.indexing.Versioning import VERSION_START
67
from snapred.backend.dao.ingredients import (
78
GroceryListItem,
89
)
@@ -235,11 +236,10 @@ def normalization(self, request: NormalizationRequest):
235236
).dict()
236237

237238
def _markWorkspaceMetadata(self, request: NormalizationRequest, workspace: WorkspaceName):
238-
calibrationState = (
239-
DiffcalStateMetadata.DEFAULT
240-
if ContinueWarning.Type.DEFAULT_DIFFRACTION_CALIBRATION in request.continueFlags
241-
else DiffcalStateMetadata.EXISTS
242-
)
239+
if ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION in request.continueFlags:
240+
calibrationState = DiffcalStateMetadata.NONE
241+
else:
242+
calibrationState = DiffcalStateMetadata.EXISTS
243243
metadata = WorkspaceMetadata(diffcalState=calibrationState, normalizationState=NormalizationStateMetadata.UNSET)
244244
self.groceryService.writeWorkspaceMetadataAsTags(workspace, metadata)
245245

@@ -265,22 +265,34 @@ def _validateDiffractionCalibrationExists(self, request: NormalizationRequest):
265265

266266
self.sousChef.verifyCalibrationExists(request.runNumber, request.useLiteMode)
267267
state, _ = self.dataFactoryService.constructStateId(request.runNumber)
268+
269+
# Notes:
270+
# * At this point, the state will be initialized.
271+
# * In an initialized state, `calVersion` will never be `None`.
272+
# * `MISSING_DIFFRACTION_CALIBRATION` is now the same as the former `DEFAULT_DIFFRACTION_CALIBRATION`.
273+
# * The default calibration uses `VERSION_START`.
268274
calVersion = self.dataFactoryService.getLatestApplicableCalibrationVersion(
269275
request.runNumber, request.useLiteMode, state
270276
)
271277
if calVersion is None:
272-
continueFlags = continueFlags | ContinueWarning.Type.DEFAULT_DIFFRACTION_CALIBRATION
278+
raise RuntimeError(
279+
"Usage error: for an initialized state, "
280+
"diffraction-calibration version should always be at least the default version (VERSION_START)."
281+
)
282+
if calVersion == VERSION_START():
283+
continueFlags |= ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION
273284

285+
# remove any continue flags that were already present in the request
274286
if request.continueFlags:
275-
continueFlags = continueFlags ^ (request.continueFlags & continueFlags)
287+
continueFlags ^= request.continueFlags & continueFlags
276288

277289
if continueFlags:
278-
raise ContinueWarning(
279-
"Only the default Diffraction Calibration data is available for this run.\n"
280-
"Normalizations may not be accurate to true state of the instrument, and will be marked as such.\n"
281-
"Continue anyway?",
282-
continueFlags,
290+
message = (
291+
"<p><b>Diffraction calibration is missing.</b></p>"
292+
"<p>Default calibration will be used in place of actual calibration.</p>"
293+
"<p>Would you like to continue anyway?</p>"
283294
)
295+
raise ContinueWarning(message, continueFlags)
284296

285297
@Register("validateWritePermissions")
286298
def validateWritePermissions(self, request: CalibrationWritePermissionsRequest):

src/snapred/backend/service/ReductionService.py

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Any, Dict, List, Optional
55

66
from snapred.backend.dao import RunMetadata
7-
from snapred.backend.dao.indexing.Versioning import VersionState
7+
from snapred.backend.dao.indexing.Versioning import VERSION_START, VersionState
88
from snapred.backend.dao.ingredients import (
99
GroceryListItem,
1010
ReductionIngredients,
@@ -129,46 +129,61 @@ def validateReduction(self, request: ReductionRequest):
129129

130130
# Check if a normalization is present
131131
normalizationExists = self.dataFactoryService.normalizationExists(request.runNumber, request.useLiteMode, state)
132+
if not normalizationExists:
133+
continueFlags |= ContinueWarning.Type.MISSING_NORMALIZATION
132134

133-
calibrationExists = (
134-
self.dataFactoryService.calibrationExists(request.runNumber, request.useLiteMode, state)
135-
or request.alternativeCalibrationFilePath is not None
136-
)
135+
# Notes:
136+
# * At this point, the state will be initialized.
137+
# * In an initialized state, `calVersion` will never be `None`.
138+
# * `MISSING_DIFFRACTION_CALIBRATION` is now the same as the former `DEFAULT_DIFFRACTION_CALIBRATION`.
139+
# * The default calibration uses `VERSION_START`.
140+
calibrationExists = True
141+
if request.alternativeCalibrationFilePath is None:
142+
calVersion = self.dataFactoryService.getLatestApplicableCalibrationVersion(
143+
request.runNumber, request.useLiteMode, state
144+
)
145+
if calVersion is None:
146+
raise RuntimeError(
147+
"Usage error: for an initialized state, "
148+
"diffraction-calibration version should always be at least the default version (VERSION_START)."
149+
)
150+
if calVersion == VERSION_START():
151+
calibrationExists = False
152+
continueFlags |= ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION
137153

138154
# Determine the action based on missing components
139155
if not calibrationExists and normalizationExists:
140156
# Case: No calibration but normalization exists
141-
continueFlags |= ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION
142157
message = (
143-
"Warning: diffraction calibration is missing.If you continue, default instrument geometry will be used."
158+
"<p><b>Diffraction calibration is missing.</b></p>"
159+
"<p>Default calibration will be used in place of actual calibration.</p>"
160+
"<p>Would you like to continue anyway?</p>"
144161
)
145162
elif calibrationExists and not normalizationExists:
146163
# Case: Calibration exists but normalization is missing
147-
continueFlags |= ContinueWarning.Type.MISSING_NORMALIZATION
148164
message = (
149-
"Warning: Reduction is missing normalization data. "
150-
"Artificial normalization will be created in place of actual normalization. "
151-
"Would you like to continue?"
165+
"<p><b>Normalization calibration is missing.</b></p>"
166+
"<p>Artificial normalization will be created in place of actual normalization.</p>"
167+
"<p>Would you like to continue anyway?</p>"
152168
)
153169
elif not calibrationExists and not normalizationExists:
154170
# Case: No calibration and no normalization
155-
continueFlags |= (
156-
ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION | ContinueWarning.Type.MISSING_NORMALIZATION
157-
)
158171
message = (
159-
"Warning: Reduction is missing both normalization and calibration data. "
160-
"If you continue, default instrument geometry will be used and data will be artificially normalized. "
172+
"<p><b>Both normalization and diffraction calibrations are missing.</b></p>"
173+
"<p>Default calibration will be used in place of actual calibration.</p>"
174+
"<p>Artificial normalization will be created in place of actual normalization.</p>"
175+
"<p>Would you like to continue anyway?</p>"
161176
)
162177

163-
# Remove any continue flags that are present in the request by XOR-ing with the flags
178+
# Remove any continue flags that are also present in the request by XOR-ing with the request flags
164179
if request.continueFlags:
165180
continueFlags ^= request.continueFlags & continueFlags
166181

167182
# If there are any continue flags set, raise a ContinueWarning with the appropriate message
168183
if continueFlags and message:
169184
raise ContinueWarning(message, continueFlags)
170185

171-
# Ensure separate continue warnings for permission check
186+
# Reinitialized continue flags for the upcoming permissions check
172187
continueFlags = ContinueWarning.Type.UNSET
173188

174189
# Check that the user has write permissions to the save directory
@@ -248,12 +263,14 @@ def _createReductionRecord(
248263
state, _ = self.dataFactoryService.constructStateId(request.runNumber)
249264

250265
if request.continueFlags is not None:
251-
if ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION not in request.continueFlags:
252-
# If a diffraction calibration exists,
253-
# its version will have been filled in by `fetchReductionGroceries`.
254-
calibration = self.dataFactoryService.getCalibrationRecord(
255-
request.runNumber, request.useLiteMode, request.versions.calibration, state
256-
)
266+
# Notes:
267+
# * If a diffraction calibration exists,
268+
# its version will have been filled in by `fetchReductionGroceries`.
269+
# * `MISSING_DIFFRACTION_CALIBRATION` now means that the default diffraction calibration
270+
# with `VERSION_START` is being applied.
271+
calibration = self.dataFactoryService.getCalibrationRecord(
272+
request.runNumber, request.useLiteMode, request.versions.calibration, state
273+
)
257274
if ContinueWarning.Type.MISSING_NORMALIZATION not in request.continueFlags:
258275
normalization = self.dataFactoryService.getNormalizationRecord(
259276
request.runNumber, request.useLiteMode, state, request.versions.normalization
@@ -355,21 +372,23 @@ def prepCombinedMask(self, request: ReductionRequest) -> WorkspaceName:
355372

356373
# if there is a mask associated with the diffcal file, load it here
357374
calVersion = request.versions.calibration
358-
if (
359-
ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION not in request.continueFlags
360-
and calVersion is VersionState.LATEST
361-
):
375+
if calVersion is VersionState.LATEST:
362376
calVersion = self.dataFactoryService.getLatestApplicableCalibrationVersion(
363377
runNumber, useLiteMode, state=state
364378
)
365379

366-
if calVersion is not None:
367-
self.groceryClerk.name("diffcalMaskWorkspace").diffcal_mask(state, calVersion, runNumber).useLiteMode(
368-
useLiteMode
380+
if calVersion is None:
381+
raise RuntimeError(
382+
"Usage error: for an initialized state, "
383+
"diffraction-calibration version should always be at least the default version (VERSION_START)."
369384
)
370-
if request.alternativeCalibrationFilePath is not None:
371-
self.groceryClerk.diffCalFilePath(request.alternativeCalibrationFilePath)
372-
self.groceryClerk.add()
385+
386+
self.groceryClerk.name("diffcalMaskWorkspace").diffcal_mask(state, calVersion, runNumber).useLiteMode(
387+
useLiteMode
388+
)
389+
if request.alternativeCalibrationFilePath is not None:
390+
self.groceryClerk.diffCalFilePath(request.alternativeCalibrationFilePath)
391+
self.groceryClerk.add()
373392

374393
# if the user specified masks to use, also pull those
375394
residentMasks = {}
@@ -476,10 +495,15 @@ def fetchReductionGroceries(self, request: ReductionRequest) -> Dict[str, Any]:
476495
# If no alternativeState state is provided, use the sample's state.
477496
state, _ = self.dataFactoryService.constructStateId(request.runNumber)
478497

479-
if ContinueWarning.Type.MISSING_DIFFRACTION_CALIBRATION not in request.continueFlags:
480-
calVersion = self.dataFactoryService.getLatestApplicableCalibrationVersion(
481-
request.runNumber, request.useLiteMode, state
498+
calVersion = self.dataFactoryService.getLatestApplicableCalibrationVersion(
499+
request.runNumber, request.useLiteMode, state
500+
)
501+
if calVersion is None:
502+
raise RuntimeError(
503+
"Usage error: for an initialized state, "
504+
"diffraction-calibration version should always be at least the default version (VERSION_START)."
482505
)
506+
483507
if ContinueWarning.Type.MISSING_NORMALIZATION not in request.continueFlags:
484508
normVersion = self.dataFactoryService.getLatestApplicableNormalizationVersion(
485509
request.runNumber, request.useLiteMode, state
@@ -551,7 +575,8 @@ def _markWorkspaceMetadata(self, request: ReductionRequest, workspace: Workspace
551575
altDiffCalFilePath = str(request.alternativeCalibrationFilePath)
552576
else:
553577
calibrationState = DiffcalStateMetadata.EXISTS
554-
# The reduction workflow will automatically create a "fake" vanadium, so it shouldnt ever be None?
578+
579+
# The reduction workflow will automatically create a "fake" vanadium, so it shouldn't ever be None?
555580
normalizationState = (
556581
NormalizationStateMetadata.FAKE
557582
if ContinueWarning.Type.MISSING_NORMALIZATION in request.continueFlags

src/snapred/meta/mantid/WorkspaceNameGenerator.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from pydantic.functional_validators import BeforeValidator
1010
from typing_extensions import Annotated, Self
1111

12-
# *** DEBUG *** : CIRCULAR IMPORT?!
1312
from snapred.backend.dao.indexing.Versioning import VERSION_START, VersionState
1413
from snapred.meta.Config import Config
1514
from snapred.meta.decorators.classproperty import classproperty
@@ -224,9 +223,6 @@ def formatVersion(cls, version: Optional[int], fmt=ValueFormat.versionFormat.WOR
224223
# handle two special cases of unassigned or default version
225224
# in those cases, format will be a user-specified string
226225

227-
# *** DEBUG *** : CIRCULAR IMPORT?!
228-
from snapred.backend.dao.indexing.Versioning import VersionState
229-
230226
formattedVersion = ""
231227
if version == VersionState.DEFAULT:
232228
formattedVersion = f"v{VERSION_START()}"

0 commit comments

Comments
 (0)