-
Notifications
You must be signed in to change notification settings - Fork 19
First pass at #124 #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
YagoDel
wants to merge
22
commits into
master
Choose a base branch
from
issue124
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
First pass at #124 #126
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
a2feb24
Save instrument configuration to YAML following to a set of given pro…
YagoDel f4ed033
Removing hardcoded parameters and moving them to configuration files
YagoDel 0b2a15c
Default Andor parameters
YagoDel 5caed41
Corrected defaults
YagoDel 86181db
Utility PyQt message boxes following pymsgbox
YagoDel fd44e10
Saving config to JSON. Using PyQt for warning message boxes
YagoDel 7911fc9
Default Andor JSON
YagoDel b54b267
Debugged some of the amplifier and channel handling. Removed channel …
YagoDel be95189
Merge branch 'master' of https://github.com/nanophotonics/nplab into …
YagoDel ab56ef0
CameraHandle debug
YagoDel e5fe15e
First pass at integrating old configuration code with HDF5 files with…
YagoDel 31ef641
Debug
YagoDel 7b50dc2
Might need reverting. Removing old config code
YagoDel b80cb99
config_file for ease of use
YagoDel 1b3fcc1
Might need reverting. Updated spectrometer config and tested with Sea…
YagoDel fa7c63e
Debugged update_config. No prompt if file exists already. Handle empt…
YagoDel b894eff
Cleanup
YagoDel e9e7d6d
Merge branch 'master' into issue124
YagoDel 54cbfdb
Andor configuration loading using properties
YagoDel cdfc880
Merge from master
YagoDel 4ca63bb
Default config always points to the correct location
YagoDel ad1fde5
Bug
YagoDel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,34 +12,46 @@ | |
| """ | ||
|
|
||
| from builtins import str | ||
| from nplab.utils.thread_utils import locked_action_decorator, background_action_decorator | ||
| from nplab.utils.thread_utils import locked_action_decorator | ||
| import nplab | ||
| from weakref import WeakSet | ||
| import nplab.utils.log | ||
| from nplab.utils.array_with_attrs import ArrayWithAttrs | ||
| from nplab.utils.show_gui_mixin import ShowGUIMixin | ||
| from nplab.ui.widgets.msgbox import prompt_box | ||
| import logging | ||
| from nplab.utils.log import create_logger | ||
| import inspect | ||
| import os | ||
| import h5py | ||
| import datetime | ||
| # <<<<<<< HEAD | ||
| import json | ||
| import numpy as np | ||
| import tempfile | ||
|
|
||
|
|
||
| # ======= | ||
| from contextlib import contextmanager | ||
| # >>>>>>> 642d2633a1fc31a24d017cb97b8427f0125f9387 | ||
| LOGGER = create_logger('Instrument') | ||
| LOGGER.setLevel('INFO') | ||
|
|
||
|
|
||
| class Instrument(ShowGUIMixin): | ||
| """Base class for all instrument-control classes. | ||
|
|
||
| This class takes care of management of instruments, saving data, etc. | ||
| """ | ||
| __instances = None | ||
| metadata_property_names = () #"Tuple of names of properties that should be automatically saved as HDF5 metadata | ||
| metadata_property_names = () # tuple of names of properties that should be automatically saved as HDF5 metadata | ||
| config_property_names = () # tuple of names of properties that are saved and loaded for default configuration | ||
| _CONFIG_EXTENSION = '.json' | ||
|
|
||
| def __init__(self): | ||
| """Create an instrument object.""" | ||
| super(Instrument, self).__init__() | ||
| Instrument.instances_set().add(self) #keep track of instances (should this be in __new__?) | ||
| Instrument.instances_set().add(self) # keep track of instances (should this be in __new__?) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think using new is necessary |
||
| self._logger = logging.getLogger('Instrument.' + str(type(self)).split('.')[-1].split('\'')[0]) | ||
|
|
||
| @classmethod | ||
|
|
@@ -60,7 +72,7 @@ def get_instance(cls, create=True, exceptions=True, *args, **kwargs): | |
| Usually returns the first available instance. | ||
| """ | ||
| instances = cls.get_instances() | ||
| if len(instances)>0: | ||
| if len(instances) > 0: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return instances[0] | ||
| else: | ||
| if create: | ||
|
|
@@ -99,6 +111,7 @@ def create_dataset(cls, name, flush=True, *args, **kwargs): | |
|
|
||
| :param name: should be a noun describing what the reading is (image, | ||
| spectrum, etc.) | ||
| :param flush: bool | ||
|
|
||
| Other arguments are passed to `nplab.datafile.Group.create_dataset`. | ||
| """ | ||
|
|
@@ -107,17 +120,17 @@ def create_dataset(cls, name, flush=True, *args, **kwargs): | |
| df = cls.get_root_data_folder() | ||
| dset = df.create_dataset(name, *args, **kwargs) | ||
| if 'data' in kwargs and flush: | ||
| dset.file.flush() #make sure it's in the file if we wrote data | ||
| dset.file.flush() # make sure it's in the file if we wrote data | ||
| return dset | ||
|
|
||
| def log(self, message,level = 'info'): | ||
| def log(self, message, level='info'): | ||
| """Save a log message to the current datafile. | ||
|
|
||
| This is the preferred way to output debug/informational messages. They | ||
| will be saved in the current HDF5 file and optionally shown in the | ||
| nplab console. | ||
| """ | ||
| nplab.utils.log.log(message, from_object=self,level = level) | ||
| nplab.utils.log.log(message, from_object=self, level=level) | ||
|
|
||
| def get_metadata(self, | ||
| property_names=[], | ||
|
|
@@ -177,31 +190,190 @@ def bundle_metadata(self, data, enable=True, **kwargs): | |
| else: | ||
| return data | ||
|
|
||
| def open_config_file(self): | ||
| """Open the config file for the current spectrometer and return it, creating if it's not there""" | ||
| if not hasattr(self,'_config_file'): | ||
| def get_config(self, mode='named'): | ||
| """Returns the configuration dictionary | ||
|
|
||
| :param mode: str. Either 'named' or 'all' | ||
| If 'named' it only iterates over self.config_property_names. | ||
| If 'all' it iterates over the whole __dir__, ignoring hidden attributes/methods, and attempts to get the | ||
| values. Currently only returns values if they are one of the following: | ||
| bool, dict, float, int, list, str, tuple, np.array | ||
| :return: | ||
| """ | ||
| configuration = dict() | ||
| if mode == 'named': | ||
| for name in self.config_property_names: | ||
| try: | ||
| configuration[name] = getattr(self, name) | ||
| except Exception as e: | ||
| self._logger.debug('Failed getting configuration for key: %s' % name) | ||
| elif mode == 'all': | ||
| for name in dir(self): | ||
| if not name.startswith('_'): # ignores hidden attributes/methods | ||
| try: | ||
| value = getattr(self, name) | ||
| if type(value) in [bool, dict, float, int, list, str, tuple, np.array]: | ||
| try: | ||
| # check whether value can be saved to JSON. Check is done here rather than in | ||
| # self.save_config because self.save_config dumps the whole config at once | ||
| with tempfile.TemporaryFile('w') as f: | ||
| json.dump(value, f) | ||
| configuration[name] = value | ||
| except Exception as e: | ||
| self._logger.info('Configuration value for key: %s cannot be saved to json' % name) | ||
| except Exception as e: | ||
| self._logger.debug('Failed getting configuration for key: %s' % name) | ||
| else: | ||
| raise ValueError("Unrecognised configuration mode: %s. Needs to be 'named' or 'all'" % mode) | ||
| return configuration | ||
|
|
||
| def set_config(self, configuration): | ||
| """Sets attributes according to configuration | ||
| :param configuration: dict | ||
| :return: | ||
| """ | ||
| for key, value in configuration.items(): | ||
| try: | ||
| setattr(self, key, value) | ||
| except Exception as e: | ||
| self._logger.info('Configuration could not be set for: %s = %s' % (key, value)) | ||
|
|
||
| config = property(get_config, set_config) | ||
|
|
||
| def _config_filename(self, name=None, extension=None): | ||
| """Returns valid file path | ||
|
|
||
| :param name: str. Can be just the filename, a filename with/out an extension, or a relative/absolute path | ||
| :param extension: str | ||
| :return: str. Absolute path | ||
| """ | ||
| if name is None: | ||
| name = 'config' # default name | ||
| if extension is None: | ||
| extension = self._CONFIG_EXTENSION # default extension. Can be changed in subclasses | ||
| # Ensure name has expected extension or adds it if not there | ||
| root, ext = os.path.splitext(name) | ||
| if not ext: | ||
| ext = extension | ||
| else: | ||
| assert ext == extension | ||
| filename = root + ext | ||
|
|
||
| # Default location for configuration file is wherever the instance's Python definition is | ||
| if not os.path.isabs(filename): | ||
| f = inspect.getfile(self.__class__) | ||
| d = os.path.dirname(f) | ||
| self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5')), mode='a') | ||
| self._config_file.attrs['date'] = datetime.datetime.now().strftime("%H:%M %d/%m/%y") | ||
| return self._config_file | ||
|
|
||
| config_file = property(open_config_file) | ||
|
|
||
| def update_config(self, name, data, attrs= None): | ||
| """Update the configuration file for this spectrometer. | ||
|
|
||
| A file is created in the nplab directory that holds configuration | ||
| data for the spectrometer, including reference/background. This | ||
| function allows values to be stored in that file.""" | ||
| f = self.config_file | ||
| if name in f: | ||
| try: del f[name] | ||
| except: | ||
| f[name][...] = data | ||
| f.flush() | ||
| # <<<<<<< HEAD | ||
| filename = os.path.join(d, filename) | ||
| return filename | ||
|
|
||
| def save_config(self, config=None, filename=None, mode='named'): | ||
| """Saves instrument configuration to file | ||
| :param config: dict or None | ||
| :param filename: str. Passed to self._config_filename | ||
| :param mode: str. If config dictionary not given, passed to self.get_config | ||
| """ | ||
| # Get filename | ||
| filename = self._config_filename(filename) | ||
| if config is None: | ||
| config = self.get_config(mode) | ||
|
|
||
| # If the file exists, checks whether the user wants to overwrite it | ||
| if os.path.exists(filename): | ||
| reply = prompt_box(text='That configuration file exists. Do you want to overwrite it?', default=filename) | ||
| if not reply: | ||
| return | ||
| filename = reply | ||
|
|
||
| _, ext = os.path.splitext(filename) | ||
| if ext == '.json': | ||
| # Dumps the configuration dictionary to a JSON | ||
| with open(filename, 'w') as config_file: | ||
| json.dump(config, config_file, indent=4) | ||
| elif ext == '.h5': | ||
| with h5py.File(filename, 'w') as dfile: | ||
| dfile.attrs['date'] = datetime.datetime.now().strftime("%H:%M %d/%m/%y") | ||
| for name, value in config.items(): | ||
| try: | ||
| dfile.create_dataset(name, data=value) | ||
| except Exception as e: | ||
| self._logger.info('Configuration value for key: %s cannot be saved to HDF5' % name) | ||
| else: | ||
| raise ValueError('Unrecognised extension: %s' % ext) | ||
|
|
||
| def load_config(self, filename=None): | ||
| """Loads configuration from file | ||
| :param filename: str. Passed to self._config_filename | ||
| :return: dict | ||
| """ | ||
| # Get filename | ||
| filename = self._config_filename(filename) | ||
| _, ext = os.path.splitext(filename) | ||
|
|
||
| # Loads and sets the configuration | ||
| if ext == '.json': | ||
| with open(filename, 'r') as config_file: | ||
| config = json.load(config_file) | ||
| elif ext == '.h5': | ||
| with h5py.File(filename, 'r') as dfile: | ||
| config = dict() | ||
| for key, value in dfile.items(): | ||
| config[key] = value | ||
| else: | ||
| raise ValueError('Unrecognised extension: %s' % ext) | ||
| return config | ||
|
|
||
| config_file = property(load_config, save_config) | ||
|
|
||
| def update_config(self, name, data, filename=None): | ||
| """Edits configuration file | ||
| :param name: str | ||
| :param data: anything that can be parsed by JSON or directly saved to HDF5 | ||
| :param filename: str. Passed to self._config_filename | ||
| """ | ||
| # Get filename | ||
| filename = self._config_filename(filename) | ||
|
|
||
| _, ext = os.path.splitext(filename) | ||
| if ext == '.json': | ||
| # Need to read the whole JSON first, modify it, and then re-write the file | ||
| with open(filename, 'a') as config_file: | ||
| try: | ||
| config = json.load(config_file) | ||
| except: # would fail if config_file is empty | ||
| config = dict() | ||
| config[name] = data | ||
| config_file.seek(0) | ||
| json.dump(config, config_file) | ||
| config_file.truncate() | ||
| elif ext == '.h5': | ||
| with h5py.File(filename, 'a') as f: | ||
| if name in f: | ||
| del f[name] | ||
| f.create_dataset(name, data=data) | ||
| else: | ||
| f.create_dataset(name, data=data ,attrs = attrs) | ||
| raise ValueError('Unrecognised extension: %s' % ext) | ||
| # ======= | ||
| # self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5')), mode='a') | ||
| # self._config_file.attrs['date'] = datetime.datetime.now().strftime("%H:%M %d/%m/%y") | ||
| # return self._config_file | ||
| # | ||
| # config_file = property(open_config_file) | ||
| # | ||
| # def update_config(self, name, data, attrs= None): | ||
| # """Update the configuration file for this spectrometer. | ||
| # | ||
| # A file is created in the nplab directory that holds configuration | ||
| # data for the spectrometer, including reference/background. This | ||
| # function allows values to be stored in that file.""" | ||
| # f = self.config_file | ||
| # if name in f: | ||
| # try: del f[name] | ||
| # except: | ||
| # f[name][...] = data | ||
| # f.flush() | ||
| # else: | ||
| # f.create_dataset(name, data=data ,attrs = attrs) | ||
|
|
||
| @contextmanager | ||
| def temporarily_set(self, **kwargs): | ||
|
|
@@ -223,3 +395,4 @@ def temporarily_set(self, **kwargs): | |
| finally: | ||
| for key, value in original_settings.items(): | ||
| setattr(self, key, value) | ||
| # >>>>>>> 642d2633a1fc31a24d017cb97b8427f0125f9387 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locked_action and background_action are nicer aliases IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could agree, not an issue for this branch :P (it's been like that since the start of nplab)