J. Stahn
- added flatbuffer scheme 'da00' for the bema monitor - reading in beam monitor signal (not yet used: there are changes / bug-fixes in 'da00' planned for the near future)
This commit is contained in:
@@ -29,11 +29,17 @@ class AmorTiming:
|
||||
chopperPhase: float
|
||||
tau: float
|
||||
|
||||
@dataclass
|
||||
class DA00EventBuffer:
|
||||
signal: np.ndarray
|
||||
time: np.ndarray
|
||||
|
||||
# Structured datatypes used for event streams
|
||||
EVENT_TYPE = np.dtype([('tof', np.float64), ('pixelID', np.uint32), ('mask', np.int32)])
|
||||
PACKET_TYPE = np.dtype([('start_index', np.uint32), ('time', np.int64)])
|
||||
PULSE_TYPE = np.dtype([('time', np.int64), ('monitor', np.float32)])
|
||||
PC_TYPE = np.dtype([('current', np.float32), ('time', np.int64)])
|
||||
BM_TYPE = np.dtype([('value', np.int64), ('time', np.int64)])
|
||||
LOG_TYPE = np.dtype([('value', np.float32), ('time', np.int64)])
|
||||
|
||||
# define the bitmask for individual event filters
|
||||
@@ -63,6 +69,7 @@ class AmorEventStream:
|
||||
packets: np.recarray # PACKET_TYPE
|
||||
pulses: np.recarray # PULSE_TYPE
|
||||
proton_current: np.recarray # PC_TYPE
|
||||
beam_monitor: np.recarray # BM_TYPE
|
||||
device_logs: Dict[str, np.recarray] = field(default_factory=dict) # LOG_TYPE
|
||||
|
||||
class EventDatasetProtocol(Protocol):
|
||||
|
||||
@@ -91,6 +91,8 @@ class AssociatePulseWithMonitor(EventDataAction):
|
||||
* 2*dataset.timing.tau * 1e-3
|
||||
elif self.monitorType==MonitorType.time:
|
||||
dataset.data.pulses.monitor = 2*dataset.timing.tau
|
||||
elif self.monitorType==MonitorType.neutron_monitor:
|
||||
dataset.data.pulses.monitor = dataset.data.beam_monitor.value / 1e4
|
||||
else: # pulses
|
||||
dataset.data.pulses.monitor = 1
|
||||
|
||||
@@ -201,4 +203,4 @@ class ApplyMask(EventDataAction):
|
||||
d.device_logs[key] = np.recarray(d.pulses.shape, dtype = log.dtype)
|
||||
d.device_logs[key].time = d.pulses.time
|
||||
d.device_logs[key].value = d.pulses[key]
|
||||
dataset.update_info_from_logs()
|
||||
dataset.update_info_from_logs()
|
||||
|
||||
+39
-17
@@ -7,7 +7,6 @@ import sys
|
||||
import h5py
|
||||
import numpy as np
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@@ -17,7 +16,7 @@ from orsopy.fileio.model_language import SampleModel
|
||||
from . import const, compat
|
||||
from .header import Header
|
||||
from .event_data_types import AmorGeometry, AmorTiming, AmorEventStream, LOG_TYPE, PACKET_TYPE, EVENT_TYPE, PULSE_TYPE, \
|
||||
PC_TYPE
|
||||
PC_TYPE, BM_TYPE, DA00EventBuffer
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
@@ -33,7 +32,7 @@ NO_DEFAULT_VALUE = object() # just for allowing None default value in AmorHeader
|
||||
|
||||
class AmorHeader:
|
||||
"""
|
||||
Collects header information from Amor NeXus fiel without reading event data.
|
||||
Collects header information from Amor NeXus file without reading event data.
|
||||
"""
|
||||
# mapping of names to (hdf_path, dtype, nicos_key[, suffix])
|
||||
hdf_paths = dict(
|
||||
@@ -44,7 +43,7 @@ class AmorHeader:
|
||||
user_affiliation=('entry1/user/affiliation', str),
|
||||
sample_name=('entry1/sample/name', str),
|
||||
sample_model=('entry1/sample/model', str),
|
||||
sample_geometry=('entry1/sample/geometry', dict),
|
||||
sample_geometry=('entry1/sample/geometry', str),
|
||||
source_name=('entry1/Amor/source/name', str),
|
||||
start_time=('entry1/start_time', str),
|
||||
start_time_fallback=('entry1/Amor/measurement_configuration/start_time', str),
|
||||
@@ -52,7 +51,7 @@ class AmorHeader:
|
||||
|
||||
chopper_separation=('entry1/Amor/chopper/pair_separation', float),
|
||||
chopper_distance=('entry1/Amor/chopper/distance', float),
|
||||
detector_distance=('entry1/Amor/detector/transformation/distance', float),
|
||||
detector_distance=('entry1/Amor/detector_stage/transformation/distance', float),
|
||||
sample_temperature=('entry1/sample/environment/temperature', float),
|
||||
sample_magnetic_field=('entry1/sample/environment/magnetic_field', float),
|
||||
sample_current=('entry1/sample/environment/current', float),
|
||||
@@ -68,14 +67,14 @@ class AmorHeader:
|
||||
virtual_source_vertical=('entry1/Amor/virtual_source/vertical', float),
|
||||
ch1_trigger_phase=('entry1/Amor/chopper/ch1_trigger_phase', float, 'ch1_trigger_phase'),
|
||||
ch2_trigger_phase=('entry1/Amor/chopper/ch2_trigger_phase', float, 'ch2_trigger_phase'),
|
||||
chopper_speed=('entry1/Amor/chopper/rotation_speed', float, 'chopper_phase'),
|
||||
chopper_speed=('entry1/Amor/chopper/rotation_speed', float, 'chopper_speed'),
|
||||
chopper_phase=('entry1/Amor/chopper/phase', float, 'chopper_phase'),
|
||||
|
||||
polarization_config_label=('entry1/Amor/polarization/configuration', int, 'polarization_config_label', '/*'),
|
||||
|
||||
data=('entry1/detector/data', None), # data group used to load events from
|
||||
trigger=('entry1/detector/trigger', float),
|
||||
monitor=('entry1/detector/monitor', float),
|
||||
beam_monitor=('entry1/beam_monitor', None), # special data group (da00 flat-buffer scheme)
|
||||
proton_current=('entry1/detector/proton_current', float),
|
||||
acquisition_filter=('entry1/detector/acquisition_filter', int),
|
||||
)
|
||||
@@ -147,6 +146,9 @@ class AmorHeader:
|
||||
return output, unit
|
||||
else:
|
||||
return output
|
||||
elif hdfgrp.attrs.get('writer_module', None) == 'da00':
|
||||
# da00 float-buffer stream data
|
||||
return DA00EventBuffer(signal=hdfgrp['signal'][:], time=hdfgrp['time'][:])
|
||||
elif dtype is str:
|
||||
return self.read_string(hdf_path)
|
||||
else:
|
||||
@@ -262,7 +264,8 @@ class AmorHeader:
|
||||
)
|
||||
sample_geometry = self.rv('sample_geometry', default=None)
|
||||
if sample_geometry:
|
||||
self.sample.geometry = sample_geometry
|
||||
import yaml
|
||||
self.sample.geometry = yaml.safe_load(sample_geometry)
|
||||
# while event times are not evaluated, use first value reported in file for SEE
|
||||
for param in self.SEE_PARAMS:
|
||||
key = f'sample_{param}'
|
||||
@@ -454,14 +457,16 @@ class AmorEventData(AmorHeader):
|
||||
events.pixelID = data_group['event_id'][self.first_index:self.last_index+1]
|
||||
events.mask = 0
|
||||
|
||||
self._beam_monitor = self.rv('beam_monitor', default=None)
|
||||
pulses = self.read_chopper_trigger_stream(packets)
|
||||
current = self.read_proton_current_stream(packets)
|
||||
beam_monitor = self.read_proton_current_stream(packets)
|
||||
#beam_monitor = self.read_beam_monitor_stream(packets) TODO
|
||||
|
||||
# read parameter logs not present in old files to ensure they are in self._log_keys if they exist
|
||||
_monitor = self.rv('monitor', default=None)
|
||||
_acquisition_filter = self.rv('acquisition_filter', default=None)
|
||||
|
||||
self.data = AmorEventStream(events, packets, pulses, current)
|
||||
self.data = AmorEventStream(events, packets, pulses, current, beam_monitor)
|
||||
|
||||
if self.first_index>0 and not self.EOF:
|
||||
# label the file name if not all events were used
|
||||
@@ -488,8 +493,11 @@ class AmorEventData(AmorHeader):
|
||||
sample_keys = [f'sample_{param}' for param in self.SEE_PARAMS]
|
||||
relevant_items += sample_keys
|
||||
for key, log in self.data.device_logs.items():
|
||||
# if SE is missing there can be a missing key or empty dataset
|
||||
if key not in relevant_items:
|
||||
continue
|
||||
if log.value.size == 0:
|
||||
continue
|
||||
if log.value.dtype in [np.int8, np.int16, np.int32, np.int64]:
|
||||
# for integer items (flags) report the most common one
|
||||
value = np.bincount(log.value).argmax()
|
||||
@@ -515,10 +523,8 @@ class AmorEventData(AmorHeader):
|
||||
pulseTimeS = chopper1TriggerTime
|
||||
else:
|
||||
logging.critical(' No chopper trigger data available, using event steram instead, pulse filtering will fail!')
|
||||
data_group = self.hdf[self.hdf_paths['data'][0]]
|
||||
startTime = np.array(data_group['event_time_zero'][0], dtype=np.int64)
|
||||
stopTime = np.array(data_group['event_time_zero'][-2], dtype=np.int64)
|
||||
pulseTimeS = np.arange(startTime, stopTime, self.timing.tau*1e9, dtype=np.int64)
|
||||
startTime = np.array(self._beam_monitor.time[0], dtype=np.int64)
|
||||
pulseTimeS = self._beam_monitor.time
|
||||
pulses = np.recarray(pulseTimeS.shape, dtype=PULSE_TYPE)
|
||||
pulses.time = pulseTimeS
|
||||
pulses.monitor = 1. # default is monitor pulses as it requires no calculation
|
||||
@@ -542,6 +548,20 @@ class AmorEventData(AmorHeader):
|
||||
(proton_current.time<=packets.time[-1])]
|
||||
return proton_current
|
||||
|
||||
def read_beam_monitor_stream(self, packets):
|
||||
_signal = self._beam_monitor.signal
|
||||
_time = self._beam_monitor.time
|
||||
plse = np.array([0, 5, 6, 11])
|
||||
base = np.array([2, 3, 8, 9])
|
||||
i = 0
|
||||
beam_monitor = np.recarray(shape=np.shape(packets.time)[0], dtype=BM_TYPE)
|
||||
for tt, tme in enumerate(_time):
|
||||
if (tme>=packets.time[0])&(tme<=packets.time[-1]):
|
||||
beam_monitor.value[i] = _signal[tt][plse].sum() - _signal[tt][base].sum()
|
||||
beam_monitor.time[i] = tme
|
||||
i += 1
|
||||
return beam_monitor[:i+1] # TODO: the '+1' is strange. Why needed?
|
||||
|
||||
def info(self):
|
||||
output = ""
|
||||
for key in ['owner', 'experiment', 'sample', 'instrument_settings']:
|
||||
@@ -558,10 +578,11 @@ class AmorEventData(AmorHeader):
|
||||
new_events = np.concatenate([self.data.events, other.data.events]).view(np.recarray)
|
||||
new_pulses = np.concatenate([self.data.pulses, other.data.pulses]).view(np.recarray)
|
||||
new_proton_current = np.concatenate([self.data.proton_current, other.data.proton_current]).view(np.recarray)
|
||||
new_beam_monitor = np.concatenate([self.data.beam_monitor, other.data.beam_monitor]).view(np.recarray)
|
||||
new_packets = np.concatenate([self.data.packets, other.data.packets]).view(np.recarray)
|
||||
new_packets.start_index[self.data.packets.shape[0]:] += self.data.events.shape[0]
|
||||
self.data = AmorEventStream(new_events, new_packets, new_pulses, new_proton_current)
|
||||
# Indicate that this is amodified dataset, basically counts number of appends as negative indices
|
||||
self.data = AmorEventStream(new_events, new_packets, new_pulses, new_proton_current, new_beam_monitor)
|
||||
# Indicate that this is a modified dataset, basically counts number of appends as negative indices
|
||||
self.last_index = min(self.last_index-1, -1)
|
||||
self.file_list += other.file_list
|
||||
|
||||
@@ -589,5 +610,6 @@ class AmorEventData(AmorHeader):
|
||||
setattr(output, key, value)
|
||||
# TODO: this is not strictly correct, as the packet/event relationship is lost
|
||||
output.data = AmorEventStream(self.data.events[event_filter], self.data.packets,
|
||||
self.data.pulses[pulse_filter], self.data.proton_current)
|
||||
self.data.pulses[pulse_filter], self.data.proton_current,
|
||||
self.data.beam_monitor)
|
||||
return output
|
||||
|
||||
+2
-2
@@ -247,7 +247,7 @@ class MonitorType(StrEnum):
|
||||
debug = 'x'
|
||||
|
||||
MONITOR_UNITS = {
|
||||
MonitorType.neutron_monitor: 'cnts',
|
||||
MonitorType.neutron_monitor: 'au',
|
||||
MonitorType.proton_charge: 'mC',
|
||||
MonitorType.time: 's',
|
||||
MonitorType.auto: 'various',
|
||||
@@ -273,7 +273,7 @@ class ExperimentConfig(ArgParsable):
|
||||
},
|
||||
)
|
||||
chopperSpeed: float = field(
|
||||
default=500,
|
||||
default=450,
|
||||
metadata={
|
||||
'short': 'cs',
|
||||
'group': 'instrument settings',
|
||||
|
||||
+1
-1
@@ -829,4 +829,4 @@ class CombinedProjection(ProjectionInterface):
|
||||
pi.update_plot()
|
||||
|
||||
# def update_range(self, event):
|
||||
# ...
|
||||
# ...
|
||||
|
||||
Reference in New Issue
Block a user