# -*- coding: utf-8 -*-
"""
.. Authors
Novimir Pablant <npablant@pppl.gov>
James Kring <jdk0026@tigermail.auburn.edu>
Yevgeniy Yakusevich <eugenethree@gmail.com>
"""
import numpy as np
import logging
import os
import copy
from xicsrt.objects._ConfigObject import ConfigObject
from xicsrt.tools import xicsrt_misc
from xicsrt import _version
log = logging.getLogger(__name__)
try:
import xicsrt_contrib
except:
log.debug('The xicrsrt_contrib package is not installed.')
else:
log.debug('The xicrsrt_contrib package is installed.')
[docs]
def default_config():
"""
number_of_iter : int (1)
Number of raytracing iterations to perform for each raytracing run.
Iterations are performed in a single process and with a single
initialization of the raytracing objects and the random seed.
All iterations will be combined before saving images or output files.
number_of_runs : int (1)
Number of raytracing runs to perform. For each run, the specified
number of iterations will be performed. Raytracing runs can be
performed in separate processes enabling the use of multiprocessing.
At the start of each run all raytracing objects will be created and
initialized. Images and output files will be saved after each run.
random_seed : int (None)
A random seed to initialize the pseudo-random number generator. If
random_seed is equal to None then a random seed will be provided by
the python/numpy internals. When a integer seed is provided and
multiple raytracing runs are performed, the random seed will be
incremented by one for each successive run. This seed can be used to
make raytracing runs reproducible on the level of individual rays;
however, reproducibility is not guaranteed between different versions
of XICSRT or different versions of Python. Random seed initialization
performed using `np.random.seed()`.
pathlist : list (list())
A list of paths that contain user defined raytracing modules (sources,
optics, filters, apertures, etc.). These paths will be searched for
filenames that match the requested 'class_name' in the object config.
User defined paths are searched before the builtin or contrib paths.
pathlist_default : list
A list of paths to the builtin and contributed raytracing objects.
This option should not be changed by the user, but can be useful
for inspection to see what directories are actually being searched.
strict_config_check : bool (True)
Use strict checking to ensure that the config dictionary only contains
valid options for the given object. This helps avoid unexpected
behavior as well and alerting to typos in the configuration. When set
to `False`, unrecognized config options will be quietly ignored.
output_path : str (None)
Path in which to save images and results from the raytracing run. If
note then the current working path will be used. Use the option
`make_directories` to control directory creation.
output_prefix : str ("xicsrt")
Filenames for images and results are automatically generated. Use
this option to add a prefix to the beginning of all filenames. An
underscore will be automatically added after the prefix. For images
the following format will be used: "prefix_optic_suffix_run.tif".
For example: "xicsrt_detector_scan01_0000.tif".
output_suffix : str (None)
If present this string will be added to automatically generated
filenames after the optic name and before the run_suffix. See the
option `output_prefix` for more information.
output_run_suffix : str (None)
This option is used internally and should not be set by the user.
image_ext : str ('.tif')
Controls the file format of the saved images. Any format supported by
the `pillow` package can be used; .tif images are recommended.
results_ext : str ('.hdf5')
Controls the file format for saving the results dictionary. Currently
hdf5 (.hdf5, .h5), pickle (.pickle, .pkl) and json (.json) file
formats are supported. The json format is not recommended as it may
lead to very large file sizes!
make_directories : bool (False)
Controls whether the output path should be created if it does not
already exist. If False an error will be raised if the output path
does not exist.
keep_meta : bool (True)
Controls whether to calculate and keep metadata and statistics
relating to the raytracing.
keep_images : bool (True)
Controls whether to generate and keep pixelated images of the ray
intersections at each optic. Control of image generation for
individual optics can also be set within the object specific
config sections.
keep_history : bool (True)
Controls whether to calculate and keep the raytracing history. Rays
will be sorted into 'lost' and 'found' rays, where found rays are
those that reach the final optic element. The 'found' ray history
will be kept in full, the 'lost' ray history will be truncated (see
option `history_max_lost`).
The ray history provides a great deal of information about the
raytracing and enables ray plotting (2d and 3d) and post processing.
However, turning on the ray history also *greatly* increases memory
usage since the rays must be duplicated and saved for every optical
element. If only final intersection images are required, consider
setting this option to `False` to improve raytracing performance.
history_max_lost : int (10000)
Number of 'lost' rays to retain in the raytrace history. Lost rays
are those that are launched from the source but that do not reach the
last optical element (typically a detector). For many x-ray raytracing
applications the number of lost rays will be very large, and retention
of all lost rays would quickly exhaust available system memory. To
avoid memory issues, while still retaining some lost rays for
diagnostic purposes, a randomized truncation of the lost rays is
performed.
save_config : bool (False)
Option whether or not to save the config dictionary. Output format
is currently limited to json format (hdf5 and pickle coming soon).
save_images : bool (False)
Controls saving of images. Images will be saved for every run, and a
combined image will be saved at the conclusion of all runs. Control of
output for individual optics can be set within the object specific
config sections. Image format will be determined by the option
`image_ext` (default .tif). Images will be saved to the `output_path`.
save_results : bool (False)
Controls saving of the raytracing results dictionary. The contents of
the results dictionary are controlled by `keep_meta`, `keep_images`
and `keep_history`. Results will only be saved after all runs are
completed. Output format will be determined by the option
`results_ext` (default .hdf5). OUtput will be saved to the
`output_path`.
print_results : bool (True)
Control text output to terminal of raytracing summary and optics
specific information. (Note: control of logging/debugging output is
controlled through a separate option that is not yet implemented.)
version: string
The version number of xicsrt when this config was created.
This option is set internally and should not be modified by the user.
"""
config = dict()
config['general'] = dict()
config['general']['version'] = _version.__version__
config['general']['number_of_iter'] = 1
config['general']['number_of_runs'] = 1
config['general']['random_seed'] = None
config['general']['pathlist'] = []
config['general']['pathlist_default'] = get_pathlist_default()
config['general']['strict_config_check'] = True
config['general']['output_path'] = None
config['general']['output_prefix'] = 'xicsrt'
config['general']['output_suffix'] = None
config['general']['output_run_suffix'] = None
config['general']['image_ext'] = '.tif'
config['general']['results_ext'] = '.hdf5'
config['general']['config_ext'] = '.json'
config['general']['make_directories'] = False
config['general']['keep_meta'] = True
config['general']['keep_images'] = True
config['general']['keep_history'] = True
config['general']['history_max_lost'] = 10000
config['general']['save_config'] = False
config['general']['save_images'] = False
config['general']['save_results'] = False
config['general']['print_results'] = True
config['sources'] = dict()
config['optics'] = dict()
config['filters'] = dict()
config['scenario'] = dict()
return config
[docs]
def get_config(config_user=None):
config = default_config()
update_config(config, config_user, strict=False, update=True)
return config
[docs]
def refresh_config(config_new):
"""
When a config file is loaded from a another system or from a different user
it may contain default values that are not appropriate for the current
environment. This function will overwrite these options with new default
values where appropriate.
"""
refresh = {}
refresh['general'] = {}
refresh['general']['pathlist_default'] = None
config_new = copy.deepcopy(config_new)
update_config(
config_new,
refresh,
strict=False,
update=True,
ignore_none=False,
)
config = default_config()
update_config(
config,
config_new,
strict=False,
update=True,
ignore_none=True,
)
return config
[docs]
def get_pathlist_default():
"""
Return a list of the default sources and optics directories.
These locations will be based on the location of this module.
"""
pathlist = []
pathlist = _add_pathlist_builtin(pathlist)
pathlist = _add_pathlist_contrib(pathlist)
return pathlist
[docs]
def _add_pathlist_builtin(pathlist):
# Add paths to built-in objects.
path_module = os.path.dirname(os.path.abspath(__file__))
pathlist.append(os.path.join(path_module, 'filters'))
pathlist.append(os.path.join(path_module, 'sources'))
pathlist.append(os.path.join(path_module, 'optics'))
return pathlist
[docs]
def _add_pathlist_contrib(pathlist):
# Check if the xicsrt_contrib module was successfully imported.
if not 'xicsrt_contrib' in globals():
return pathlist
# Add paths to the xicsrt_contrib objects.
path_module = os.path.dirname(os.path.abspath(xicsrt_contrib.__file__))
pathlist.append(os.path.join(path_module, 'filters'))
pathlist.append(os.path.join(path_module, 'sources'))
pathlist.append(os.path.join(path_module, 'optics'))
return pathlist
[docs]
def config_to_numpy(obj):
xicsrt_misc._convert_to_numpy(obj)
return obj
[docs]
def config_from_numpy(obj):
xicsrt_misc._convert_from_numpy(obj)
return obj
[docs]
def update_config(
config,
config_new,
strict=None,
update=None,
ignore_none=None,
):
"""
Overwrite any values in the given config dict with the values in the
config_new dict. This will be done recursively to allow nested
dictionaries.
keywords:
strict (True)
If True then an error will be raised if an option is found in
the user dict that is not found in the default dict.
update (False)
If True any unmatched options that are found will be retained.
When False they will simply be ignored. This option has no effect
unless strict = False.
ignore_none (False)
If True any options found in config_new with a value of None will
be ignored.
"""
_update_config_dict(config, config_new, strict, update, ignore_none)
return config
[docs]
def _update_config_dict(
config,
config_new,
strict=None,
update=None,
ignore_none=None,
):
"""
Recursive worker function for `update_config`.
"""
if strict is None:
strict = True
if update is None:
update = False
if ignore_none is None:
ignore_none = False
if config_new is None:
return
for key in config_new:
if not key in config:
if strict:
raise Exception("User option not recognized: {}".format(key))
if update:
config[key] = config_new[key]
else:
if isinstance(config[key], dict) and isinstance(config_new[key], dict):
_update_config_dict(
config[key],
config_new[key],
strict=strict,
update=update,
ignore_none=ignore_none
)
else:
if ignore_none and config_new[key] is None:
pass
else:
config[key] = config_new[key]