amor-eos 3.3.1__tar.gz → 3.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {amor_eos-3.3.1 → amor_eos-3.3.3}/PKG-INFO +1 -1
  2. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/PKG-INFO +1 -1
  3. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/SOURCES.txt +2 -1
  4. amor_eos-3.3.3/eos/__init__.py +6 -0
  5. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/__main__.py +2 -1
  6. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/command_line.py +2 -2
  7. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/event_handling.py +1 -1
  8. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/file_reader.py +32 -4
  9. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/options.py +8 -0
  10. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/reduction_reflectivity.py +17 -3
  11. amor_eos-3.3.3/tests/test_full_analysis_v2.py +125 -0
  12. amor_eos-3.3.1/eos/__init__.py +0 -6
  13. {amor_eos-3.3.1 → amor_eos-3.3.3}/LICENSE +0 -0
  14. {amor_eos-3.3.1 → amor_eos-3.3.3}/README.rst +0 -0
  15. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/dependency_links.txt +0 -0
  16. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/entry_points.txt +0 -0
  17. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/requires.txt +0 -0
  18. {amor_eos-3.3.1 → amor_eos-3.3.3}/amor_eos.egg-info/top_level.txt +0 -0
  19. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/compat.py +0 -0
  20. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/const.py +0 -0
  21. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/e2h.py +0 -0
  22. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/event_analysis.py +0 -0
  23. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/event_data_types.py +0 -0
  24. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/header.py +0 -0
  25. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/helpers.py +0 -0
  26. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/helpers_fallback.py +0 -0
  27. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/helpers_numba.py +0 -0
  28. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/instrument.py +0 -0
  29. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/kafka_events.py +0 -0
  30. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/kafka_serializer.py +0 -0
  31. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/logconfig.py +0 -0
  32. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/ls.py +0 -0
  33. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/nicos.py +0 -0
  34. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/nicos_interface.py +0 -0
  35. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/normalization.py +0 -0
  36. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/path_handling.py +0 -0
  37. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/projection.py +0 -0
  38. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/reduction_e2h.py +0 -0
  39. {amor_eos-3.3.1 → amor_eos-3.3.3}/eos/reduction_kafka.py +0 -0
  40. {amor_eos-3.3.1 → amor_eos-3.3.3}/pyproject.toml +0 -0
  41. {amor_eos-3.3.1 → amor_eos-3.3.3}/setup.cfg +0 -0
  42. {amor_eos-3.3.1 → amor_eos-3.3.3}/tests/test_event_handling.py +0 -0
  43. {amor_eos-3.3.1 → amor_eos-3.3.3}/tests/test_full_analysis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amor_eos
3
- Version: 3.3.1
3
+ Version: 3.3.3
4
4
  Summary: EOS reflectometry reduction for AMOR instrument
5
5
  Author: Jochen Stahn - Paul Scherrer Institut
6
6
  Author-email: jochen.stahn@psi.ch
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amor_eos
3
- Version: 3.3.1
3
+ Version: 3.3.3
4
4
  Summary: EOS reflectometry reduction for AMOR instrument
5
5
  Author: Jochen Stahn - Paul Scherrer Institut
6
6
  Author-email: jochen.stahn@psi.ch
@@ -37,4 +37,5 @@ eos/reduction_e2h.py
37
37
  eos/reduction_kafka.py
38
38
  eos/reduction_reflectivity.py
39
39
  tests/test_event_handling.py
40
- tests/test_full_analysis.py
40
+ tests/test_full_analysis.py
41
+ tests/test_full_analysis_v2.py
@@ -0,0 +1,6 @@
1
+ """
2
+ Package to handle data redction at the AMOR instrument to be used by __main__.py script.
3
+ """
4
+
5
+ __version__ = '3.3.3'
6
+ __date__ = '2026-08-24'
@@ -8,6 +8,7 @@ Author: Jochen Stahn (algorithms, python draft),
8
8
  import logging
9
9
 
10
10
  # need to do absolute import here as pyinstaller requires it
11
+ from eos import __version__
11
12
  from eos.options import ReflectivityConfig, ReaderConfig, ExperimentConfig, ReflectivityReductionConfig, ReflectivityOutputConfig
12
13
  from eos.command_line import commandLineArgs
13
14
  from eos.logconfig import setup_logging, update_loglevel, setup_logfile
@@ -29,7 +30,7 @@ def main():
29
30
 
30
31
  if output_config.logFile:
31
32
  setup_logfile()
32
- logging.warning('######## eos - data reduction for Amor ########')
33
+ logging.warning(f'######## eos ({__version__}) - data reduction for Amor ########')
33
34
 
34
35
  # only import heavy module if sufficient command line parameters were provided
35
36
  from eos.reduction_reflectivity import ReflectivityReduction
@@ -2,14 +2,14 @@ import argparse
2
2
 
