Jsonify lens-shading as lists

This commit is contained in:
Joel Collins 2019-09-15 14:05:33 +01:00
parent 756f18d853
commit 354524de64

View file

@ -4,8 +4,10 @@ import errno
import logging import logging
import shutil import shutil
import copy import copy
import numpy as np
from fractions import Fraction from fractions import Fraction
from collections import abc
from .utilities import recursively_apply
""" """
Attributes: Attributes:
@ -18,7 +20,7 @@ Attributes:
# HANDLE THE DEFAULT CONFIGURATION FILE # HANDLE THE DEFAULT CONFIGURATION FILE
HERE = os.path.abspath(os.path.dirname(__file__)) HERE = os.path.abspath(os.path.dirname(__file__))
DEFAULT_CONFIG_PATH = os.path.join(HERE, 'microscope_settings.default.yaml') DEFAULT_CONFIG_PATH = os.path.join(HERE, "microscope_settings.default.yaml")
USER_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".openflexure") USER_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".openflexure")
USER_CONFIG_FILE = os.path.join(USER_CONFIG_DIR, "microscope_settings.yaml") USER_CONFIG_FILE = os.path.join(USER_CONFIG_DIR, "microscope_settings.yaml")
@ -26,7 +28,7 @@ USER_CONFIG_FILE = os.path.join(USER_CONFIG_DIR, "microscope_settings.yaml")
USER_CONFIG_FILE_OLD = os.path.join(USER_CONFIG_DIR, "microscoperc.yaml") USER_CONFIG_FILE_OLD = os.path.join(USER_CONFIG_DIR, "microscoperc.yaml")
# Load the default config # Load the default config
with open(DEFAULT_CONFIG_PATH, 'r') as default_rc: with open(DEFAULT_CONFIG_PATH, "r") as default_rc:
DEFAULT_CONFIG = default_rc.read() DEFAULT_CONFIG = default_rc.read()
# HANDLE CUSTOM YAML PARSING # HANDLE CUSTOM YAML PARSING
@ -36,76 +38,51 @@ def construct_yaml_bool(self, node):
YAML otherwise caused grief for picamera properties where 'off' is a valid string value. YAML otherwise caused grief for picamera properties where 'off' is a valid string value.
""" """
override_bool_values = { override_bool_values = {
'yes': True, "yes": True,
'no': False, "no": False,
'true': True, "true": True,
'false': False, "false": False,
'on': 'on', "on": "on",
'off': 'off', "off": "off",
} }
value = self.construct_scalar(node) value = self.construct_scalar(node)
return override_bool_values[value.lower()] return override_bool_values[value.lower()]
yaml.Loader.add_constructor(u'tag:yaml.org,2002:bool', construct_yaml_bool) yaml.Loader.add_constructor(u"tag:yaml.org,2002:bool", construct_yaml_bool)
# HANDLE CONVERTING ARBITRARY CONFIG TO JSON-SAFE JSON # HANDLE CONVERTING ARBITRARY CONFIG TO JSON-SAFE JSON
def convert_type_to_json_safe(v): def convert_type_to_json_safe(v):
"""Make an individual attribute JSON-safe""" """Make an individual attribute JSON-safe"""
if isinstance(v, Fraction): if isinstance(v, Fraction):
return float(v) return float(v)
elif isinstance(v, np.ndarray):
return v.tolist()
else: else:
return v return v
def recursively_apply(data, func):
"""
Recursively apply a function to a dictionary, list, array, or tuple
Args: def settings_to_json(data: dict):
data: Input iterable data
func: Function to apply to all non-iterable values
"""
# If the object is a dictionary
if isinstance(data, abc.Mapping):
return {key: recursively_apply(val, func) for key, val in data.items()}
# If the object is iterable but NOT a dictionary or a string
elif (isinstance(data, abc.Iterable) and
not isinstance(data, abc.Mapping) and
not isinstance(data, str)):
return [recursively_apply(x, func) for x in data]
# if the object is neither a map nor iterable
else:
return func(data)
def settings_to_json(data: dict, clean_keys=True):
""" """
Make a copy of an input dictionary that's safe for JSON return Make a copy of an input dictionary that's safe for JSON return
Args: Args:
data: Input dictionary data: Input dictionary
clean_keys: Modify any keys unsuitable for JSON return
""" """
# Do not overwrite original data dictionary # Do not overwrite original data dictionary
d = copy.deepcopy(data) d = copy.deepcopy(data)
# If we're cleaning up unsuitable keys
if clean_keys:
# Convert lens_shading_table to a bool
if 'picamera_settings' in d['camera_settings'] and 'lens_shading_table' in d['camera_settings']['picamera_settings']:
logging.debug("Bool-ifying lens_shading_table")
if d['camera_settings']['picamera_settings']['lens_shading_table'] is not None:
d['camera_settings']['picamera_settings']['lens_shading_table'] = True
return recursively_apply(d, convert_type_to_json_safe) return recursively_apply(d, convert_type_to_json_safe)
# HANDLE BASIC LOADING AND SAVING OF YAML FILES # HANDLE BASIC LOADING AND SAVING OF YAML FILES
def load_yaml_file(config_path) -> dict: def load_yaml_file(config_path) -> dict:
""" """
Open a .yaml config file Open a .yaml config file
@ -138,7 +115,7 @@ def save_yaml_file(config_path: str, config_dict: dict, safe: bool = False):
logging.info("Saving {}...".format(config_path)) logging.info("Saving {}...".format(config_path))
logging.debug(config_dict) logging.debug(config_dict)
with open(config_path, 'w') as outfile: with open(config_path, "w") as outfile:
if not safe: if not safe:
yaml.dump(config_dict, outfile) yaml.dump(config_dict, outfile)
else: else:
@ -173,7 +150,7 @@ def initialise_file(config_path, populate: str = ""):
create_file(config_path) create_file(config_path)
logging.info("Populating {}...".format(config_path)) logging.info("Populating {}...".format(config_path))
with open(config_path, 'w') as outfile: with open(config_path, "w") as outfile:
outfile.write(populate) outfile.write(populate)
@ -186,12 +163,13 @@ class OpenflexureSettingsFile:
config_path (str): Path to the config YAML file (None falls back to default location) config_path (str): Path to the config YAML file (None falls back to default location)
expand (bool): Expand paths to valid auxillary config files. expand (bool): Expand paths to valid auxillary config files.
""" """
def __init__(self, config_path: str = None, expand: bool = True): def __init__(self, config_path: str = None, expand: bool = True):
global DEFAULT_CONFIG, USER_CONFIG_FILE global DEFAULT_CONFIG, USER_CONFIG_FILE
self.expandable_keys = { self.expandable_keys = {
'camera_settings': None, "camera_settings": None,
'stage_settings': None "stage_settings": None,
} #: Dictionary of keys that can be passed as a file path string and expanded automatically } #: Dictionary of keys that can be passed as a file path string and expanded automatically
# Set arguments # Set arguments
@ -226,10 +204,10 @@ class OpenflexureSettingsFile:
save_config = self.contract_config(config) save_config = self.contract_config(config)
else: else:
save_config = config save_config = config
if backup: if backup:
if os.path.isfile(self.config_path): if os.path.isfile(self.config_path):
shutil.copyfile(self.config_path, self.config_path+".bk") shutil.copyfile(self.config_path, self.config_path + ".bk")
logging.debug("Saving settings dictionary to disk") logging.debug("Saving settings dictionary to disk")
save_yaml_file(self.config_path, save_config) save_yaml_file(self.config_path, save_config)
@ -255,8 +233,7 @@ class OpenflexureSettingsFile:
# For each value in the raw loaded config # For each value in the raw loaded config
for key, value in config_dict.items(): for key, value in config_dict.items():
# If it's a valid expandable parameter # If it's a valid expandable parameter
if (key in self.expandable_keys and if key in self.expandable_keys and type(value) is str:
type(value) is str):
logging.debug("Expanding {}".format(value)) logging.debug("Expanding {}".format(value))
@ -282,9 +259,11 @@ class OpenflexureSettingsFile:
# For each value in the expanded config # For each value in the expanded config
for key, value in config_dict.items(): for key, value in config_dict.items():
# If it's a valid expandable parameter # If it's a valid expandable parameter
if (key in self.expandable_keys and if (
self.expandable_keys[key] is not None and key in self.expandable_keys
type(value) is dict): and self.expandable_keys[key] is not None
and type(value) is dict
):
logging.debug("Saving to {}".format(self.expandable_keys[key])) logging.debug("Saving to {}".format(self.expandable_keys[key]))
# Create the file if it doesn't exist # Create the file if it doesn't exist