Start implementing new way to build command line arguments and defaults based on options classes directly

This commit is contained in:
2025-08-27 17:19:40 +02:00
parent b6e3cb2eef
commit 7f01f89f2b
7 changed files with 321 additions and 211 deletions

4
eos.py
View File

@@ -20,7 +20,6 @@ import logging
from libeos.command_line import command_line_options
from libeos.logconfig import setup_logging
from libeos.reduction import AmorReduction
#=====================================================================================================
# TODO:
@@ -35,6 +34,9 @@ def main():
# read command line arguments and generate classes holding configuration parameters
config = command_line_options()
# only import heavy module if sufficient command line parameters were provieded
from libeos.reduction import AmorReduction
# Create reducer with these arguments
reducer = AmorReduction(config)
# Perform actual reduction

View File

@@ -1,7 +1,7 @@
import argparse
from .logconfig import update_loglevel
from .options import ReaderConfig, EOSConfig, ExperimentConfig, OutputConfig, ReductionConfig, Defaults
from .options import ReaderConfig, EOSConfig, ExperimentConfig, OutputConfig, ReductionConfig
def commandLineArgs():
@@ -12,135 +12,126 @@ def commandLineArgs():
msg = "eos reads data from (one or several) raw file(s) of the .hdf format, \
performs various corrections, conversations and projections and exports\
the resulting reflectivity in an orso-compatible format."
clas = argparse.ArgumentParser(description = msg)
clas = argparse.ArgumentParser(description = msg, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
input_data = clas.add_argument_group('input data')
input_data.add_argument("-f", "--fileIdentifier",
required = True,
nargs = '+',
help = "file number(s) or offset (if < 1)")
input_data.add_argument("-n", "--normalisationFileIdentifier",
default = Defaults.normalisationFileIdentifier,
nargs = '+',
help = "file number(s) of normalisation measurement")
input_data.add_argument("-rp", "--rawPath",
type = str,
default = Defaults.rawPath,
help = "ath to directory with .hdf files")
input_data.add_argument("-Y", "--year",
default = Defaults.year,
type = int,
help = "year the measurement was performed")
input_data.add_argument("-sub", "--subtract",
help = "R(q_z) curve to be subtracted (in .Rqz.ort format)")
input_data.add_argument("-nm", "--normalisationMethod",
default = Defaults.normalisationMethod,
help = "normalisation method: [o]verillumination, [u]nderillumination, [d]irect_beam")
input_data.add_argument("-mt", "--monitorType",
type = str,
default = Defaults.monitorType,
help = "one of [p]rotonCurrent, [t]ime or [n]eutronMonitor")
clas.add_argument('-v', '--verbose', action='count', default=0)
output = clas.add_argument_group('output')
output.add_argument("-o", "--outputName",
default = Defaults.outputName,
help = "output file name (withot suffix)")
output.add_argument("-op", "--outputPath",
type = str,
default = Defaults.outputPath,
help = "path for output")
output.add_argument("-of", "--outputFormat",
nargs = '+',
default = Defaults.outputFormat,
help = "one of [Rqz.ort, Rlt.ort]")
output.add_argument("-ai", "--incidentAngle",
type = str,
default = Defaults.incidentAngle,
help = "calulate alpha_i from [alphaF, mu, nu]",
)
output.add_argument("-r", "--qResolution",
default = Defaults.qResolution,
type = float,
help = "q_z resolution")
output.add_argument("-ts", "--timeSlize",
nargs = '+',
type = float,
help = "time slizing <interval> ,[<start> [,stop]]")
output.add_argument("-s", "--scale",
nargs = '+',
default = Defaults.scale,
type = float,
help = "scaling factor for R(q_z)")
output.add_argument("-S", "--autoscale",
nargs = 2,
type = float,
help = "scale to 1 in the given q_z range")
clas_groups = {}
masks = clas.add_argument_group('masks')
masks.add_argument("-l", "--lambdaRange",
default = Defaults.lambdaRange,
nargs = 2,
type = float,
help = "wavelength range")
masks.add_argument("-t", "--thetaRange",
default = Defaults.thetaRange,
nargs = 2,
type = float,
help = "absolute theta range")
masks.add_argument("-T", "--thetaRangeR",
default = Defaults.thetaRangeR,
nargs = 2,
type = float,
help = "relative theta range")
masks.add_argument("-y", "--yRange",
default = Defaults.yRange,
nargs = 2,
type = int,
help = "detector y range")
masks.add_argument("-q", "--qzRange",
default = Defaults.qzRange,
nargs = 2,
type = float,
help = "q_z range")
masks.add_argument("-ct", "--lowCurrentThreshold",
default = Defaults.lowCurrentThreshold,
type = float,
help = "proton current threshold for discarding neutron pulses")
all_arguments = []
for cls in [ReaderConfig, ExperimentConfig, OutputConfig, ReductionConfig]:
all_arguments += cls.get_commandline_parameters()
all_arguments.sort() # parameters are sorted alphabetically, unless they have higher priority
for cpc in all_arguments:
if not cpc.group in clas_groups:
clas_groups[cpc.group] = clas.add_argument_group(cpc.group)
if cpc.short_form:
clas_groups[cpc.group].add_argument(
f'-{cpc.short_form}', f'--{cpc.argument}', **cpc.add_argument_args
)
else:
clas_groups[cpc.group].add_argument(
f'--{cpc.argument}', **cpc.add_argument_args
)
overwrite = clas.add_argument_group('overwrite')
overwrite.add_argument("-cs", "--chopperSpeed",
default = Defaults.chopperSpeed,
type = float,
help = "chopper speed in rpm")
overwrite.add_argument("-cp", "--chopperPhase",
default = Defaults.chopperPhase,
type = float,
help = "chopper phase")
overwrite.add_argument("-co", "--chopperPhaseOffset",
default = Defaults.chopperPhaseOffset,
type = float,
help = "phase offset between chopper opening and trigger pulse")
overwrite.add_argument("-m", "--muOffset",
default = Defaults.muOffset,
type = float,
help = "mu offset")
overwrite.add_argument("-mu", "--mu",
default = Defaults.mu,
type = float,
help ="value of mu")
overwrite.add_argument("-nu", "--nu",
default = Defaults.nu,
type = float,
help = "value of nu")
overwrite.add_argument("-sm", "--sampleModel",
default = Defaults.sampleModel,
type = str,
help = "1-line orso sample model description")
misc = clas.add_argument_group('misc')
misc.add_argument('-v', '--verbose', action='store_true')
misc.add_argument('-vv', '--debug', action='store_true')
#
# output = clas.add_argument_group('output')
# output.add_argument("-o", "--outputName",
# default = Defaults.outputName,
# help = "output file name (withot suffix)")
# output.add_argument("-op", "--outputPath",
# type = str,
# default = Defaults.outputPath,
# help = "path for output")
# output.add_argument("-of", "--outputFormat",
# nargs = '+',
# default = Defaults.outputFormat,
# help = "one of [Rqz.ort, Rlt.ort]")
# output.add_argument("-ai", "--incidentAngle",
# type = str,
# default = Defaults.incidentAngle,
# help = "calulate alpha_i from [alphaF, mu, nu]",
# )
# output.add_argument("-r", "--qResolution",
# default = Defaults.qResolution,
# type = float,
# help = "q_z resolution")
# output.add_argument("-ts", "--timeSlize",
# nargs = '+',
# type = float,
# help = "time slizing <interval> ,[<start> [,stop]]")
# output.add_argument("-s", "--scale",
# nargs = '+',
# default = Defaults.scale,
# type = float,
# help = "scaling factor for R(q_z)")
# output.add_argument("-S", "--autoscale",
# nargs = 2,
# type = float,
# help = "scale to 1 in the given q_z range")
#
# masks = clas.add_argument_group('masks')
# masks.add_argument("-l", "--lambdaRange",
# default = Defaults.lambdaRange,
# nargs = 2,
# type = float,
# help = "wavelength range")
# masks.add_argument("-t", "--thetaRange",
# default = Defaults.thetaRange,
# nargs = 2,
# type = float,
# help = "absolute theta range")
# masks.add_argument("-T", "--thetaRangeR",
# default = Defaults.thetaRangeR,
# nargs = 2,
# type = float,
# help = "relative theta range")
# masks.add_argument("-y", "--yRange",
# default = Defaults.yRange,
# nargs = 2,
# type = int,
# help = "detector y range")
# masks.add_argument("-q", "--qzRange",
# default = Defaults.qzRange,
# nargs = 2,
# type = float,
# help = "q_z range")
# masks.add_argument("-ct", "--lowCurrentThreshold",
# default = Defaults.lowCurrentThreshold,
# type = float,
# help = "proton current threshold for discarding neutron pulses")
#
#
# overwrite = clas.add_argument_group('overwrite')
# overwrite.add_argument("-cs", "--chopperSpeed",
# default = Defaults.chopperSpeed,
# type = float,
# help = "chopper speed in rpm")
# overwrite.add_argument("-cp", "--chopperPhase",
# default = Defaults.chopperPhase,
# type = float,
# help = "chopper phase")
# overwrite.add_argument("-co", "--chopperPhaseOffset",
# default = Defaults.chopperPhaseOffset,
# type = float,
# help = "phase offset between chopper opening and trigger pulse")
# overwrite.add_argument("-m", "--muOffset",
# default = Defaults.muOffset,
# type = float,
# help = "mu offset")
# overwrite.add_argument("-mu", "--mu",
# default = Defaults.mu,
# type = float,
# help ="value of mu")
# overwrite.add_argument("-nu", "--nu",
# default = Defaults.nu,
# type = float,
# help = "value of nu")
# overwrite.add_argument("-sm", "--sampleModel",
# default = Defaults.sampleModel,
# type = str,
# help = "1-line orso sample model description")
return clas.parse_args()
@@ -177,44 +168,11 @@ def output_format_list(outputFormat):
def command_line_options():
clas = commandLineArgs()
update_loglevel(clas.verbose, clas.debug)
update_loglevel(clas.verbose)
reader_config = ReaderConfig(
year = clas.year,
rawPath = clas.rawPath,
)
experiment_config = ExperimentConfig(
sampleModel = clas.sampleModel,
chopperSpeed = clas.chopperSpeed,
chopperPhase = clas.chopperPhase,
chopperPhaseOffset = clas.chopperPhaseOffset,
yRange = clas.yRange,
lambdaRange = clas.lambdaRange,
qzRange = clas.qzRange,
lowCurrentThreshold = clas.lowCurrentThreshold,
incidentAngle = clas.incidentAngle,
mu = clas.mu,
nu = clas.nu,
muOffset = clas.muOffset,
monitorType = clas.monitorType,
)
reduction_config = ReductionConfig(
qResolution = clas.qResolution,
qzRange = clas.qzRange,
autoscale = clas.autoscale,
thetaRange = clas.thetaRange,
thetaRangeR = clas.thetaRangeR,
fileIdentifier = clas.fileIdentifier,
scale = clas.scale,
subtract = clas.subtract,
normalisationFileIdentifier = clas.normalisationFileIdentifier,
normalisationMethod = clas.normalisationMethod,
timeSlize = clas.timeSlize,
)
output_config = OutputConfig(
outputFormats = output_format_list(clas.outputFormat),
outputName = clas.outputName,
outputPath = clas.outputPath,
)
reader_config = ReaderConfig.from_args(clas)
experiment_config = ExperimentConfig.from_args(clas)
reduction_config = ReductionConfig.from_args(clas)
output_config = OutputConfig.from_args(clas)
return EOSConfig(reader_config, experiment_config, reduction_config, output_config)

View File

@@ -18,7 +18,7 @@ from orsopy.fileio.model_language import SampleModel
from . import const
from .header import Header
from .instrument import Detector
from .options import ExperimentConfig, ReaderConfig
from .options import ExperimentConfig, IncidentAngle, MonitorType, ReaderConfig
try:
from . import nb_helpers
@@ -162,7 +162,7 @@ class AmorData:
'deg'),
wavelength = fileio.ValueRange(const.lamdaCut, self.config.lambdaRange[1], 'angstrom'),
#polarization = fileio.Polarization.unpolarized,
polarization = self.polarizationConfig
polarization = fileio.Polarization(self.polarizationConfig)
)
self.header.measurement_instrument_settings.mu = fileio.Value(round(self.mu, 3), 'deg', comment='sample angle to horizon')
self.header.measurement_instrument_settings.nu = fileio.Value(round(self.nu, 3), 'deg', comment='detector angle to horizon')
@@ -189,7 +189,7 @@ class AmorData:
self.associate_pulse_with_monitor()
# following lines: debugging output to trace the time-offset of proton current and neutron pulses
if self.config.monitorType == 'x':
if self.config.monitorType == MonitorType.debug:
cpp, t_bins = np.histogram(self.wallTime_e, self.pulseTimeS)
np.savetxt('tme.hst', np.vstack((self.pulseTimeS[:-1], cpp, self.monitorPerPulse[:-1])).T)
@@ -255,21 +255,21 @@ class AmorData:
def read_proton_current_stream(self):
self.currentTime = np.array(self.hdf['entry1/Amor/detector/proton_current/time'][:], dtype=np.int64)
self.current = np.array(self.hdf['entry1/Amor/detector/proton_current/value'][:,0], dtype=float)
if self.config.monitorType == "auto":
if self.config.monitorType == MonitorType.auto:
if self.current.sum() > 1:
self.monitorType = 'p'
self.monitorType = MonitorType.proton_charge
logging.warn(' monitor type set to "proton current"')
else:
self.monitorType = 't'
self.monitorType = MonitorType.time
logging.warn(' monitor type set to "time"')
def associate_pulse_with_monitor(self):
if self.config.monitorType == 'p': # protonCharge
if self.config.monitorType == MonitorType.proton_charge:
self.currentTime -= np.int64(self.seriesStartTime)
self.monitorPerPulse = self.get_current_per_pulse(self.pulseTimeS, self.currentTime, self.current) * 2*self.tau * 1e-3
# filter low-current pulses
self.monitorPerPulse = np.where(self.monitorPerPulse > 2*self.tau * self.config.lowCurrentThreshold * 1e-3, self.monitorPerPulse, 0)
elif self.config.monitorType == 't': # countingTime
elif self.config.monitorType == MonitorType.time:
self.monitorPerPulse = np.ones(np.shape(self.pulseTimeS)[0])*2*self.tau
else: # pulses
self.monitorPerPulse = np.ones(np.shape(self.pulseTimeS)[0])
@@ -288,13 +288,13 @@ class AmorData:
return pulseCurrentS
def average_events_per_pulse(self):
if self.config.monitorType == 'p':
if self.config.monitorType == MonitorType.proton_charge:
for i, time in enumerate(self.pulseTimeS):
events = np.shape(self.wallTime_e[self.wallTime_e == time])[0]
logging.info(f'pulse: {i:6.0f}, events: {events:6.0f}, monitor: {self.monitorPerPulse[i]:6.2f}')
def monitor_threshold(self):
#if self.config.monitorType == 'p': # fix to check for file compatibility
#if self.config.monitorType == MonitorType.proton_charge: # fix to check for file compatibility
self.totalNumber = np.shape(self.tof_e[self.tof_e<=self.stopTime])[0]
if True:
goodTimeS = self.pulseTimeS[self.monitorPerPulse!=0]
@@ -337,7 +337,7 @@ class AmorData:
def correct_for_chopper_opening(self):
# correct tof for beam size effect at chopper: t_cor = (delta / 180 deg) * tau
if self.config.incidentAngle == 'alphaF':
if self.config.incidentAngle == IncidentAngle.alphaF:
self.tof_e -= ( self.delta_e / 180. ) * self.tau
else:
# TODO: check sign of correction
@@ -359,12 +359,12 @@ class AmorData:
self.lamda_e<=self.config.lambdaRange[1]))
# alpha_f
# q_z
if self.config.incidentAngle == 'alphaF':
if self.config.incidentAngle == IncidentAngle.alphaF:
alphaF_e = self.nu - self.mu + self.delta_e
self.qz_e = 4*np.pi*(np.sin(np.deg2rad(alphaF_e))/self.lamda_e)
# qx_e = 0.
self.header.measurement_scheme = 'angle- and energy-dispersive'
elif self.config.incidentAngle == 'nu':
elif self.config.incidentAngle == IncidentAngle.nu:
alphaF_e = (self.nu + self.delta_e + self.kap + self.kad) / 2.
self.qz_e = 4*np.pi*(np.sin(np.deg2rad(alphaF_e))/self.lamda_e)
# qx_e = 0.