3
3
  from typing import List, Type
4
4
  from .options import ArgParsable
5
-
5
+ from . import __version__
6
6
 
7
7
  def commandLineArgs(config_items: List[Type[ArgParsable]], program_name=None, extra_args=[]):
8
8
  """
9
9
  Process command line argument.
10
10
  The type of the default values is used for conversion and validation.
11
11
  """
12
- msg = "eos reads data from (one or several) raw file(s) of the .hdf format, \
12
+ msg = f"eos ({__version__}) reads data from (one or several) raw file(s) of the .hdf format, \
13
13
  performs various corrections, conversations and projections and exports\
14
14
  the resulting reflectivity in an orso-compatible format."
15
15
  clas = argparse.ArgumentParser(prog=program_name,
@@ -184,7 +184,7 @@ class ApplyMask(EventDataAction):
184
184
  # only run this calculation if debug level is actually active
185
185
  filtered_by_mask = {}
186
186
  for key, value in EVENT_BITMASKS.items():
187
- filtered_by_mask[key] = ((d.events.mask & value)!=0).sum()
187
+ filtered_by_mask[key] = int(((d.events.mask & value)!=0).sum())
188
188
  logging.debug(f" Removed by filters: {filtered_by_mask}")
189
189
  if self.bitmask_filter is None:
190
190
  d.events = d.events[d.events.mask==0]
@@ -221,7 +221,8 @@ class AmorHeader:
221
221
  self._start_time_ns = np.uint64(start_timestamp*1e9)
222
222
 
223
223
  # read general information and first data set
224
- title = self.rv('title')
224
+ # make sure title has only a single line
225
+ title = self.rv('title').replace('\r', ' ').replace('\n', ' ').replace(' ', ' ')
225
226
  proposal_id = self.rv('proposal_id')
226
227
  user_name = self.rv('user_name')
227
228
  user_affiliation = self.rv('user_affiliation', default='unknown')
@@ -379,6 +380,7 @@ class AmorEventData(AmorHeader):
379
380
  data: AmorEventStream
380
381
 
381
382
  eventStartTime: np.int64
383
+ correctTimeFallback:bool = True
382
384
 
383
385
  def __init__(self, fileName:Union[str, h5py.File, BinaryIO], first_index:int=0, max_events:int=100_000_000):
384
386
  if type(fileName) is str:
@@ -520,11 +522,26 @@ class AmorEventData(AmorHeader):
520
522
  startTime = chopper1TriggerTime[0]
521
523
  pulseTimeS = chopper1TriggerTime
522
524
  elif self._beam_monitor.time.shape[0] > 1:
523
- logging.critical(' No chopper trigger data available, using first monitor time!')
525
+ logging.critical(' No chopper trigger data available, using first monitor time!')
524
526
  startTime = np.array(self._beam_monitor.time[0], dtype=np.int64)
525
527
  pulseTimeS = self._beam_monitor.time
528
+ elif self.correctTimeFallback:
529
+ logging.critical(
530
+ ' No chopper trigger data or monitor time available, using event stearm and packet times instead!')
531
+ data_group = self.hdf[self.hdf_paths['data'][0]]
532
+ startTime = np.array(data_group['event_time_zero'][0], dtype=np.int64)
533
+ stopTime = np.array(data_group['event_time_zero'][-1], dtype=np.int64)
534
+ pulseTimeS = np.arange(startTime, stopTime, self.timing.tau*1e9, dtype=np.int64)
535
+ # find times that are closest to the next packet time and correct them to fit exactly
536
+ all_times = np.unique(packets.time)
537
+ all_times.sort()
538
+ tauhalf = self.timing.tau*5e8
539
+ for ti in all_times:
540
+ idx = np.where((pulseTimeS+tauhalf)>=ti)[0][0]
541
+ # to keep approximate tau spacing, correct all times after the current packet
542
+ pulseTimeS[idx:] -= (pulseTimeS[idx]-ti)
526
543
  else:
527
- logging.critical(' No chopper trigger data available, using event steram instead, pulse filtering will fail!')
544
+ logging.critical(' No chopper trigger data or monitor time available, using event steram instead, pulse filtering will fail!')
528
545
  data_group = self.hdf[self.hdf_paths['data'][0]]
529
546
  startTime = np.array(data_group['event_time_zero'][0], dtype=np.int64)
530
547
  stopTime = np.array(data_group['event_time_zero'][-2], dtype=np.int64)
@@ -605,9 +622,20 @@ class AmorEventData(AmorHeader):
605
622
  new_beam_monitor = None
606
623
  else:
607
624
  new_beam_monitor = np.concatenate([self.data.beam_monitor, other.data.beam_monitor]).view(np.recarray)
625
+ if self.data.device_logs is None or other.data.device_logs is None:
626
+ new_device_logs = None
627
+ else:
628
+ new_device_logs = {}
629
+ for key, slog in self.data.device_logs.items():
630
+ olog = other.data.device_logs[key]
631
+ new_device_logs[key] = np.recarray(slog.shape[0]+olog.shape[0],
632
+ dtype = slog.dtype)
633
+ new_device_logs[key].time = np.concatenate([slog.time, olog.time])
634
+ new_device_logs[key].value = np.concatenate([slog.value, olog.value])
608
635
  new_packets = np.concatenate([self.data.packets, other.data.packets]).view(np.recarray)
609
636
  new_packets.start_index[self.data.packets.shape[0]:] += self.data.events.shape[0]
610
- self.data = AmorEventStream(new_events, new_packets, new_pulses, new_proton_current, new_beam_monitor)
637
+ self.data = AmorEventStream(new_events, new_packets, new_pulses, new_proton_current, new_beam_monitor,
638
+ new_device_logs)
611
639
  # Indicate that this is a modified dataset, basically counts number of appends as negative indices
612
640
  self.last_index = min(self.last_index-1, -1)
613
641
  self.file_list += other.file_list
@@ -490,6 +490,14 @@ class ReflectivityReductionConfig(ArgParsable):
490
490
  },
