Files
eos/libeos/event_analysis.py

123 lines
5.6 KiB
Python

"""
Define an event dataformat that performs reduction actions like wavelength calculation on per-event basis.
With large number of events these actions can be time consuming so they use numba based functions.
"""
import numpy as np
import logging
from typing import Tuple
from . import const
from .event_data_types import EventDataAction, EventDatasetProtocol, append_fields, EVENT_BITMASKS
from .helpers import filter_project_x, merge_frames, extract_walltime
from .instrument import Detector
from .options import IncidentAngle
from .header import Header
class ExtractWalltime(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol) ->None:
# TODO: fix numba type definition after refactor
wallTime = extract_walltime(dataset.data.events.tof,
dataset.data.packets.start_index.astype(np.uint64),
dataset.data.packets.Time)
logging.debug(f' expending event stream by wallTime')
new_events = append_fields(dataset.data.events, [('wallTime', wallTime.dtype)])
new_events.wallTime = wallTime
dataset.data.events = new_events
class MergeFrames(EventDataAction):
def perform_action(self, dataset: EventDatasetProtocol)->None:
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)
class AnalyzePixelIDs(EventDataAction):
def __init__(self, yRange: Tuple[int, int]):
self.yRange = yRange
def perform_action(self, dataset: EventDatasetProtocol) ->None:
d = dataset.data
# TODO: change numba implementation to use native pixelID type
(detZ, detXdist, delta, mask) = filter_project_x(
Detector.pixelLookUp, d.events.pixelID.astype(np.int64), self.yRange[0], self.yRange[1]
)
ana_events = append_fields(d.events, [
('detZ', detZ.dtype), ('detXdist', detXdist.dtype), ('delta', delta.dtype)])
# add analysis per event
ana_events.detZ = detZ
ana_events.detXdist = detXdist
ana_events.delta = delta
ana_events.mask += np.logical_not(mask)*EVENT_BITMASKS['yRange']
d.events = ana_events
class CalculateWavelength(EventDataAction):
def __init__(self, lambdaRange: Tuple[float, float]):
self.lambdaRange = lambdaRange
def perform_action(self, dataset: EventDatasetProtocol) ->None:
d = dataset.data
if not 'detXdist' in dataset.data.events.dtype.names:
raise ValueError("CalculateWavelength requires dataset with analyzed pixels, perform AnalyzePixelIDs first")
#lamdaMax = const.lamdaCut+1.e13*dataset.timing.tau*const.hdm/(dataset.geometry.chopperDetectorDistance+124.)
# lambda
lamda = (1.e13*const.hdm)*d.events.tof/(dataset.geometry.chopperDetectorDistance+d.events.detXdist)
final_events = append_fields(d.events, [('lamda', np.float64)])
# add analysis per event
final_events.lamda = lamda
final_events.mask += EVENT_BITMASKS["LamdaRange"]*(
(self.lambdaRange[0]>lamda) | (lamda>self.lambdaRange[1]))
d.events = final_events
class CalculateQ(EventDataAction):
def __init__(self, incidentAngle: IncidentAngle):
self.incidentAngle = incidentAngle
def perform_action(self, dataset: EventDatasetProtocol) ->None:
d = dataset.data
if not 'lamda' in dataset.data.events.dtype.names:
raise ValueError("CalculateQ requires dataset with analyzed wavelength, perform CalculateWavelength first")
lamda = d.events.lamda
final_events = append_fields(d.events, [('qz', np.float64)])
# alpha_f
# q_z
if self.incidentAngle == IncidentAngle.alphaF:
alphaF_e = dataset.geometry.nu - dataset.geometry.mu + d.events.delta
final_events.qz = 4*np.pi*(np.sin(np.deg2rad(alphaF_e))/lamda)
elif self.incidentAngle == IncidentAngle.nu:
alphaF_e = (dataset.geometry.nu + d.events.delta + dataset.geometry.kap + dataset.geometry.kad) / 2.
final_events.qz = 4*np.pi*(np.sin(np.deg2rad(alphaF_e))/lamda)
else:
alphaF_e = dataset.geometry.nu - dataset.geometry.mu + d.events.delta
alphaI = dataset.geometry.kap + dataset.geometry.kad + dataset.geometry.mu
final_events.qz = 2*np.pi * ((np.sin(np.deg2rad(alphaF_e)) + np.sin(np.deg2rad(alphaI)))/lamda)
final_events = append_fields(final_events, [('qx', np.float64)])
final_events.qx = 2*np.pi * ((np.cos(np.deg2rad(alphaF_e)) - np.cos(np.deg2rad(alphaI)))/lamda)
dataset.data.events = final_events
def update_header(self, header: Header):
if self.incidentAngle == IncidentAngle.alphaF:
header.measurement_scheme = 'angle- and energy-dispersive'
else:
header.measurement_scheme = 'energy-dispersive'
class FilterQzRange(EventDataAction):
def __init__(self, qzRange: Tuple[float, float]):
self.qzRange = qzRange
def perform_action(self, dataset: EventDatasetProtocol) ->None:
d = dataset.data
if not 'qz' in dataset.data.events.dtype.names:
raise ValueError("FilterQzRange requires dataset with qz values per events, perform WavelengthAndQ first")
if self.qzRange[1]<0.5:
d.events.mask += EVENT_BITMASKS["qRange"]*((self.qzRange[0]>d.events.qz) | (d.events.qz>self.qzRange[1]))