View File

@@ -33,10 +33,10 @@ def setup_logging():
logfile.setLevel(logging.DEBUG)
logger.addHandler(logfile)
def update_loglevel(verbose=False, debug=False):
if verbose:
def update_loglevel(verbose=0):
if verbose==1:
logging.getLogger().handlers[0].setLevel(logging.INFO)
if debug:
if verbose>1:
console = logging.getLogger().handlers[0]
console.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(levelname).1s %(message)s')

View File

@@ -1,8 +1,10 @@
"""
Classes for stroing various configurations needed for reduction.
"""
from dataclasses import dataclass, field
from typing import Optional, Tuple
import argparse
from dataclasses import dataclass, field, Field, fields, MISSING
from enum import StrEnum
from typing import get_args, get_origin, List, Optional, Tuple, Union
from datetime import datetime
from os import path
import numpy as np
@@ -39,52 +41,196 @@ class Defaults:
sampleModel = None
lowCurrentThreshold = 50
#
@dataclass
class ReaderConfig:
year: int
rawPath: Tuple[str]
startTime: Optional[float] = 0
class CommandlineParameterConfig:
argument: str # default parameter for command line resutign ins "--argument"
add_argument_args: dict # all arguments that will be passed to add_argument method
short_form: Optional[str] = None
group: str = 'misc'
priority: int = 0
def __gt__(self, other):
"""
Sort required arguments first, then use priority, then name
"""
return (not self.add_argument_args.get('required', False), -self.priority, self.argument)>(
not other.add_argument_args.get('required', False), -other.priority, other.argument)
class ArgParsable:
def __init_subclass__(cls):
# create a nice documentation string that takes help into account
cls.__doc__ = cls.__name__ + " Parameters:\n"
for key, typ in cls.__annotations__.items():
if get_origin(typ) is Union and type(None) in get_args(typ):
optional = True
typ = get_args(typ)[0]
else:
optional = False
value = getattr(cls, key, None)
cls.__doc__ += f" {key} ({typ.__name__})"
if isinstance(value, Field):
if value.default is not MISSING:
cls.__doc__ += f" = {value.default}"
if 'help' in value.metadata:
cls.__doc__ += f" - {value.metadata['help']}"
elif value is not None:
cls.__doc__ += f" = {value}"
if optional:
cls.__doc__ += " [Optional]"
cls.__doc__ += "\n"
return cls
@classmethod
def get_commandline_parameters(cls) -> List[CommandlineParameterConfig]:
"""
Return a list of arguments used in building the command line parameters.
Union types besides Optional are not supported.
"""
output = []
for field in fields(cls):
args={}
if field.default is not MISSING:
args['default'] = field.default
args['required'] = False
elif field.default_factory is not MISSING:
args['default'] = field.default_factory()
args['required'] = False
else:
args['required'] = True
if get_origin(field.type) is Union and type(None) in get_args(field.type):
# optional argument
typ = get_args(field.type)[0]
del(args['default'])
else:
typ = field.type
if get_origin(typ) is list:
args['nargs'] = '+'
typ = get_args(typ)[0]
elif get_origin(typ) is tuple:
args['nargs'] = len(get_args(typ))
typ = get_args(typ)[0]
if issubclass(typ, StrEnum):
args['choices'] = [ci.value for ci in typ]
if field.default is not MISSING:
args['default'] = field.default.value
typ = str
if typ is bool:
args['action'] = 'store_false' if field.default else 'store_true'
else:
args['type'] = typ
if 'help' in field.metadata:
args['help'] = field.metadata['help']
output.append(CommandlineParameterConfig(
field.name,
add_argument_args=args,
group=field.metadata.get('group', 'misc'),
short_form=field.metadata.get('short', None),
priority=field.metadata.get('priority', 0),
))
return output
@classmethod
def from_args(cls, args: argparse.Namespace):
"""
Create the child class from the command line argument Namespace object.
All attributes that are not needed for this class are ignored.
"""
inpargs = {}
for field in fields(cls):
value = getattr(args, field.name)
typ = field.type
if get_origin(field.type) is Union and type(None) in get_args(field.type):
# optional argument
typ = get_args(field.type)[0]
if issubclass(typ, StrEnum):
# convert str to enum
try:
value = typ(value)
except ValueError:
choices = [ci.value for ci in typ]
raise ValueError(f"Parameter --{field.name} has to be one of {choices}")
inpargs[field.name] = value
return cls(**inpargs)
@dataclass
class ExperimentConfig:
incidentAngle: str
class ReaderConfig(ArgParsable):
year: int = field(default=datetime.now().year,
metadata={'short': 'Y', 'group': 'input data', 'help': 'year the measurement was performed'})
rawPath: List[str] = field(default_factory=lambda: ['.', path.join('.','raw'), path.join('..','raw'), path.join('..','..','raw')],
metadata={
'short': 'rp',
'group': 'input data',
'help': 'Search paths for hdf files'})
startTime: Optional[float] = None
class IncidentAngle(StrEnum):
alphaF = 'alphaF'
mu = 'mu'
nu = 'nu'
class MonitorType(StrEnum):
auto = 'a'
proton_charge = 'p'
time = 't'
neutron_monitor = 'n'
debug = 'x'
@dataclass
class ExperimentConfig(ArgParsable):
chopperPhase: float
chopperSpeed: float
yRange: Tuple[float, float]
lambdaRange: Tuple[float, float]
qzRange: Tuple[float, float]
monitorType: str
lowCurrentThreshold: float
incidentAngle: IncidentAngle = IncidentAngle.alphaF
sampleModel: Optional[str] = None
chopperPhaseOffset: float = 0
mu: Optional[float] = None
nu: Optional[float] = None
muOffset: Optional[float] = None
monitorType: MonitorType = field(default=MonitorType.auto, metadata={'short': 'mt',
'group': 'input data', 'help': 'one of [a]uto, [p]rotonCurrent, [t]ime or [n]eutronMonitor'})
class NormalisationMethod(StrEnum):
direct_beam = 'd'
over_illuminated = 'o'
under_illuminated = 'u'
@dataclass
class ReductionConfig:
normalisationMethod: str
class ReductionConfig(ArgParsable):
qResolution: float
qzRange: Tuple[float, float]
thetaRange: Tuple[float, float]
#thetaRangeR: Tuple[float, float]
thetaRangeR: list
thetaRangeR: List[float]
fileIdentifier: List[str] = field(metadata={'short': 'f', 'priority': 100,
'group': 'input data', 'help': 'file number(s) or offset (if < 1)'})
fileIdentifier: list = field(default_factory=lambda: ["0"])
scale: list = field(default_factory=lambda: [1]) #per file scaling; if less elements than files use the last one
normalisationMethod: NormalisationMethod = field(default=NormalisationMethod.over_illuminated,
metadata={'short': 'nm', 'priority': 90, 'group': 'input data',
'help': 'normalisation method: [o]verillumination, [u]nderillumination, [d]irect_beam'})
scale: List[float] = field(default_factory=lambda: [1.]) #per file scaling; if less elements than files use the last one
autoscale: Optional[Tuple[bool, bool]] = None
subtract: Optional[str] = None
normalisationFileIdentifier: Optional[list] = None
timeSlize: Optional[list] = None
autoscale: bool = False # TODO: This made no sense, it is used as single bool.
subtract: Optional[str] = field(default=None, metadata={'short': 'sub',
'group': 'input data', 'help': 'File with R(q_z) curve to be subtracted (in .Rqz.ort format)'})
normalisationFileIdentifier: Optional[List[str]] = field(default=None, metadata={'short': 'n', 'priority': 90,
'group': 'input data', 'help': 'file number(s) of normalisation measurement'})
timeSlize: Optional[List[float]] = None
@dataclass
class OutputConfig:
outputFormats: list
class OutputConfig(ArgParsable):
outputFormats: List[str]
outputName: str
outputPath: str

