diff --git a/eos/__main__.py b/eos/__main__.py index f8a1635..a30a451 100644 --- a/eos/__main__.py +++ b/eos/__main__.py @@ -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 diff --git a/eos/command_line.py b/eos/command_line.py index 12ad312..6c95123 100644 --- a/eos/command_line.py +++ b/eos/command_line.py @@ -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. diff --git a/eos/event_handling.py b/eos/event_handling.py index 0ad806a..1fa3120 100644 --- a/eos/event_handling.py +++ b/eos/event_handling.py @@ -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: diff --git a/eos/options.py b/eos/options.py index 5ad5657..5d781b0 100644 --- a/eos/options.py +++ b/eos/options.py @@ -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 + diff --git a/eos/reduction.py b/eos/reduction_reflectivity.py similarity index 96% rename from eos/reduction.py rename to eos/reduction_reflectivity.py index 7dfd74f..cb173cb 100644 --- a/eos/reduction.py +++ b/eos/reduction_reflectivity.py @@ -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) diff --git a/tests/test_full_analysis.py b/tests/test_full_analysis.py index ad28cfe..d20a314 100644 --- a/tests/test_full_analysis.py +++ b/tests/test_full_analysis.py @@ -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)