491
491
  )
492
492
 
493
+ correctTimeFallback: bool = field(
494
+ default=True,
495
+ metadata={
496
+ 'group': 'data manicure',
497
+ 'help': 'if falleng back to event stream for pulse time estimation, correct exact values '
498
+ 'to recover filtering options (slower)',
499
+ },
500
+ )
493
501
 
494
502
  class OutputFomatOption(StrEnum):
495
503
  Rqz_ort = "Rqz.ort"
@@ -46,7 +46,10 @@ class ReflectivityReduction:
46
46
  self.normevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType)
47
47
  if self.config.experiment.monitorType in [MonitorType.proton_charge, MonitorType.debug]:
48
48
  self.normevent_actions |= ea.ExtractWalltime()
49
- self.normevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
49
+ if self.config.experiment.lowCurrentThreshold>0:
50
+ self.normevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
51
+ else:
52
+ logging.debug(' low current threashold disabled')
50
53
  self.normevent_actions |= eh.FilterStrangeTimes()
51
54
  self.normevent_actions |= ea.MergeFrames()
52
55
  self.normevent_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange)
@@ -61,9 +64,10 @@ class ReflectivityReduction:
61
64
  self.dataevent_time_correction = eh.CorrectSeriesTime(0) # will be set from first dataset
62
65
  self.dataevent_actions |= self.dataevent_time_correction
63
66
  self.dataevent_actions |= eh.AssociatePulseWithMonitor(self.config.experiment.monitorType)
64
- if self.config.experiment.monitorType in [MonitorType.proton_charge or MonitorType.debug]:
67
+ if self.config.experiment.monitorType in [MonitorType.proton_charge, MonitorType.debug]:
65
68
  # the filtering only makes sense if using actual monitor data, not time
66
- self.dataevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
69
+ if self.config.experiment.lowCurrentThreshold>0:
70
+ self.dataevent_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold)
67
71
  self.dataevent_actions |= eh.FilterStrangeTimes()
68
72
  self.dataevent_actions |= ea.MergeFrames(extractNeutronPulses=self.config.reduction.extractNeutronPulses)
69
73
  self.dataevent_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange)
@@ -78,6 +82,9 @@ class ReflectivityReduction:
78
82
 
79
83
  self.grid = LZGrid(self.config.reduction.qResolution, self.config.reduction.qzRange)
80
84
 
85
+ # transfer option to data reader
86
+ AmorEventData.correctTimeFallback = self.config.reduction.correctTimeFallback
87
+
81
88
  def reduce(self):
82
89
  if not os.path.exists(f'{self.config.output.outputPath}'):
83
90
  logging.debug(f'Creating destination path {self.config.output.outputPath}')
@@ -231,6 +238,13 @@ class ReflectivityReduction:
231
238
  logging.warning(
232
239
  f'backgroung file {self.sFileName} not compatible with q_z scale ({len(self.sq_q)} vs. {len(result.Q)})')
233
240
 
241
+ if not self.config.reduction.is_default('scale'):
242
+ try:
243
+ scale = self.config.reduction.scale[i]
244
+ except IndexError:
245
+ scale = self.config.reduction.scale[-1]
246
+ result.scale(scale)
247
+
234
248
  orso_data = fileio.OrsoDataset(headerRqz, result.data)
235
249
  self.last_result = result
236
250
  self.datasetsRqz.append(orso_data)
