Start implementing new way to build command line arguments and defaults based on options classes directly
This commit is contained in:
4
eos.py
4
eos.py
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user