Skip to content

Commit ce02432

Browse files
authored
Merge pull request #100 from neutrons/timeFilter
Time filter
2 parents 4dd7c85 + cefefe4 commit ce02432

File tree

4 files changed

+248
-2
lines changed

4 files changed

+248
-2
lines changed

src/snapwrap/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.1.0.dev32"
1+
__version__ = "2.1.0.dev34"

src/snapwrap/configDefinitions/__init__.py

Whitespace-only changes.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
nexus:
2+
lite:
3+
prefix: shared/lite/filtered/SNAP_
4+
extension: .lite.nxs.h5
5+
native:
6+
prefix: nexus/SNAP_
7+
extension: .nxs.h5
8+
file:
9+
prefix: SNAP_
10+
extension: .nxs.h5
11+
dataFormat:
12+
# Assume that input data will be in event format:
13+
event: true

src/snapwrap/utils.py

Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import importlib
1111
import copy
1212
import time
13+
import importlib.resources as resources
1314

1415
from .wrapConfig import WrapConfig
1516
import snapwrap.snapStateMgr as ssm
@@ -93,6 +94,238 @@ def deploy():
9394
for key in deployInfo["vcs_info"]:
9495
print(f"{key}:{deployInfo['vcs_info'][key]}")
9596

97+
def getConfigPath(name: str):
98+
99+
# returns the full path to a SNAPRed config file stored in the
100+
# snapwrap package configDefinitions folder
101+
102+
return str(resources.files("snapwrap.configDefinitions") / f"{name}.yml")
103+
104+
def reloadRedConfig(path=None):
105+
106+
# allows reloading of specific overrides of SNAPRed application.yml parameters
107+
# by specifying the path to an override file
108+
# if path is not specified then it will reload the original yml according to the
109+
# current environment
110+
111+
if path is None:
112+
Config.reload() # reloads original config according to environment
113+
print("Original SNAPRed config reloaded")
114+
else:
115+
# confirm file exists at path
116+
if not os.path.isfile(path):
117+
print(f"Error: specified SNAPRed config override file does not exist at {path}")
118+
return
119+
else:
120+
Config.reload(path) # reloads specified override file
121+
print(f"SNAPRed config override applied")
122+
return
123+
124+
def filterLite(runNumber, boundaries, **reduce_kwargs):
125+
126+
# accepts a single run number and instructions for filtering
127+
# and then reduces the data accordingly
128+
129+
# first validate inputs
130+
if not isinstance(runNumber, int):
131+
print("Error: runNumber must be an integer")
132+
return
133+
if not isinstance(boundaries, dict):
134+
print("Error: boundaries must be a dictionary")
135+
return
136+
137+
# process boundaries
138+
if boundaries["type"] == "time":
139+
# require list of times in seconds as floats.
140+
if boundaries["units"] == "seconds":
141+
secondBoundaries = [float(x) for x in boundaries["values"]]
142+
elif boundaries["units"] == "minutes":
143+
secondBoundaries = [float(x)*60.0 for x in boundaries["values"]]
144+
elif boundaries["units"] == "hours":
145+
secondBoundaries = [float(x)*3600.0 for x in boundaries["values"]]
146+
else:
147+
print(f"Error: currently only time units of seconds, minutes, and hours are supported. You requested: {boundaries['units']}")
148+
return
149+
# TODO: check if boundaries are within run time of run runNumber
150+
151+
else:
152+
print("Error: currently only time boundaries are supported")
153+
return
154+
155+
# obtain original nexus file path
156+
iptsPath = GetIPTS(RunNumber=runNumber, Instrument="SNAP")
157+
nexusPath = f"{iptsPath}/nexus/SNAP_{runNumber}.nxs.h5"
158+
159+
# override SNAPRed parameters lite location params
160+
s = getConfigPath("nexusDefinitionFilterOverride")
161+
reloadRedConfig(s)
162+
163+
# specify lite params
164+
liteDir=f"{iptsPath}/{Config['nexus']['lite']['prefix'][0:-5]}"
165+
liteFilename=f"SNAP_{runNumber}.lite.nxs.h5"
166+
liteYml = f"SNAP_{runNumber}.lite.yml"
167+
litePars = {"TOFTol" : -0.0001, #default for no TOF compression
168+
"clockTol": None,
169+
"liteGroupMapFile":Config['instrument']['lite']['map']['file'],
170+
"liteIDF":Config['instrument']['lite']['definition']['file'],
171+
"liteDir":liteDir,
172+
"liteFilename":liteFilename,
173+
"liteYAML":liteYml,
174+
"saveLite":True
175+
}
176+
for key in litePars.keys():
177+
print(f"{key}: {litePars[key]}")
178+
179+
#loop through boundaries and reduce
180+
outputWSNames = []
181+
for sliceID in range(len(secondBoundaries)-1):
182+
183+
startTime = secondBoundaries[sliceID]
184+
stopTime = secondBoundaries[sliceID+1]
185+
186+
displayStartTime = boundaries["values"][sliceID]
187+
displayStopTime = boundaries["values"][sliceID+1]
188+
displayUnits = boundaries["units"]
189+
190+
litePars["filterStartTime"]= startTime
191+
litePars["filterStopTime"]= stopTime
192+
193+
# load time filtered raw data
194+
LoadEventNexus(Filename=nexusPath,
195+
OutputWorkspace="tmp",
196+
LoadMonitors=False,
197+
FilterByTimeStart=startTime,
198+
FilterByTimeStop=stopTime)
199+
200+
# make lite version and save to disk in special filtered folder
201+
makeLite(inWS="tmp",outWS="tmpLite",litePars=litePars,overwrite=True)
202+
203+
# reduce this filtered lite data.
204+
print(f"Reducing run {runNumber} from {startTime}s to {stopTime}s")
205+
206+
wsNames = reduce(runNumber=runNumber, **reduce_kwargs)
207+
208+
# rename output workspace to indicate sequence
209+
210+
print(f"Renaming operation...for slice {sliceID}")
211+
for name in wsNames:
212+
print("Original name: ",name)
213+
newName = f"{name[:-17]}slice_{str(sliceID).zfill(3)}"
214+
print("New name: ",newName)
215+
RenameWorkspace(InputWorkspace=name, OutputWorkspace=newName)
216+
outputWSNames.append(newName)
217+
218+
# update label for plotting
219+
220+
from mantid.api import TextAxis
221+
222+
ws = mtd[newName]
223+
N = ws.getNumberHistograms()
224+
labels = [f"bank {i+1}: {displayStartTime:.1f} to {displayStopTime:.1f} {displayUnits}" for i in range(N)]
225+
taxis = TextAxis.create(N)
226+
for i,lab in enumerate(labels):
227+
taxis.setLabel(i, lab)
228+
229+
ws.replaceAxis(1, taxis)
230+
231+
DeleteWorkspace(Workspace="tmp")
232+
DeleteWorkspace(Workspace="tmpLite")
233+
234+
# group output names into workspace group
235+
236+
#First group alphabetically
237+
238+
def sortKey(name):
239+
parts = name.split("_")
240+
string_id = parts[2] # "all", "bank", "column"
241+
slice_num = int(parts[-1]) # "000" -> 0
242+
return (string_id, slice_num)
243+
244+
sortedOutputWSNames = sorted(outputWSNames, key=sortKey)
245+
246+
GroupWorkspaces(InputWorkspaces=sortedOutputWSNames, OutputWorkspace=f"slice_{runNumber}")
247+
248+
#Reset config to original
249+
reloadRedConfig()
250+
251+
print(f"Config reset. lite data directory reset to: {Config['nexus']['lite']['prefix'][0:-5]}")
252+
return
253+
254+
def makeLite(inWS,outWS,litePars,overwrite=False):
255+
256+
# utility to convert an input native workspace to a lite workspace
257+
# requires litePars, a dictionary providing necessary parameters
258+
259+
#check if lite directory exists and create if it doesn't
260+
261+
mut.LoadH5GroupingDefinition(inWS,
262+
litePars["liteGroupMapFile"],
263+
"liteGroup")
264+
265+
logger.notice("makelite: Grouping Pixels")
266+
267+
GroupDetectors(InputWorkspace=inWS,
268+
OutputWorkspace=outWS,
269+
CopyGroupingFromWorkspace="liteGroup")
270+
271+
DeleteWorkspace(Workspace="liteGroup")
272+
273+
logger.notice("makelite: relabelling pixel IDs")
274+
nHst = mtd[outWS].getNumberHistograms()
275+
for i in range(nHst):
276+
el = mtd[outWS].getSpectrum(i)
277+
el.clearDetectorIDs()
278+
el.addDetectorID(i)
279+
mtd[outWS].setComment(mtd[outWS].getComment() + "\nLite")
280+
281+
logger.notice("makelite: loading instrument")
282+
283+
LoadInstrument(Workspace=outWS,
284+
Filename=litePars["liteIDF"],
285+
RewriteSpectraMap=False)
286+
287+
logger.notice("makelite: compressingEvents")
288+
#modified to allow compression tolerances to be switched
289+
290+
if litePars["TOFTol"] is None:
291+
tolVal = 1e-5
292+
else:
293+
tolVal = litePars["TOFTol"]
294+
295+
if litePars["clockTol"] is None:
296+
297+
CompressEvents(InputWorkspace=outWS,
298+
OutputWorkspace=outWS,
299+
Tolerance=tolVal)
300+
# sortFirst=False) #not in stable mantid release yet.
301+
else:
302+
CompressEvents(InputWorkspace=outWS,
303+
OutputWorkspace=outWS,
304+
Tolerance=tolVal,
305+
WallClockTolerance=litePars["clockTol"])
306+
307+
if litePars["saveLite"]:
308+
#check that lite directory exists
309+
if not os.path.exists(litePars['liteDir']):
310+
try:
311+
os.mkdir(litePars['liteDir'])
312+
except:
313+
logger.error(f"makelite: unable to create lite directory: {litePars['liteDir']}")
314+
print(f"Perhaps you don't have write permissions?")
315+
316+
litePath = f"{litePars['liteDir']}{litePars['liteFilename']}"
317+
318+
SaveNexusProcessed(InputWorkspace=outWS,
319+
Filename=litePath,
320+
Title="autoLite")
321+
322+
logger.notice(f"makelite: Lite file {litePath} written to disk")
323+
324+
with open(f"{litePars['liteDir']}{litePars['liteYAML']}", 'w') as file:
325+
yaml.dump(litePars,file)
326+
327+
logger.notice(f"makelite: compression parameters written to: {litePars['liteDir']}{litePars['liteYAML']}")
328+
96329
def purgeNormalisation(isLite=True,purge=False):
97330
#this removes all existing normalization folders. User with caution!!!!!!!!!!!
98331
allAvailableStates = ssm.availableStates()
@@ -1843,7 +2076,7 @@ def reduce(runNumber,
18432076

18442077
citation()
18452078
config.setLogLevel(3, quiet=True)
1846-
return ingredients.pixelGroups
2079+
return data.record.workspaceNames #ingredients.pixelGroups
18472080

18482081

18492082

0 commit comments

Comments
 (0)