Click here to go to the corresponding page for the latest version of DIALS
Source code for dials.util.options
from __future__ import absolute_import, division, print_function
import copy
import sys
import os
import itertools
import optparse
import pickle
import traceback
from collections import defaultdict, namedtuple
from orderedset import OrderedSet
try:
import cPickle # deliberately not using six.moves
pickle_errors = pickle.UnpicklingError, cPickle.UnpicklingError
except ImportError:
pickle_errors = (pickle.UnpicklingError,)
import libtbx.phil
from dials.util import Sorry
from dials.util.multi_dataset_handling import (
sort_tables_to_experiments_order,
renumber_table_id_columns,
)
from dxtbx.model import ExperimentList
tolerance_phil_scope = libtbx.phil.parse(
"""
tolerance
.help = "Tolerances used to determine shared models"
.expert_level = 2
{
beam {
wavelength = 1e-6
.type = float(value_min=0.0)
.help = "The wavelength tolerance"
direction = 1e-6
.type = float(value_min=0.0)
.help = "The direction tolerance"
polarization_normal = 1e-6
.type = float(value_min=0.0)
.help = "The polarization normal tolerance"
polarization_fraction = 1e-6
.type = float(value_min=0.0)
.help = "The polarization fraction tolerance"
}
detector {
fast_axis = 1e-6
.type = float(value_min=0.0)
.help = "The fast axis tolerance"
slow_axis = 1e-6
.type = float(value_min=0.0)
.help = "The slow axis tolerance"
origin = 5e-2
.type = float(value_min=0.0)
.help = "The origin tolerance"
}
goniometer {
rotation_axis = 1e-6
.type = float(value_min=0.0)
.help = "The rotation axis tolerance"
fixed_rotation = 1e-6
.type = float(value_min=0.0)
.help = "The fixed rotation tolerance"
setting_rotation = 1e-6
.type = float(value_min=0.0)
.help = "The setting rotation tolerance"
}
scan {
oscillation = 0.01
.type = float(value_min=0.0)
.help = "The oscillation tolerance for the scan"
}
}
"""
)
geometry_phil_scope = libtbx.phil.parse(
"""
geometry
.help = "Allow overrides of experimental geometry"
.expert_level = 2
{
include scope dxtbx.model.beam.beam_phil_scope
include scope dxtbx.model.detector.detector_phil_scope
include scope dxtbx.model.goniometer.goniometer_phil_scope
include scope dxtbx.model.scan.scan_phil_scope
convert_stills_to_sequences = False
.type = bool
.help = "When overriding the scan, convert stills into sequences"
.short_caption = "Convert stills into sequences"
convert_sequences_to_stills = False
.type = bool
.help = "When overriding the scan, convert sequences into stills"
.short_caption = "Convert sequences into stills"
}
""",
process_includes=True,
)
format_phil_scope = libtbx.phil.parse(
"""
format
.help = "Options to pass to the Format class"
.expert_level = 2
{
dynamic_shadowing = auto
.type = bool
.help = "Enable dynamic shadowing"
multi_panel = False
.type = bool
.help = "Enable a multi-panel detector model."
"(Not supported by all detector formats)"
}
"""
)
[docs]class ConfigWriter(object):
"""Class to write configuration to file."""
def __init__(self, master_phil):
"""
Initialise with the master phil.
:param master_phil: The master phil scope
"""
self._master_phil = master_phil
[docs] def write(self, params, filename):
"""
Write the configuration to file.
:param params: The input phil parameters
:param filename: The output filename
"""
# Get the modified phil
modified_phil = self._master_phil.format(python_object=params)
# Get the phil as text
text = modified_phil.as_str()
# Write the text to file
with open(filename, "w") as f:
f.write(text)
# Simple tuple to hold basic information on why an argument failed
ArgumentHandlingErrorInfo = namedtuple(
"ArgumentHandlingErrorInfo",
["name", "validation", "message", "traceback", "type", "exception"],
)
[docs]class Importer(object):
"""A class to import the command line arguments."""
def __init__(
self,
args,
read_experiments=False,
read_reflections=False,
read_experiments_from_images=False,
check_format=True,
verbose=False,
compare_beam=None,
compare_detector=None,
compare_goniometer=None,
scan_tolerance=None,
format_kwargs=None,
load_models=True,
):
"""
Parse the arguments. Populates its instance attributes in an intelligent way
from the arguments in args.
If include is set, only those items set will be tried. If not, then if
exclude is set, then those items will not be tested.
These are the types we can import:
- images: a list of images
- reflections : a list of reflections
- experiments: a list of experiments
:param args: The arguments to parse
:param read_experiments: Try to read the experiments
:param read_reflections: Try to read the reflections
:param read_experiments_from_images: Try to read the experiments from images
:param check_format: Check the format when reading images
:param verbose: True/False print out some stuff
:param load_models: Whether to load all models for ExperimentLists
"""
# Initialise output
self.experiments = []
self.reflections = []
self.unhandled = args
# Keep track of any errors whilst handling arguments
self.handling_errors = defaultdict(list)
# First try to read image files
if read_experiments_from_images:
self.unhandled = self.try_read_experiments_from_images(
self.unhandled,
verbose,
compare_beam,
compare_detector,
compare_goniometer,
scan_tolerance,
format_kwargs,
load_models,
)
# Second try to read experiment files
if read_experiments:
self.unhandled = self.try_read_experiments(
self.unhandled, check_format, verbose
)
# Third try to read reflection files
if read_reflections:
self.unhandled = self.try_read_reflections(self.unhandled, verbose)
def _handle_converter_error(self, argument, exception, type, validation=False):
"Record information about errors that occured processing an argument"
self.handling_errors[argument].append(
ArgumentHandlingErrorInfo(
name=argument,
validation=validation,
message=str(exception),
traceback=traceback.format_exc(),
type=type,
exception=exception,
)
)
[docs] def try_read_experiments_from_images(
self,
args,
verbose,
compare_beam,
compare_detector,
compare_goniometer,
scan_tolerance,
format_kwargs,
load_models=True,
):
"""
Try to import images.
:param args: The input arguments
:param verbose: Print verbose output
:param compare_beam:
:param compare_detector:
:param compare_goniometer:
:param scan_tolerance:
:param format_kwargs:
:param load_models: Whether to load all models for ExperimentLists
:return: Unhandled arguments
"""
from dxtbx.model.experiment_list import ExperimentListFactory
from dials.util.phil import FilenameDataWrapper, ExperimentListConverters
from glob import glob
# If filenames contain wildcards, expand
args_new = []
for arg in args:
if "*" in arg:
args_new.extend(glob(arg))
else:
args_new.append(arg)
args = args_new
unhandled = []
experiments = ExperimentListFactory.from_filenames(
args,
verbose=verbose,
unhandled=unhandled,
compare_beam=compare_beam,
compare_detector=compare_detector,
compare_goniometer=compare_goniometer,
scan_tolerance=scan_tolerance,
format_kwargs=format_kwargs,
load_models=load_models,
)
if len(experiments) > 0:
filename = "<image files>"
obj = FilenameDataWrapper(filename, experiments)
ExperimentListConverters.cache[filename] = obj
self.experiments.append(obj)
return unhandled
[docs] def try_read_experiments(self, args, check_format, verbose):
"""
Try to import experiments.
:param args: The input arguments
:param check_format: True/False check the image format
:param verbose: Print verbose output
:returns: Unhandled arguments
"""
from dials.util.phil import ExperimentListConverters
from dxtbx.model.experiment_list import InvalidExperimentListError
converter = ExperimentListConverters(check_format)
unhandled = []
for argument in args:
try:
self.experiments.append(converter.from_string(argument))
except InvalidExperimentListError as e:
# This is a validation-related error: The file appears not to be in the correct format
self._handle_converter_error(
argument, e, type="ExperimentList", validation=True
)
unhandled.append(argument)
except Exception as e:
self._handle_converter_error(argument, e, type="ExperimentList")
unhandled.append(argument)
return unhandled
[docs] def try_read_reflections(self, args, verbose):
"""Try to import reflections.
:param args: The input arguments
:param verbose: Print verbose output
:returns: Unhandled arguments
"""
from dials.util.phil import ReflectionTableConverters
converter = ReflectionTableConverters()
unhandled = []
for argument in args:
try:
self.reflections.append(converter.from_string(argument))
except pickle_errors:
self._handle_converter_error(
argument,
pickle.UnpicklingError("Appears to be an invalid pickle file"),
type="Reflections",
validation=True,
)
unhandled.append(argument)
except Exception as e:
self._handle_converter_error(argument, e, type="Reflections")
unhandled.append(argument)
return unhandled
[docs]class PhilCommandParser(object):
"""A class to parse phil parameters from positional arguments"""
def __init__(
self,
phil=None,
read_experiments=False,
read_reflections=False,
read_experiments_from_images=False,
check_format=True,
):
"""
Initialise the parser.
:param phil: The phil scope
:param read_experiments: Try to read the experiments
:param read_reflections: Try to read the reflections
:param read_experiments_from_images: Try to read the experiments from images
:param check_format: Check the format when reading images
"""
from dials.util.phil import parse
# Set the system phil scope
if phil is None:
self._system_phil = parse("")
else:
self._system_phil = copy.deepcopy(phil)
# Set the flags
self._read_experiments = read_experiments
self._read_reflections = read_reflections
self._read_experiments_from_images = read_experiments_from_images
self._check_format = check_format
# Adopt the input scope
input_phil_scope = self._generate_input_scope()
if input_phil_scope is not None:
self.system_phil.adopt_scope(input_phil_scope)
# Set the working phil scope
self._phil = self.system_phil.fetch(source=parse(""))
@property
def phil(self):
"""
Get the phil object
:return: The phil scope
"""
return self._phil
@property
def system_phil(self):
"""
Get the system phil.
:return: The system phil scope
"""
return self._system_phil
@property
def diff_phil(self):
"""
Get the diff phil.
:return: The difference phil scope
"""
return self.system_phil.fetch_diff(source=self.phil)
[docs] def parse_args(
self, args, verbose=False, return_unhandled=False, quick_parse=False
):
"""
Parse the command line arguments.
:param args: The input arguments
:param verbose: Print verbose output
:param return_unhandled: True/False also return unhandled arguments
:param quick_parse: Return as fast as possible and without reading any data,
ignoring class constructor options.
:return: The options and parameters and (optionally) unhandled arguments
"""
from dxtbx.model.experiment_list import BeamComparison
from dxtbx.model.experiment_list import DetectorComparison
from dxtbx.model.experiment_list import GoniometerComparison
from dials.util.phil import parse
# Parse the command line phil parameters
user_phils = []
unhandled = []
interpretor = self.system_phil.command_line_argument_interpreter()
def _is_a_phil_file(filename):
return any(
filename.endswith(phil_ext)
for phil_ext in (".phil", ".param", ".params", ".eff", ".def")
)
for arg in args:
if (
_is_a_phil_file(arg)
and os.path.isfile(arg)
and os.path.getsize(arg) > 0
):
try:
user_phils.append(parse(file_name=arg))
except Exception:
if return_unhandled:
unhandled.append(arg)
else:
raise
elif arg.find("=") >= 0:
try:
user_phils.append(interpretor.process_arg(arg=arg))
except Exception:
if return_unhandled:
unhandled.append(arg)
else:
raise
else:
unhandled.append(arg)
# Fetch the phil parameters
self._phil, unused = self.system_phil.fetch(
sources=user_phils, track_unused_definitions=True
)
# Print if bad definitions
if len(unused) > 0:
msg = [item.object.as_str().strip() for item in unused]
msg = "\n".join([" %s" % line for line in msg])
raise RuntimeError(
"The following definitions were not recognised\n%s" % msg
)
# Extract the parameters
params = self._phil.extract()
# Stop at this point if quick_parse is set. A second pass may be needed.
if quick_parse:
return params, unhandled
# Create some comparison functions
if self._read_experiments_from_images:
compare_beam = BeamComparison(
wavelength_tolerance=params.input.tolerance.beam.wavelength,
direction_tolerance=params.input.tolerance.beam.direction,
polarization_normal_tolerance=params.input.tolerance.beam.polarization_normal,
polarization_fraction_tolerance=params.input.tolerance.beam.polarization_fraction,
)
compare_detector = DetectorComparison(
fast_axis_tolerance=params.input.tolerance.detector.fast_axis,
slow_axis_tolerance=params.input.tolerance.detector.slow_axis,
origin_tolerance=params.input.tolerance.detector.origin,
)
compare_goniometer = GoniometerComparison(
rotation_axis_tolerance=params.input.tolerance.goniometer.rotation_axis,
fixed_rotation_tolerance=params.input.tolerance.goniometer.fixed_rotation,
setting_rotation_tolerance=params.input.tolerance.goniometer.setting_rotation,
)
scan_tolerance = params.input.tolerance.scan.oscillation
# FIXME Should probably make this smarter since it requires editing here
# and in dials.import phil scope
try:
format_kwargs = {
"dynamic_shadowing": params.format.dynamic_shadowing,
"multi_panel": params.format.multi_panel,
}
except AttributeError:
format_kwargs = None
else:
compare_beam = None
compare_detector = None
compare_goniometer = None
scan_tolerance = None
format_kwargs = None
try:
load_models = params.load_models
except AttributeError:
load_models = True
# Try to import everything
importer = Importer(
unhandled,
read_experiments=self._read_experiments,
read_reflections=self._read_reflections,
read_experiments_from_images=self._read_experiments_from_images,
check_format=self._check_format,
verbose=verbose,
compare_beam=compare_beam,
compare_detector=compare_detector,
compare_goniometer=compare_goniometer,
scan_tolerance=scan_tolerance,
format_kwargs=format_kwargs,
load_models=load_models,
)
# Grab a copy of the errors that occured in case the caller wants them
self.handling_errors = importer.handling_errors
# Add the cached arguments
for obj in importer.experiments:
params.input.experiments.append(obj)
for obj in importer.reflections:
params.input.reflections.append(obj)
# Convert to phil
self._phil = self.system_phil.format(python_object=params)
return params, importer.unhandled
def _generate_input_scope(self):
"""
Generate the required input scope.
:return: The input phil scope
"""
from dials.util.phil import parse
# Create the input scope
require_input_scope = (
self._read_experiments
or self._read_reflections
or self._read_experiments_from_images
)
if not require_input_scope:
return None
input_phil_scope = parse("input {}")
main_scope = input_phil_scope.get_without_substitution("input")
assert len(main_scope) == 1
main_scope = main_scope[0]
# Add the experiments phil scope
if self._read_experiments or self._read_experiments_from_images:
phil_scope = parse(
"""
experiments = None
.type = experiment_list(check_format=%r)
.multiple = True
.help = "The experiment list file path"
"""
% self._check_format
)
main_scope.adopt_scope(phil_scope)
# If reading images, add some more parameters
if self._read_experiments_from_images:
main_scope.adopt_scope(tolerance_phil_scope)
# Add the reflections scope
if self._read_reflections:
phil_scope = parse(
"""
reflections = None
.type = reflection_table
.multiple = True
.help = "The reflection table file path"
"""
)
main_scope.adopt_scope(phil_scope)
# Return the input scope
return input_phil_scope
[docs]class OptionParserBase(optparse.OptionParser, object):
"""The base class for the option parser."""
def __init__(self, config_options=False, sort_options=False, **kwargs):
"""
Initialise the class.
:param config_options: True/False show configuration options
:param sort_options: True/False show argument sorting options
"""
# Initialise the option parser
super(OptionParserBase, self).__init__(**kwargs)
# Add an option to show configuration parameters
if config_options:
self.add_option(
"-c",
"--show-config",
action="store_true",
default=False,
dest="show_config",
help="Show the configuration parameters.",
)
self.add_option(
"-a",
"--attributes-level",
default=0,
type="int",
dest="attributes_level",
help="Set the attributes level for showing configuration parameters",
)
self.add_option(
"-e",
"--expert-level",
type="int",
default=0,
dest="expert_level",
help="Set the expert level for showing configuration parameters",
)
self.add_option(
"--export-autocomplete-hints",
action="store_true",
default=False,
dest="export_autocomplete_hints",
help=optparse.SUPPRESS_HELP,
)
# Add an option to sort
if sort_options:
self.add_option(
"-s",
"--sort",
action="store_true",
dest="sort",
default=False,
help="Sort the arguments",
)
# Set a verbosity parameter
self.add_option(
"-v", action="count", default=0, dest="verbose", help="Increase verbosity"
)
# Add an option for PHIL file to parse - PHIL files passed as
# positional arguments are also read but this allows the user to
# explicitly specify STDIN
self.add_option(
"--phil",
action="append",
metavar="FILE",
help="PHIL files to read. Pass '-' for STDIN. Can be specified multiple times, but duplicates ignored.",
)
[docs] def parse_args(self, args=None, quick_parse=False):
"""
Parse the command line arguments and get system configuration.
:param args: The arguments to parse.
:returns: The options and phil parameters
"""
# Parse the command line arguments, this will separate out
# options (e.g. -o, --option) and positional arguments, in
# which phil options will be included.
options, args = super(OptionParserBase, self).parse_args(args=args)
# Read any argument-specified PHIL file. Ignore duplicates.
if options.phil:
for philfile in OrderedSet(options.phil):
# Should we read STDIN?
if philfile == "-":
lines = sys.stdin.readlines()
else:
# Otherwise, assume we've been given a path
with open(philfile) as phil_input:
lines = phil_input.readlines()
# Add these to the unparsed argument list
args.extend(l.strip() for l in lines)
# Maybe sort the data
if hasattr(options, "sort") and options.sort:
args = sorted(args)
# Return the parameters
return options, args
[docs] def format_epilog(self, formatter):
"""Don't do formatting on epilog."""
if self.epilog is None:
return ""
return self.epilog
[docs]class OptionParser(OptionParserBase):
"""A class to parse command line options and get the system configuration.
The class extends optparse.OptionParser to include the reading of phil
parameters."""
def __init__(
self,
phil=None,
read_experiments=False,
read_reflections=False,
read_experiments_from_images=False,
check_format=True,
sort_options=False,
**kwargs
):
"""
Initialise the class.
:param phil: The phil scope
:param read_experiments: Try to read the experiments
:param read_reflections: Try to read the reflections
:param read_experiments_from_images: Try to read the experiments from images
:param check_format: Check the format when reading images
:param sort_options: Show argument sorting options
"""
# Create the phil parser
self._phil_parser = PhilCommandParser(
phil=phil,
read_experiments=read_experiments,
read_reflections=read_reflections,
read_experiments_from_images=read_experiments_from_images,
check_format=check_format,
)
# Initialise the option parser
super(OptionParser, self).__init__(
sort_options=sort_options,
config_options=self.system_phil.as_str() != "",
**kwargs
)
[docs] def parse_args(
self,
args=None,
show_diff_phil=False,
return_unhandled=False,
ignore_unhandled=False,
quick_parse=False,
):
"""
Parse the command line arguments and get system configuration.
:param args: The input arguments
:param show_diff_phil: True/False Print the diff phil
:param return_unhandled: True/False return unhandled arguments
:param ignore_unhandled: True/False ignore unhandled arguments
if return_unhandled is False
:param quick_parse: Return as fast as possible and without reading any data,
ignoring class constructor options
:return: The options and phil parameters
"""
# Parse the command line arguments, this will separate out
# options (e.g. -o, --option) and positional arguments, in
# which phil options will be included.
options, args = super(OptionParser, self).parse_args(
args=args, quick_parse=quick_parse
)
# Show config
if hasattr(options, "show_config") and options.show_config:
print(
"Showing configuration parameters with:\n"
" attributes_level = %d\n"
" expert_level = %d\n"
% (options.attributes_level, options.expert_level)
)
print(
self.phil.as_str(
expert_level=options.expert_level,
attributes_level=options.attributes_level,
)
)
exit(0)
if (
hasattr(options, "export_autocomplete_hints")
and options.export_autocomplete_hints
):
self._export_autocomplete_hints()
exit(0)
# Parse the phil parameters
params, args = self._phil_parser.parse_args(
args,
options.verbose > 0,
return_unhandled=return_unhandled,
quick_parse=quick_parse,
)
# Print the diff phil
if show_diff_phil:
diff_phil_str = self.diff_phil.as_str()
if diff_phil_str != "":
print("The following parameters have been modified:\n")
print(diff_phil_str)
# Return the parameters
if return_unhandled:
return params, options, args
elif len(args) > 0 and not quick_parse:
# Handle printing any messages to diagnose unhandled arguments
msg = self._warn_about_unhandled_args(args, verbosity=options.verbose)
if ignore_unhandled:
print(msg)
else:
raise Sorry(msg)
return params, options
def _warn_about_unhandled_args(self, unhandled, verbosity=0):
"""
Generate any messages about unhandled arguments.
This separates errors by validation/non-validation related, and only
gives the user validation information if there's no other reason (or
asked for verbose output).
:param unhandled: List of unhandled arguments
:param verbosity: The output verbosity determined during parsing
:returns: A formatted information string
"""
msg = []
msg.append("Unable to handle the following arguments:")
# If we have any detailed information about why any of the
# arguments weren't processed, give this to the user
for arg in [x for x in unhandled if x in self._phil_parser.handling_errors]:
# Split the reasons for unhandling into validation, non-validation
non_valid = [
x for x in self._phil_parser.handling_errors[arg] if not x.validation
]
valid = [x for x in self._phil_parser.handling_errors[arg] if x.validation]
# If we have non-validation-related errors, these are more important
for _, err in itertools.groupby(non_valid, key=lambda x: x.message):
err = list(err)
# Grouping the errors by message lets us avoid repeating messages
if len(err) > 1:
msg.append(
' "{}" failed repeatedly during processing:\n{}\n'.format(
arg, " " + err[0].message
)
)
elif isinstance(err[0].exception, Sorry):
msg.append(
' "{}" failed during {} processing:\n {}\n'.format(
arg, err[0].type, err[0].message
)
)
else:
msg.append(
' "{}" failed during {} processing:\n{}\n'.format(
arg,
err[0].type,
"\n".join(
" " + x for x in err[0].traceback.splitlines()
),
)
)
# Otherwise (or if asked for verbosity), list the validation errors
if valid and (not non_valid or verbosity > 1):
msg.append(
" {} did not appear to conform to any{} expected format:".format(
arg, " other" if non_valid else ""
)
)
slen = max(len(x.type) for x in valid)
for err in valid:
msg.append(
" - {} {}".format(
"{}:".format(err.type).ljust(slen + 1), err.message
)
)
return "\n".join(msg)
@property
def phil(self):
"""
Get the phil object
:returns: The phil scope
"""
return self._phil_parser.phil
@property
def system_phil(self):
"""
Get the system phil.
:returns: The system phil scope
"""
return self._phil_parser.system_phil
@property
def diff_phil(self):
"""
Get the diff phil.
:returns: The diff phil scope
"""
return self._phil_parser.diff_phil
def _strip_rst_markup(self, text):
"""
Strip rst markup
:param text: The text to strip
:return: The stripped text
"""
return text.replace("::", ":")
[docs] def format_help(self, formatter=None):
"""
Format the help string
:param formatter: The formatter to use
:return: The formatted help text
"""
result = super(OptionParser, self).format_help(formatter=formatter)
return self._strip_rst_markup(result)
def _export_autocomplete_hints(self):
# complete list of all parameters
parameter_list = []
# short name -> full name expansion for unique names
parameter_expansion_list = {}
# full name -> list of flags for choice parameters
parameter_choice_list = {}
for d in self.phil.all_definitions():
# Create complete list of all parameters
parameter_list.append(d.path)
# Create expansion (alias) list for unique names that are not expert commands
if (
d.object.name not in parameter_expansion_list
and d.object.expert_level is None
and d.parent.expert_level is None
):
parameter_expansion_list[d.object.name] = d.path
else:
parameter_expansion_list[d.object.name] = None
# Extract parameter choice lists
if d.object.type.phil_type == "choice":
parameter_choice_list[d.path] = [
w[1:] if w.startswith("*") else w
for w in (str(x) for x in d.object.words)
]
elif d.object.type.phil_type == "bool":
parameter_choice_list[d.path] = ["true", "false"]
def construct_completion_tree(paths):
"""Construct a tree of parameters, grouped by common prefixes"""
# Split parameter paths at '.' character
paths = [p.split(".", 1) for p in paths]
# Identify all names that are directly on this level
# or represent parameter groups with a common prefix
top_elements = {"%s%s" % (x[0], "=" if len(x) == 1 else ".") for x in paths}
# Partition all names that are further down the tree by their prefix
subpaths = {}
for p in paths:
if len(p) > 1:
if p[0] not in subpaths:
subpaths[p[0]] = []
subpaths[p[0]].append(p[1])
# If there are prefixes with only one name beneath them, put them on the top level
for s in list(subpaths.keys()):
if len(subpaths[s]) == 1:
top_elements.remove("%s." % s)
top_elements.add("%s.%s=" % (s, subpaths[s][0]))
del subpaths[s]
result = {"": top_elements}
# Revursively process each group
for n, x in subpaths.items():
result[n] = construct_completion_tree(x)
return result
print("function _dials_autocomplete_flags ()")
print("{")
print(' case "$1" in')
for p in parameter_choice_list:
print("\n %s)" % p)
print(
' _dials_autocomplete_values="%s";;'
% " ".join(parameter_choice_list[p])
)
print("\n *)")
print(' _dials_autocomplete_values="";;')
print(" esac")
print("}")
print("function _dials_autocomplete_expansion ()")
print("{")
print(' case "$1" in')
for p, exp in parameter_expansion_list.items():
if exp is not None:
print("\n %s=)" % p)
print(' _dials_autocomplete_values="%s=";;' % exp)
print("\n *)")
print(' _dials_autocomplete_values="";;')
print(" esac")
print("}")
tree = construct_completion_tree(parameter_list)
def _tree_to_bash(prefix, tree):
for subkey in tree:
if subkey != "":
_tree_to_bash(prefix + subkey + ".", tree[subkey])
print("\n %s*)" % (prefix + subkey + "."))
print(
' _dials_autocomplete_values="%s";;'
% " ".join(
sorted(
[prefix + subkey + "." + x for x in tree[subkey][""]]
)
)
)
print("function _dials_autocomplete_hints ()")
print("{")
print(' case "$1" in')
_tree_to_bash("", tree)
toplevelset = tree[""] | {
p + "=" for p, exp in parameter_expansion_list.items() if exp is not None
}
print("\n *)")
print(' _dials_autocomplete_values="%s";;' % " ".join(sorted(toplevelset)))
print(" esac")
print("}")
[docs]def flatten_reflections(filename_object_list):
"""
Flatten a list of reflections tables
A check is also made for the 'id' values in the reflection tables, which are
renumbered from 0..n-1 to avoid clashes. The experiment_identifiers dict is
also updated if present in the input tables.
:param filename_object_list: The parameter item
:return: The flattened reflection table
"""
tables = [o.data for o in filename_object_list]
if len(tables) > 1:
tables = renumber_table_id_columns(tables)
return tables
[docs]def flatten_experiments(filename_object_list):
"""
Flatten a list of experiment lists
:param filename_object_list: The parameter item
:return: The flattened experiment lists
"""
result = ExperimentList()
for o in filename_object_list:
result.extend(o.data)
return result
[docs]def reflections_and_experiments_from_files(
reflection_file_object_list, experiment_file_object_list
):
"""Extract reflection tables and an experiment list from the files.
If experiment identifiers are set, the order of the reflection tables is
changed to match the order of experiments.
"""
tables = flatten_reflections(reflection_file_object_list)
experiments = flatten_experiments(experiment_file_object_list)
if tables and experiments:
tables = sort_tables_to_experiments_order(tables, experiments)
return tables, experiments