@@ -0,0 +1,125 @@
1
+ import os
2
+ import cProfile
3
+ import numpy as np
4
+ from unittest import TestCase
5
+ from dataclasses import fields, MISSING
6
+ from eos import options, reduction_reflectivity, logconfig
7
+ from orsopy import fileio
8
+
9
+ logconfig.setup_logging()
10
+ logconfig.update_loglevel(1)
11
+
12
+ # TODO: add unit tests for individual parts of reduction
13
+
14
+ class FullAmorTest(TestCase):
15
+ @classmethod
16
+ def setUpClass(cls):
17
+ # generate map for option defaults
18
+ cls._field_defaults = {}
19
+ for opt in [options.ExperimentConfig, options.ReflectivityReductionConfig, options.ReflectivityOutputConfig]:
20
+ defaults = {}
21
+ for field in fields(opt):
22
+ if field.default not in [None, MISSING]:
23
+ defaults[field.name] = field.default
24
+ elif field.default_factory not in [None, MISSING]:
25
+ defaults[field.name] = field.default_factory()
26
+ cls._field_defaults[opt.__name__] = defaults
27
+ cls.pr = cProfile.Profile()
28
+
29
+ @classmethod
30
+ def tearDownClass(cls):
31
+ cls.pr.dump_stats("profile_test.prof")
32
+
33
+ def setUp(self):
34
+ self.pr.enable()
35
+ self.reader_config = options.ReaderConfig(
36
+ year=2026,
37
+ rawPath=["test_data"],
38
+ )
39
+
40
+ def tearDown(self):
41
+ self.pr.disable()
42
+ for fi in ['test_results/test.Rqz.ort', 'test_results/5952.norm']:
43
+ try:
44
+ os.unlink(fi)
45
+ except FileNotFoundError:
46
+ pass
47
+
48
+ def test_time_slicing(self):
49
+ experiment_config = options.ExperimentConfig(
50
+ yRange=(18, 48),
51
+ lambdaRange=(3., 11.5),
52
+ sampleModel='air | 10 H2O | D2O'
53
+ )
54
+ reduction_config = options.ReflectivityReductionConfig(
55
+ qResolution=0.01,
56
+ qzRange=(0.01, 0.15),
57
+ thetaRange=(-0.75, 0.75),
58
+ fileIdentifier=["3629", "3630"],
59
+ scale=[1],
60
+ normalisationFileIdentifier=[],
61
+ timeSlize=[300.0]
62
+ )
63
+ output_config = options.ReflectivityOutputConfig(
64
+ outputFormats=[options.OutputFomatOption.Rqz_ort],
65
+ outputName='test',
66
+ outputPath='test_results',
67
+ )
68
+ config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
69
+ # run three times to get similar timing to noslicing runs
70
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
71
+ reducer.reduce()
72
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
73
+ reducer.reduce()
74
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
75
+ reducer.reduce()
76
+
77
+ def test_noslicing(self):
78
+ experiment_config = options.ExperimentConfig(
79
+ yRange=(18, 48),
80
+ lambdaRange=(3., 11.5),
81
+ )
82
+ reduction_config = options.ReflectivityReductionConfig(
83
+ qResolution=0.01,
84
+ thetaRange=(-0.75, 0.75),
85
+ fileIdentifier=["3629", "3630"],
86
+ scale=[1],
87
+ normalisationFileIdentifier=["3631"],
88
+ autoscale=(0.0, 0.05),
89
+ )
90
+ output_config = options.ReflectivityOutputConfig(
91
+ outputFormats=[options.OutputFomatOption.Rqz_ort],
92
+ outputName='test',
93
+ outputPath='test_results',
94
+ )
95
+ config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
96
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
97
+ reducer.reduce()
98
+ # run second time to reuse norm file
99
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
100
+ reducer.reduce()
101
+
102
+ def test_noslicing_nmon(self):
103
+ experiment_config = options.ExperimentConfig(
104
+ yRange=(18, 48),
105
+ lambdaRange=(3., 11.5),
106
+ )
107
+ reduction_config = options.ReflectivityReductionConfig(
108
+ qResolution=0.01,
109
+ thetaRange=(-0.75, 0.75),
110
+ fileIdentifier=["3629", "3630"],
111
+ scale=[1],
112
+ normalisationFileIdentifier=["3631"],
113
+ autoscale=(0.0, 0.05),
114
+ )
115
+ output_config = options.ReflectivityOutputConfig(
116
+ outputFormats=[options.OutputFomatOption.Rqz_ort],
117
+ outputName='test',
118
+ outputPath='test_results',
119
+ )
120
+ config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
121
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
122
+ reducer.reduce()
123
+ # run second time to reuse norm file
124
+ reducer = reduction_reflectivity.ReflectivityReduction(config)
125
+ reducer.reduce()
@@ -1,6 +0,0 @@
1
- """
2
- Package to handle data redction at AMOR instrument to be used by __main__.py script.
3
- """
4
-
5
- __version__ = '3.3.1'
6
- __date__ = '2026-07-09'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes