Extract some actions from file reader to event actions as they depend on series time or parameter overwrites

This commit is contained in:
2025-10-02 12:23:23 +02:00
parent ae6aa9d24d
commit 5ecdecfe24
3 changed files with 85 additions and 29 deletions

View File

@@ -2,12 +2,65 @@
Calculations performed on AmorEventData.
"""
import logging
import os
import numpy as np
from . import const
from .options import MonitorType
from .header import Header
from .options import ExperimentConfig, MonitorType
from .event_data_types import EventDatasetProtocol, EventDataAction
from .helpers import merge_frames
from .helpers import merge_frames, extract_walltime
class ApplyParameterOverwrites(EventDataAction):
def __init__(self, config: ExperimentConfig):
self.config=config
def perform_action(self, dataset: EventDatasetProtocol) ->None:
if self.config.muOffset:
logging.debug(f' set muOffset = {self.config.muOffset}')
dataset.geometry.mu += self.config.muOffset
if self.config.mu:
logging.debug(f' replaced mu = {dataset.geometry.mu} with {self.config.mu}')
dataset.geometry.mu = self.config.mu
if self.config.nu:
logging.debug(f' replaced nu = {dataset.geometry.nu} with {self.config.nu}')
dataset.geometry.nu = self.config.nu
if self.config.chopperPhaseOffset:
logging.debug(
f' replaced ch1TriggerPhase = {dataset.timing.ch1TriggerPhase} '
f'with {self.config.chopperPhaseOffset}')
dataset.timing.ch1TriggerPhase = self.config.chopperPhaseOffset
logging.info(f' mu = {dataset.geometry.mu:6.3f}, '
f'nu = {dataset.geometry.nu:6.3f}, '
f'kap = {dataset.geometry.kap:6.3f}, '
f'kad = {dataset.geometry.kad:6.3f}')
def update_header(self, header:Header) ->None:
if self.config.sampleModel:
if 'yml' in self.config.sampleModel or 'yaml' in self.config.sampleModel:
import yaml
if os.path.isfile(self.config.sampleModel):
with open(self.config.sampleModel, 'r') as model_yml:
model = yaml.safe_load(model_yml)
else:
logging.warning(f' ! the file {self.config.sampleModel}.yml does not exist. Ignored!')
else:
model = dict(stack=self.config.sampleModel)
logging.debug(f' set sample.model = {self.config.sampleModel}')
header.sample.model = model
class CorrectChopperPhase(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol) ->None:
dataset.data.events.tof += dataset.timing.tau*(dataset.timing.ch1TriggerPhase-dataset.timing.chopperPhase/2)/180
class ExtractWalltime(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol) ->None:
# TODO: fix numba type definition after refactor
dataset.data.events.wallTime = extract_walltime(dataset.data.events.tof,
dataset.data.packets.start_index.astype(np.uint64),
dataset.data.packets.Time)
class CorrectSeriesTime(EventDataAction):
@@ -18,10 +71,11 @@ class CorrectSeriesTime(EventDataAction):
dataset.data.pulses.time -= self.seriesStartTime
dataset.data.events.wallTime -= self.seriesStartTime
dataset.data.proton_current.time -= self.seriesStartTime
start, stop = dataset.data.proton_current.time[0], dataset.data.proton_current.time[-1]
logging.debug(f' wall time from {start:6.1f} s to {stop/1e9:6.1f} s, '
start, stop = dataset.data.events.wallTime[0], dataset.data.events.wallTime[-1]
logging.debug(f' wall time from {start/1e9:6.1f} s to {stop/1e9:6.1f} s, '
f'series time = {self.seriesStartTime/1e9:6.1f}')
class AssociatePulseWithMonitor(EventDataAction):
def __init__(self, monitorType:MonitorType, lowCurrentThreshold:float):
self.monitorType = monitorType
@@ -78,6 +132,7 @@ class AssociatePulseWithMonitor(EventDataAction):
return pulseCurrentS
class FilterStrangeTimes(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol)->None:
filter_e = (dataset.data.events.tof<=2*dataset.timing.tau)
@@ -85,9 +140,10 @@ class FilterStrangeTimes(EventDataAction):
if not filter_e.all():
logging.warning(f' strange times: {np.logical_not(filter_e).sum()}')
class MergeFrames(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol)->None:
tofCut = const.lamdaCut+dataset.geometry.chopperDetectorDistance/const.hdm*1e-13
tofCut = const.lamdaCut*dataset.geometry.chopperDetectorDistance/const.hdm*1e-13
total_offset = (tofCut +
dataset.timing.tau * (dataset.timing.ch1TriggerPhase + dataset.timing.chopperPhase/2)/180)
dataset.data.events.tof = merge_frames(dataset.data.events.tof, tofCut, dataset.timing.tau, total_offset)

View File

@@ -18,9 +18,8 @@ from orsopy.fileio.model_language import SampleModel
from . import const, event_handling as eh, event_analysis as ea
from .header import Header
from .helpers import extract_walltime
from .event_data_types import AmorGeometry, AmorTiming, AmorEventStream, PACKET_TYPE, EVENT_TYPE, PULSE_TYPE, PC_TYPE
from .options import ExperimentConfig, IncidentAngle, ReaderConfig
from .options import ExperimentConfig, IncidentAngle, ReaderConfig, MonitorType
try:
import zoneinfo
@@ -101,7 +100,7 @@ class AmorEventData:
eventStartTime: np.int64
def __init__(self, fileName:Union[str, h5py.File, BinaryIO], first_index:int=0, max_events:int=1_000_000):
def __init__(self, fileName:Union[str, h5py.File, BinaryIO], first_index:int=0, max_events:int=100_000_000):
if type(fileName) is str:
self.fileName = fileName
self.hdf = h5py.File(fileName, 'r', swmr=True)
@@ -119,9 +118,7 @@ class AmorEventData:
self.read_event_stream()
# actions applied to any dataset
self.correct_for_chopper_phases()
self.read_chopper_trigger_stream()
self.extract_walltime()
self.read_proton_current_stream()
if type(fileName) is str:
@@ -220,7 +217,7 @@ class AmorEventData:
chopperSeparation, detectorDistance, chopperDetectorDistance)
self.timing = AmorTiming(ch1TriggerPhase, ch2TriggerPhase, chopperSpeed, chopperPhase, tau)
polarizationConfigLabel = self._replace_if_missing('polarization/configuration/value', 'polarizer_config_label', int)
polarizationConfigLabel = self._replace_if_missing('polarization/configuration/average_value', 'polarizer_config_label', int)
polarizationConfig = fileio.Polarization(polarizationConfigs[polarizationConfigLabel])
logging.debug(f' polarization configuration: {polarizationConfig} (index {polarizationConfigLabel})')
@@ -260,6 +257,7 @@ class AmorEventData:
"""
Add dataset information into an existing header.
"""
logging.info(f' meta data from: {self.fileName}')
header.owner = self.owner
header.experiment = self.experiment
header.sample = self.sample
@@ -298,12 +296,8 @@ class AmorEventData:
events.pixelID = self.hdf['/entry1/Amor/detector/data/event_id'][self.first_index:self.last_index+1]
self.data = AmorEventStream(events, packets)
def correct_for_chopper_phases(self):
self.data.events.tof += self.timing.tau*(self.timing.ch1TriggerPhase-self.timing.chopperPhase/2)/180
def read_chopper_trigger_stream(self):
chopper1TriggerTime = np.array(self.hdf['entry1/Amor/chopper/ch2_trigger/event_time_zero'][:-2],
dtype=np.int64)
chopper1TriggerTime = np.array(self.hdf['entry1/Amor/chopper/ch2_trigger/event_time_zero'][:-2], dtype=np.int64)
#self.chopper2TriggerTime = self.chopper1TriggerTime + np.array(self.hdf['entry1/Amor/chopper/ch2_trigger/event_time'][:-2], dtype=np.int64)
# + np.array(self.hdf['entry1/Amor/chopper/ch2_trigger/event_time_offset'][:], dtype=np.int64)
if np.shape(chopper1TriggerTime)[0] > 2:
@@ -322,12 +316,6 @@ class AmorEventData:
self.data.pulses = pulses
self.eventStartTime = startTime
def extract_walltime(self):
# TODO: fix numba type definition after refactor
self.data.events.wallTime = extract_walltime(self.data.events.tof,
self.data.packets.start_index.astype(np.uint64),
self.data.packets.Time)
def read_proton_current_stream(self):
proton_current = np.recarray(self.hdf['entry1/Amor/detector/proton_current/time'].shape, dtype=PC_TYPE)
proton_current.time = self.hdf['entry1/Amor/detector/proton_current/time'][:]
@@ -410,7 +398,12 @@ class AmorData:
def prepare_actions(self):
# setup all actions performed in origin AmorData, time correction requires first dataset start time
self.event_actions = eh.AssociatePulseWithMonitor(self.config.monitorType, self.config.lowCurrentThreshold)
self.time_correction = eh.CorrectSeriesTime(0)
self.event_actions = eh.ApplyParameterOverwrites(self.config) # some actions use instrument parameters, change before that
self.event_actions |= eh.CorrectChopperPhase()
self.event_actions |= eh.ExtractWalltime()
self.event_actions |= self.time_correction
self.event_actions |= eh.AssociatePulseWithMonitor(self.config.monitorType, self.config.lowCurrentThreshold)
self.event_actions |= eh.FilterStrangeTimes()
self.event_actions |= eh.MergeFrames()
self.event_actions |= ea.AnalyzePixelIDs(self.config.yRange)
@@ -421,16 +414,23 @@ class AmorData:
def process(self):
self.dataset = AmorEventData(self.file_list[0])
time_correction = eh.CorrectSeriesTime(self.dataset.eventStartTime)
time_correction(self.dataset)
if self.config.monitorType==MonitorType.auto:
if self.dataset.data.proton_current.current.sum()>1:
self.monitorType = MonitorType.proton_charge
logging.warning(' monitor type set to "proton current"')
else:
self.monitorType = MonitorType.time
logging.warning(' monitor type set to "time"')
# update actions to sue selected monitor
self.prepare_actions()
self.time_correction.seriesStartTime = self.dataset.eventStartTime
self.event_actions(self.dataset)
if not self.norm:
self.dataset.update_header(self.header)
time_correction.update_header(self.header)
self.event_actions.update_header(self.header)
for fi in self.file_list[1:]:
di = AmorEventData(fi)
time_correction(di)
self.event_actions(di)
self.dataset.append(di)

View File

@@ -50,7 +50,7 @@ class FullAmorTest(TestCase):
chopperPhaseOffset=-5,
monitorType=self._field_defaults['ExperimentConfig']['monitorType'],
lowCurrentThreshold=self._field_defaults['ExperimentConfig']['lowCurrentThreshold'],
yRange=(11., 41.),
yRange=(11, 41),
lambdaRange=(2., 15.),
incidentAngle=self._field_defaults['ExperimentConfig']['incidentAngle'],
mu=0,
@@ -90,7 +90,7 @@ class FullAmorTest(TestCase):
chopperPhaseOffset=-5,
monitorType=self._field_defaults['ExperimentConfig']['monitorType'],
lowCurrentThreshold=self._field_defaults['ExperimentConfig']['lowCurrentThreshold'],
yRange=(11., 41.),
yRange=(11, 41),
lambdaRange=(2., 15.),
incidentAngle=self._field_defaults['ExperimentConfig']['incidentAngle'],
mu=0,