separate AssociatePulseWithMonitor and FilterMonitorThreshold to allow monitor use without wallTime

This commit is contained in:
2025-10-07 10:29:02 +02:00
parent 6c0c2fcab8
commit aacbe3ed6f
6 changed files with 48 additions and 41 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ def main():
logging.warning('######## eos - data reduction for Amor ########')
# only import heavy module if sufficient command line parameters were provided
from eos.reduction import ReflectivityReduction
from eos.reduction_reflectivity import ReflectivityReduction
# Create reducer with these arguments
reducer = ReflectivityReduction(config)
# Perform actual reduction
+2 -2
View File
@@ -1,10 +1,10 @@
import argparse
from typing import List
from typing import List, Type
from .options import ArgParsable
def commandLineArgs(config_items: List[ArgParsable], program_name=None):
def commandLineArgs(config_items: List[Type[ArgParsable]], program_name=None):
"""
Process command line argument.
The type of the default values is used for conversion and validation.
+23 -21
View File
@@ -77,16 +77,12 @@ class CorrectSeriesTime(EventDataAction):
class AssociatePulseWithMonitor(EventDataAction):
def __init__(self, monitorType:MonitorType, lowCurrentThreshold:float):
def __init__(self, monitorType:MonitorType):
self.monitorType = monitorType
self.lowCurrentThreshold = lowCurrentThreshold
def perform_action(self, dataset: EventDatasetProtocol)->None:
logging.debug(f' using monitor type {self.monitorType}')
if self.monitorType in [MonitorType.proton_charge or MonitorType.debug]:
if not 'wallTime' in dataset.data.events.dtype.names:
raise ValueError(
"AssociatePulseWithMonitor requires walltTime to be extracted, please run ExtractWalltime first")
monitorPerPulse = self.get_current_per_pulse(dataset.data.pulses.time,
dataset.data.proton_current.time,
dataset.data.proton_current.current)\
@@ -101,25 +97,12 @@ class AssociatePulseWithMonitor(EventDataAction):
dataset.data.pulses.monitor = 1
if self.monitorType == MonitorType.debug:
if not 'wallTime' in dataset.data.events.dtype.names:
raise ValueError(
"AssociatePulseWithMonitor requires walltTime for debugging, please run ExtractWalltime first")
cpp, t_bins = np.histogram(dataset.data.events.wallTime, dataset.data.pulses.time)
np.savetxt('tme.hst', np.vstack((dataset.data.pulses.time[:-1], cpp, dataset.data.pulses.monitor[:-1])).T)
if self.monitorType in [MonitorType.proton_charge or MonitorType.debug]:
self.monitor_threshold(dataset)
def monitor_threshold(self, dataset):
goodTimeS = dataset.data.pulses.time[dataset.data.pulses.monitor!=0]
filter_e = np.logical_not(np.isin(dataset.data.events.wallTime, goodTimeS))
dataset.data.events.mask += EVENT_BITMASKS['MonitorThreshold']*filter_e
logging.info(f' low-beam (<{self.lowCurrentThreshold} mC) rejected pulses: '
f'{dataset.data.pulses.monitor.shape[0]-goodTimeS.shape[0]} '
f'out of {dataset.data.pulses.monitor.shape[0]}')
logging.info(f' with {filter_e.sum()} events')
if goodTimeS.shape[0]:
logging.info(f' average counts per pulse = {dataset.data.events.shape[0]/goodTimeS.shape[0]:7.1f}')
else:
logging.info(f' average counts per pulse = undefined')
@staticmethod
def get_current_per_pulse(pulseTimeS, currentTimeS, currents):
# add currents for early pulses and current time value after last pulse (j+1)
@@ -134,6 +117,25 @@ class AssociatePulseWithMonitor(EventDataAction):
pulseCurrentS[i] = currents[j]
return pulseCurrentS
class FilterMonitorThreshold(EventDataAction):
def __init__(self, lowCurrentThreshold:float):
self.lowCurrentThreshold = lowCurrentThreshold
def perform_action(self, dataset: EventDatasetProtocol) ->None:
if not 'wallTime' in dataset.data.events.dtype.names:
raise ValueError(
"FilterMonitorThreshold requires walltTime to be extracted, please run ExtractWalltime first")
goodTimeS = dataset.data.pulses.time[dataset.data.pulses.monitor!=0]
filter_e = np.logical_not(np.isin(dataset.data.events.wallTime, goodTimeS))
dataset.data.events.mask += EVENT_BITMASKS['MonitorThreshold']*filter_e
logging.info(f' low-beam (<{self.lowCurrentThreshold} mC) rejected pulses: '
f'{dataset.data.pulses.monitor.shape[0]-goodTimeS.shape[0]} '
f'out of {dataset.data.pulses.monitor.shape[0]}')
logging.info(f' with {filter_e.sum()} events')
if goodTimeS.shape[0]:
logging.info(f' average counts per pulse = {dataset.data.events.shape[0]/goodTimeS.shape[0]:7.1f}')
else:
logging.info(f' average counts per pulse = undefined')
class FilterStrangeTimes(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol)->None:
+12 -1
View File
@@ -201,6 +201,14 @@ class MonitorType(StrEnum):
neutron_monitor = 'n'
debug = 'x'
MONITOR_UNITS = {
MonitorType.neutron_monitor: 'cnts',
MonitorType.proton_charge: 'mC',
MonitorType.time: 's',
MonitorType.auto: 'various',
MonitorType.debug: 'mC',
}
@dataclass
class ExperimentConfig(ArgParsable):
chopperPhase: float = field(
@@ -581,4 +589,7 @@ class EOSConfig:
logging.debug(f'Argument list build in EOSConfig.call_string: {mlst}')
return mlst
@dataclass
class E2HConfig:
reader: ReaderConfig
@@ -8,21 +8,13 @@ from orsopy import fileio
from .file_reader import AmorEventData
from .header import Header
from .path_handling import PathResolver
from .options import EOSConfig, IncidentAngle, MonitorType, NormalisationMethod
from .instrument import Detector, LZGrid
from .options import EOSConfig, IncidentAngle, MonitorType, NormalisationMethod, MONITOR_UNITS
from .instrument import LZGrid
from .normalization import LZNormalisation
from . import event_handling as eh, event_analysis as ea
from .projection import LZProjection
MONITOR_UNITS = {
MonitorType.neutron_monitor: 'cnts',
MonitorType.proton_charge: 'mC',
MonitorType.time: 's',
MonitorType.auto: 'various',
MonitorType.debug: 'mC',
}
class ReflectivityReduction:
config: EOSConfig
header: Header
@@ -39,7 +31,7 @@ class ReflectivityReduction:
def prepare_actions(self):
"""
Does not do any actual reduction.
Prepare the actions applied to each event dataset, does not do any actual reduction.
"""
self.path_resolver = PathResolver(self.config.reader.year, self.config.reader.rawPath)
@@ -49,10 +41,10 @@ class ReflectivityReduction:
# explicit steps performed on AmorEventDataset for normalization files
self.normevent_actions = eh.ApplyPhaseOffset(self.config.experiment.chopperPhaseOffset)
self.normevent_actions |= eh.CorrectChopperPhase()
self.normevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType)
if self.config.experiment.monitorType in [MonitorType.proton_charge, MonitorType.debug]:
self.normevent_actions |= ea.ExtractWalltime()
self.normevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType,
self.config.experiment.lowCurrentThreshold)
self.dataevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
self.normevent_actions |= eh.FilterStrangeTimes()
self.normevent_actions |= ea.MergeFrames()
self.normevent_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange)
@@ -66,8 +58,10 @@ class ReflectivityReduction:
self.dataevent_actions |= ea.ExtractWalltime()
self.dataevent_time_correction = eh.CorrectSeriesTime(0) # will be set from first dataset
self.dataevent_actions |= self.dataevent_time_correction
self.dataevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType,
self.config.experiment.lowCurrentThreshold)
self.dataevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType)
if self.config.experiment.monitorType in [MonitorType.proton_charge or MonitorType.debug]:
# the filtering only makes sense if using actual monitor data, not time
self.dataevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
self.dataevent_actions |= eh.FilterStrangeTimes()
self.dataevent_actions |= ea.MergeFrames()
self.dataevent_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange)
+1 -1
View File
@@ -2,7 +2,7 @@ import os
import cProfile
from unittest import TestCase
from dataclasses import fields, MISSING
from eos import options, reduction, logconfig
from eos import options, reduction_reflectivity, logconfig
logconfig.setup_logging()
logconfig.update_loglevel(1)