View File

@@ -8,7 +8,7 @@ from orsopy import fileio
from .command_line import expand_file_list
from .file_reader import AmorData
from .header import Header
from .options import EOSConfig
from .options import EOSConfig, IncidentAngle, MonitorType, NormalisationMethod
from .instrument import Grid
class AmorReduction:
@@ -22,7 +22,10 @@ class AmorReduction:
self.header = Header()
self.header.reduction.call = config.call_string()
self.monitorUnit = {'n': 'cnts', 'p': 'mC', 't': 's', 'auto': 'pulses'}
self.monitorUnit = {MonitorType.neutron_monitor: 'cnts',
MonitorType.proton_charge: 'mC',
MonitorType.time: 's',
MonitorType.auto: 'various'}
def reduce(self):
if not os.path.exists(f'{self.output_config.outputPath}'):
@@ -353,8 +356,7 @@ class AmorReduction:
self.normMonitor = np.sum(fromHDF.monitorPerPulse)
norm_lz, bins_l, bins_z = np.histogram2d(lamda_e, detZ_e, bins = (self.grid.lamda(), self.grid.z()))
norm_lz = np.where(norm_lz>2, norm_lz, np.nan)
if self.reduction_config.normalisationMethod == 'd':
# direct reference => invert map vertically
if self.reduction_config.normalisationMethod == NormalisationMethod.direct_beam:
self.norm_lz = np.flip(norm_lz, 1)
else:
# correct for reference sm reflectivity
@@ -426,7 +428,7 @@ class AmorReduction:
#alphaF_lz += np.rad2deg( np.arctan( 3.07e-10 * (fromHDF.detectorDistance + detXdist_e) * lamda_lz**2 ) )
alphaF_lz += np.rad2deg( np.arctan( 3.07e-10 * fromHDF.detectorDistance * lamda_lz**2 ) )
if self.experiment_config.incidentAngle == 'alphaF':
if self.experiment_config.incidentAngle == IncidentAngle.alphaF:
#alphaI_lz = alphaF_lz
qz_lz = 4.0*np.pi * np.sin(np.deg2rad(alphaF_lz)) / lamda_lz
qx_lz = self.grid.lz() * 0.
@@ -440,17 +442,17 @@ class AmorReduction:
int_lz = np.where(mask_lz, int_lz, np.nan)
thetaF_lz = np.where(mask_lz, alphaF_lz, np.nan)
if self.reduction_config.normalisationMethod == 'o':
if self.reduction_config.normalisationMethod == NormalisationMethod.over_illuminated:
logging.debug(' assuming an overilluminated sample and correcting for the angle of incidence')
thetaN_z = fromHDF.delta_z + normAngle
thetaN_lz = np.ones(np.shape(norm_lz))*thetaN_z
thetaN_lz = np.where(np.absolute(thetaN_lz)>5e-3, thetaN_lz, np.nan)
mask_lz = np.logical_and(mask_lz, np.where(np.absolute(thetaN_lz)>5e-3, True, False))
ref_lz = (int_lz * np.absolute(thetaN_lz)) / (norm_lz * np.absolute(thetaF_lz))
elif self.reduction_config.normalisationMethod == 'u':
elif self.reduction_config.normalisationMethod == NormalisationMethod.under_illuminated:
logging.debug(' assuming an underilluminated sample and ignoring the angle of incidence')
ref_lz = (int_lz / norm_lz)
elif self.reduction_config.normalisationMethod == 'd':
elif self.reduction_config.normalisationMethod == NormalisationMethod.direct_beam:
logging.debug(' assuming direct beam for normalisation and ignoring the angle of incidence')
ref_lz = (int_lz / norm_lz)
else:

View File

@@ -35,6 +35,7 @@ class FullAmorTest(TestCase):
def test_time_slicing(self):
experiment_config = options.ExperimentConfig(
chopperSpeed=options.Defaults.chopperSpeed,
chopperPhase=-13.5,
chopperPhaseOffset=-5,
monitorType=options.Defaults.monitorType,
@@ -75,6 +76,7 @@ class FullAmorTest(TestCase):
def test_noslicing(self):
experiment_config = options.ExperimentConfig(
chopperSpeed=options.Defaults.chopperSpeed,
chopperPhase=-13.5,
chopperPhaseOffset=-5,
monitorType=options.Defaults.monitorType,