From a2feb2452a749475c757e337404487aa18544408 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Thu, 15 Oct 2020 19:36:01 +0900 Subject: [PATCH 01/19] Save instrument configuration to YAML following to a set of given property names --- nplab/instrument/__init__.py | 87 ++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index 40901af1..1853fdfd 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -24,6 +24,8 @@ import os import h5py import datetime +import yaml +import pymsgbox LOGGER = create_logger('Instrument') LOGGER.setLevel('INFO') @@ -34,6 +36,7 @@ class Instrument(ShowGUIMixin): """ __instances = None metadata_property_names = () #"Tuple of names of properties that should be automatically saved as HDF5 metadata + config_property_names = () def __init__(self): """Create an instrument object.""" @@ -178,16 +181,16 @@ def bundle_metadata(self, data, enable=True, **kwargs): 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'): + if not hasattr(self, '_config_file'): f = inspect.getfile(self.__class__) d = os.path.dirname(f) - self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5'))) + self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5'), '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): + 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 @@ -200,4 +203,80 @@ def update_config(self, name, data, attrs= None): f[name][...] = data f.flush() else: - f.create_dataset(name, data=data ,attrs = attrs) \ No newline at end of file + f.create_dataset(name, data=data ,attrs = attrs) + + @property + def config(self): + """Configuration dictionary + Iterates over self.config_property_names and gets the property values + + :return: dictionary + """ + configuration = dict() + for name in self.config_property_names: + configuration[name] = getattr(self, name) + return configuration + + @config.setter + def config(self, configuration): + for key, value in configuration.items(): + setattr(self, key, value) + + def _config_filename(self, name=None, extension='.yaml'): + """Utility function + + Ensures name is a yaml path, and if it's not an absolute path, it points it to the location of the Python file + for the current class + + :param name: string. Can be just the filename, a filename with an extension, or a relative/absolute path + :return: + """ + if name is None: + name = 'config' # default name + # Ensure name has expected extension + root, ext = os.path.splitext(name) + if not ext: + ext = extension + else: + assert ext == extension + filename = root + ext + + # Default location for YAML is wherever the instance's Python definition is + if not os.path.isabs(filename): + f = inspect.getfile(self.__class__) + d = os.path.dirname(f) + filename = os.path.join(d, filename) + return filename + + def save_config(self, filename=None): + """Saves instrument configuration to YAML + :param filename: string + """ + # Get filename + filename = self._config_filename(filename) + + # If the file exists, checks whether the user wants to overwrite it + if os.path.exists(filename): + reply = pymsgbox.prompt(text='That configuration file exists. Do you want to overwrite it?', + title='', default=filename) + if reply is not None: + filename = reply + else: + return + + # Dumps the configuration dictionary to a yaml + with open(filename, 'w') as config_file: + yaml.dump(self.config, config_file) + + def load_config(self, filename=None): + """Loads configuration from YAML + :param filename: string + :return: + """ + # Get filename + filename = self._config_filename(filename) + + # Loads and sets the configuration + with open(filename, 'r') as config_file: + config = yaml.load(config_file, Loader=yaml.FullLoader) + self.config = config From f4ed033d186374d884aefdaf6ac397bcf8973d11 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Thu, 15 Oct 2020 19:39:37 +0900 Subject: [PATCH 02/19] Removing hardcoded parameters and moving them to configuration files --- nplab/instrument/camera/Andor/__init__.py | 7 ++++++- nplab/instrument/camera/Andor/andor_sdk.py | 9 --------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/nplab/instrument/camera/Andor/__init__.py b/nplab/instrument/camera/Andor/__init__.py index d859ed4e..3eee5552 100644 --- a/nplab/instrument/camera/Andor/__init__.py +++ b/nplab/instrument/camera/Andor/__init__.py @@ -16,8 +16,11 @@ class Andor(CameraRoiScale, AndorBase): metadata_property_names = ('Exposure', 'x_axis', 'CurrentTemperature',) + config_property_names = CameraRoiScale.config_property_names + ('ReadMode', 'AcquisitionMode', 'TriggerMode', + 'Exposure', 'Shutter', 'SetTemperature', + 'CoolerMode', 'FanMode', 'cooler') - def __init__(self, settings_filepath=None, camera_index=None, **kwargs): + def __init__(self, settings_filepath=None, camera_index=None, config_file=None, **kwargs): super(Andor, self).__init__() self.start(camera_index) @@ -30,6 +33,8 @@ def __init__(self, settings_filepath=None, camera_index=None, **kwargs): self.isAborted = False self.detector_shape = self.DetectorShape # passing the Andor parameter to the CameraRoiScale class + if config_file is not None: + self.load_config(config_file) def __del__(self): # Need to explicitly call this method so that the shutdown procedure is followed correctly diff --git a/nplab/instrument/camera/Andor/andor_sdk.py b/nplab/instrument/camera/Andor/andor_sdk.py index e7bda98d..aa2eb810 100644 --- a/nplab/instrument/camera/Andor/andor_sdk.py +++ b/nplab/instrument/camera/Andor/andor_sdk.py @@ -451,21 +451,12 @@ def initialize(self): """Sets the initial parameters for the Andor typical for our experiments""" self._dll_wrapper('Initialize', outputs=(c_char(),)) self.channel = 0 - self.set_andor_parameter('ReadMode', 4) - self.set_andor_parameter('AcquisitionMode', 1) - self.set_andor_parameter('TriggerMode', 0) - self.set_andor_parameter('Exposure', 0.01) detector_shape = self.get_andor_parameter('DetectorShape') self.set_andor_parameter('Image', 1, 1, 1, detector_shape[0], 1, detector_shape[1]) - self.set_andor_parameter('Shutter', 1, 0, 1, 1) - self.set_andor_parameter('SetTemperature', -90) - self.set_andor_parameter('CoolerMode', 0) - self.set_andor_parameter('FanMode', 0) try: self.set_andor_parameter('OutAmp', 1) # This means EMCCD off - this is the default mode except AndorWarning: self.set_andor_parameter('OutAmp', 0) - self.cooler = 1 @locked_action def capture(self): From 0b2a15ceeacfa34d3979a34f09340bf3f8864a72 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Fri, 16 Oct 2020 10:05:35 +0900 Subject: [PATCH 03/19] Default Andor parameters --- nplab/instrument/camera/Andor/__init__.py | 2 ++ nplab/instrument/camera/Andor/default_config.yaml | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 nplab/instrument/camera/Andor/default_config.yaml diff --git a/nplab/instrument/camera/Andor/__init__.py b/nplab/instrument/camera/Andor/__init__.py index 3eee5552..84954665 100644 --- a/nplab/instrument/camera/Andor/__init__.py +++ b/nplab/instrument/camera/Andor/__init__.py @@ -35,6 +35,8 @@ def __init__(self, settings_filepath=None, camera_index=None, config_file=None, self.detector_shape = self.DetectorShape # passing the Andor parameter to the CameraRoiScale class if config_file is not None: self.load_config(config_file) + else: + self.load_config('default_config') def __del__(self): # Need to explicitly call this method so that the shutdown procedure is followed correctly diff --git a/nplab/instrument/camera/Andor/default_config.yaml b/nplab/instrument/camera/Andor/default_config.yaml new file mode 100644 index 00000000..d7a35a1d --- /dev/null +++ b/nplab/instrument/camera/Andor/default_config.yaml @@ -0,0 +1,14 @@ +AcquisitionMode: 1 +CoolerMode: 0 +Exposure: 0.01 +FanMode: 0 +ReadMode: 4 +SetTemperature: -90 +Shutter: !!python/tuple +- 1 +- 0 +- 50 +- 50 +TriggerMode: 0 +cooler: 0 +channel: 0 From 5caed41e3bfcfe5fa8462186ef7afebc2cf21cc6 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Fri, 16 Oct 2020 10:06:35 +0900 Subject: [PATCH 04/19] Corrected defaults --- nplab/instrument/camera/Andor/default_config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nplab/instrument/camera/Andor/default_config.yaml b/nplab/instrument/camera/Andor/default_config.yaml index d7a35a1d..9fc876a1 100644 --- a/nplab/instrument/camera/Andor/default_config.yaml +++ b/nplab/instrument/camera/Andor/default_config.yaml @@ -7,8 +7,8 @@ SetTemperature: -90 Shutter: !!python/tuple - 1 - 0 -- 50 -- 50 +- 1 +- 1 TriggerMode: 0 cooler: 0 channel: 0 From 86181db96ec43d1a971a715cbbda9683c9385ba4 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Thu, 22 Oct 2020 16:59:01 +0900 Subject: [PATCH 05/19] Utility PyQt message boxes following pymsgbox --- nplab/ui/widgets/msgbox.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 nplab/ui/widgets/msgbox.py diff --git a/nplab/ui/widgets/msgbox.py b/nplab/ui/widgets/msgbox.py new file mode 100644 index 00000000..760f82ce --- /dev/null +++ b/nplab/ui/widgets/msgbox.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from nplab.utils.gui import QtWidgets, get_qt_app +from functools import reduce +import numpy as np + + +def button_box(text="Message box pop up window", title="NpLab button_box", buttons=('Ok', 'Cancel')): + app = get_qt_app() + msgBox = QtWidgets.QMessageBox() + msgBox.setIcon(QtWidgets.QMessageBox.Information) + msgBox.setText(text) + msgBox.setWindowTitle(title) + bttns = [getattr(QtWidgets.QMessageBox, b) for b in buttons] + msgBox.setStandardButtons(reduce(lambda x, y: x | y, bttns)) + + return_button = msgBox.exec() + button_index = np.argwhere(return_button == np.array(bttns))[0][0] + return button_index + + +def prompt_box(text='Enter text:', title="NpLab prompt_box", default='', widget=None, ): + app = get_qt_app() + if widget is None: + widget = QtWidgets.QWidget() + text, ok = QtWidgets.QInputDialog.getText(widget, title, text, text=default) + if ok: + return text + else: + return ok From fd44e109e569d601f4ac51083a6ab3508685829d Mon Sep 17 00:00:00 2001 From: YagoDel Date: Thu, 22 Oct 2020 17:00:26 +0900 Subject: [PATCH 06/19] Saving config to JSON. Using PyQt for warning message boxes --- nplab/instrument/__init__.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index 1853fdfd..bb0e2cce 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -18,14 +18,14 @@ 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 -import yaml -import pymsgbox +import json LOGGER = create_logger('Instrument') LOGGER.setLevel('INFO') @@ -222,10 +222,10 @@ def config(self, configuration): for key, value in configuration.items(): setattr(self, key, value) - def _config_filename(self, name=None, extension='.yaml'): + def _config_filename(self, name=None, extension='.json'): """Utility function - Ensures name is a yaml path, and if it's not an absolute path, it points it to the location of the Python file + Ensures name is a JSON path, and if it's not an absolute path, it points it to the location of the Python file for the current class :param name: string. Can be just the filename, a filename with an extension, or a relative/absolute path @@ -241,7 +241,7 @@ def _config_filename(self, name=None, extension='.yaml'): assert ext == extension filename = root + ext - # Default location for YAML is wherever the instance's Python definition is + # Default location for JSON is wherever the instance's Python definition is if not os.path.isabs(filename): f = inspect.getfile(self.__class__) d = os.path.dirname(f) @@ -249,7 +249,7 @@ def _config_filename(self, name=None, extension='.yaml'): return filename def save_config(self, filename=None): - """Saves instrument configuration to YAML + """Saves instrument configuration to JSON :param filename: string """ # Get filename @@ -257,19 +257,17 @@ def save_config(self, filename=None): # If the file exists, checks whether the user wants to overwrite it if os.path.exists(filename): - reply = pymsgbox.prompt(text='That configuration file exists. Do you want to overwrite it?', - title='', default=filename) - if reply is not None: - filename = reply - else: + reply = prompt_box(text='That configuration file exists. Do you want to overwrite it?', default=filename) + if not reply: return + filename = reply - # Dumps the configuration dictionary to a yaml + # Dumps the configuration dictionary to a JSON with open(filename, 'w') as config_file: - yaml.dump(self.config, config_file) + json.dump(self.config, config_file) def load_config(self, filename=None): - """Loads configuration from YAML + """Loads configuration from JSON :param filename: string :return: """ @@ -278,5 +276,5 @@ def load_config(self, filename=None): # Loads and sets the configuration with open(filename, 'r') as config_file: - config = yaml.load(config_file, Loader=yaml.FullLoader) + config = json.load(config_file) self.config = config From 7911fc973a77951d0243fd3686fc882decf89de4 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Fri, 23 Oct 2020 09:43:15 +0900 Subject: [PATCH 07/19] Default Andor JSON --- nplab/instrument/camera/Andor/default_config.json | 1 + nplab/instrument/camera/Andor/default_config.yaml | 14 -------------- 2 files changed, 1 insertion(+), 14 deletions(-) create mode 100644 nplab/instrument/camera/Andor/default_config.json delete mode 100644 nplab/instrument/camera/Andor/default_config.yaml diff --git a/nplab/instrument/camera/Andor/default_config.json b/nplab/instrument/camera/Andor/default_config.json new file mode 100644 index 00000000..afbf5550 --- /dev/null +++ b/nplab/instrument/camera/Andor/default_config.json @@ -0,0 +1 @@ +{"ReadMode": 4, "AcquisitionMode": 1, "TriggerMode": 0, "Exposure": 0.01, "Shutter": [1, 0, 1, 1], "SetTemperature": -90, "CoolerMode": 0, "FanMode": 0, "cooler": 0} \ No newline at end of file diff --git a/nplab/instrument/camera/Andor/default_config.yaml b/nplab/instrument/camera/Andor/default_config.yaml deleted file mode 100644 index 9fc876a1..00000000 --- a/nplab/instrument/camera/Andor/default_config.yaml +++ /dev/null @@ -1,14 +0,0 @@ -AcquisitionMode: 1 -CoolerMode: 0 -Exposure: 0.01 -FanMode: 0 -ReadMode: 4 -SetTemperature: -90 -Shutter: !!python/tuple -- 1 -- 0 -- 1 -- 1 -TriggerMode: 0 -cooler: 0 -channel: 0 From b54b2671b48c150caf911d09ed3e72cddd3f312e Mon Sep 17 00:00:00 2001 From: YagoDel Date: Fri, 23 Oct 2020 10:39:22 +0900 Subject: [PATCH 08/19] Debugged some of the amplifier and channel handling. Removed channel attribute because it was a hack --- nplab/instrument/camera/Andor/andor_sdk.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/nplab/instrument/camera/Andor/andor_sdk.py b/nplab/instrument/camera/Andor/andor_sdk.py index aa2eb810..cbda2351 100644 --- a/nplab/instrument/camera/Andor/andor_sdk.py +++ b/nplab/instrument/camera/Andor/andor_sdk.py @@ -170,6 +170,7 @@ def _dll_wrapper(self, funcname, inputs=(), outputs=(), reverse=False): :return: """ + self._logger.debug('DLL call: %s, %s, %s' % (funcname, inputs, outputs)) self._set_dll_camera() dll_input = () @@ -450,13 +451,15 @@ def abort(self): def initialize(self): """Sets the initial parameters for the Andor typical for our experiments""" self._dll_wrapper('Initialize', outputs=(c_char(),)) - self.channel = 0 detector_shape = self.get_andor_parameter('DetectorShape') self.set_andor_parameter('Image', 1, 1, 1, detector_shape[0], 1, detector_shape[1]) - try: - self.set_andor_parameter('OutAmp', 1) # This means EMCCD off - this is the default mode - except AndorWarning: + if self.get_andor_parameter('NumAmp') > 1: + self.set_andor_parameter('OutAmp', 1) # This means EMCCD off - which is default just for safety + else: self.set_andor_parameter('OutAmp', 0) + if self.get_andor_parameter('NumADChannels') > 1: + self._logger.info('Andor has more than one ADC channel. Default settings is 0') + self.set_andor_parameter('ADChannel', 0) @locked_action def capture(self): @@ -647,7 +650,6 @@ def load_params_from_file(self, filepath=None): CurrentCamera=dict(Get=dict(cmdName='GetCurrentCamera', Outputs=(c_uint,)), Set=dict(cmdName='SetCurrentCamera', Inputs=(c_uint,))), CameraHandle=dict(Get=dict(cmdName='GetCameraHandle', Outputs=(c_uint,), Inputs=(c_uint, ))), - channel=dict(value=0), PixelSize=dict(Get=dict(cmdName='GetPixelSize', Outputs=(c_float, c_float))), SoftwareWaitBetweenCaptures=dict(value=0), SoftwareVersion=dict(Get=dict(cmdName='GetSoftwareVersion', Outputs=(c_int, c_int, c_int, c_int, c_int, c_int))), @@ -692,20 +694,21 @@ def load_params_from_file(self, filepath=None): Spool=dict(Set=dict(cmdName='SetSpool', Inputs=(c_int, c_int, c_char, c_int)), value=None), NumVSSpeed=dict(Get=dict(cmdName='GetNumberVSSpeeds', Outputs=(c_int,)), value=None), NumHSSpeed=dict(Get=dict(cmdName='GetNumberHSSpeeds', Outputs=(c_int,), - Input_params=(('channel', c_int), ('OutAmp', c_int))), value=None), + Input_params=(('ADChannel', c_int), ('OutAmp', c_int))), value=None), VSSpeed=dict(Set=dict(cmdName='SetVSSpeed', Inputs=(c_int,)), Get_from_prop='VSSpeeds'), VSSpeeds=dict(Get=dict(cmdName='GetVSSpeed', Inputs=(c_int,), Outputs=(c_float,), Iterator='NumVSSpeed')), # why no work? HSSpeed=dict(Set=dict(cmdName='SetHSSpeed', Inputs=(c_int,), Input_params=(('OutAmp', c_int),)), Get_from_prop='HSSpeeds'), HSSpeeds=dict(Get=dict(cmdName='GetHSSpeed', Inputs=(c_int,) * 2, Iterator='NumHSSpeed', Outputs=(c_float,), - Input_params=(('channel', c_int), ('OutAmp', c_int),))), + Input_params=(('ADChannel', c_int), ('OutAmp', c_int),))), NumPreAmp=dict(Get=dict(cmdName='GetNumberPreAmpGains', Outputs=(c_int,))), PreAmpGains=dict(Get=dict(cmdName='GetPreAmpGain', Inputs=(c_int,), Outputs=(c_float,), Iterator='NumPreAmp')), PreAmpGain=dict(Set=dict(cmdName='SetPreAmpGain', Inputs=(c_int,)), Get_from_prop='PreAmpGains'), NumADChannels=dict(Get=dict(cmdName='GetNumberADChannels', Outputs=(c_int,))), ADChannel=dict(Set=dict(cmdName='SetADChannel', Inputs=(c_int,))), - BitDepth=dict(Get=dict(cmdName='GetBitDepth', Inputs=(c_int,), Outputs=(c_int,), Iterator='NumADChannels')) + BitDepth=dict(Get=dict(cmdName='GetBitDepth', Inputs=(c_int,), Outputs=(c_int,), Iterator='NumADChannels')), + NumAmp=dict(Get=dict(cmdName='GetNumberAmp', Outputs=(c_int,))) ) for param_name in parameters: if param_name != 'Image': From ab56ef0608f7e045608638891df70a543dd443e7 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 11:01:17 +0900 Subject: [PATCH 09/19] CameraHandle debug --- nplab/instrument/camera/Andor/andor_sdk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nplab/instrument/camera/Andor/andor_sdk.py b/nplab/instrument/camera/Andor/andor_sdk.py index 43291269..5b48d2f9 100644 --- a/nplab/instrument/camera/Andor/andor_sdk.py +++ b/nplab/instrument/camera/Andor/andor_sdk.py @@ -132,7 +132,7 @@ def start(self, camera_index=None): if self.get_andor_parameter('AvailableCameras') > 1: if camera_index is None: self._logger.warn('More than one camera available, but no index provided. Initializing camera 0') - camera_handle = self.get_andor_parameter('CameraHandle', self.camera_index) + camera_handle = self.get_andor_parameter('CameraHandle') self.set_andor_parameter('CurrentCamera', camera_handle) self.initialize() @@ -657,7 +657,7 @@ def load_params_from_file(self, filepath=None): AvailableCameras=dict(Get=dict(cmdName='GetAvailableCameras', Outputs=(c_uint,)), value=None), CurrentCamera=dict(Get=dict(cmdName='GetCurrentCamera', Outputs=(c_uint,)), Set=dict(cmdName='SetCurrentCamera', Inputs=(c_uint,))), - CameraHandle=dict(Get=dict(cmdName='GetCameraHandle', Outputs=(c_uint,), Inputs=(c_uint, ))), + CameraHandle=dict(Get=dict(cmdName='GetCameraHandle', Outputs=(c_uint,), Input_params=(('camera_index', c_int),))), PixelSize=dict(Get=dict(cmdName='GetPixelSize', Outputs=(c_float, c_float))), SoftwareWaitBetweenCaptures=dict(value=0), SoftwareVersion=dict(Get=dict(cmdName='GetSoftwareVersion', Outputs=(c_int, c_int, c_int, c_int, c_int, c_int))), From e5fe15ea32e325e0bdbc684cca6054c25ddd8924 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 11:07:44 +0900 Subject: [PATCH 10/19] First pass at integrating old configuration code with HDF5 files with new code with JSON. First step in moving away from named configuration settings --- nplab/instrument/__init__.py | 159 ++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 41 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index bb0e2cce..dc40cb5a 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -26,6 +26,10 @@ import h5py import datetime import json +import numpy as np +import tempfile + + LOGGER = create_logger('Instrument') LOGGER.setLevel('INFO') @@ -37,6 +41,7 @@ class Instrument(ShowGUIMixin): __instances = None metadata_property_names = () #"Tuple of names of properties that should be automatically saved as HDF5 metadata config_property_names = () + _CONFIG_EXTENSION = '.json' def __init__(self): """Create an instrument object.""" @@ -179,50 +184,70 @@ 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'): - f = inspect.getfile(self.__class__) - d = os.path.dirname(f) - self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5'), '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) - - @property - def config(self): + # 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'): + # f = inspect.getfile(self.__class__) + # d = os.path.dirname(f) + # self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5'), '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) + + def get_config(self, mode='named'): """Configuration dictionary Iterates over self.config_property_names and gets the property values :return: dictionary """ configuration = dict() - for name in self.config_property_names: - configuration[name] = getattr(self, name) + if mode == 'named': + for name in self.config_property_names: + configuration[name] = getattr(self, name) + elif mode == 'all': + for name in dir(self): + if not name.startswith('_'): + try: + value = getattr(self, name) + if type(value) in [bool, dict, float, int, list, str, tuple, np.array]: + try: + with tempfile.TemporaryFile('w') as f: # this check is done here rather than in self.save_config because we dump the whole configuration in self.save_config + 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 - @config.setter - def config(self, configuration): + def set_config(self, configuration): for key, value in configuration.items(): - setattr(self, key, value) + 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='.json'): + def _config_filename(self, name=None, extension=None): """Utility function Ensures name is a JSON path, and if it's not an absolute path, it points it to the location of the Python file @@ -233,6 +258,8 @@ def _config_filename(self, name=None, extension='.json'): """ if name is None: name = 'config' # default name + if extension is None: + extension = self._CONFIG_EXTENSION # Ensure name has expected extension root, ext = os.path.splitext(name) if not ext: @@ -241,19 +268,20 @@ def _config_filename(self, name=None, extension='.json'): assert ext == extension filename = root + ext - # Default location for JSON is wherever the instance's Python definition is + # 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) filename = os.path.join(d, filename) return filename - def save_config(self, filename=None): + def save_config(self, filename=None, mode='named'): """Saves instrument configuration to JSON :param filename: string """ # Get filename filename = self._config_filename(filename) + config = self.get_config(mode) # If the file exists, checks whether the user wants to overwrite it if os.path.exists(filename): @@ -262,9 +290,21 @@ def save_config(self, filename=None): return filename = reply - # Dumps the configuration dictionary to a JSON - with open(filename, 'w') as config_file: - json.dump(self.config, config_file) + _, 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 JSON @@ -273,8 +313,45 @@ def load_config(self, filename=None): """ # Get filename filename = self._config_filename(filename) + _, ext = os.path.splitext(filename) # Loads and sets the configuration - with open(filename, 'r') as config_file: - config = json.load(config_file) + 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) self.config = config + + def update_config(self, name, data, filename=None): + # Get filename + filename = self._config_filename(filename) + + # 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, 'a') as config_file: + config = json.load(config_file) + 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: + raise ValueError('Unrecognised extension: %s' % ext) From 31ef641a6e4f460f6505becdacadca75417d790c Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 11:08:07 +0900 Subject: [PATCH 11/19] Debug --- nplab/ui/widgets/msgbox.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/nplab/ui/widgets/msgbox.py b/nplab/ui/widgets/msgbox.py index 760f82ce..3e36a03b 100644 --- a/nplab/ui/widgets/msgbox.py +++ b/nplab/ui/widgets/msgbox.py @@ -22,9 +22,20 @@ def button_box(text="Message box pop up window", title="NpLab button_box", butto def prompt_box(text='Enter text:', title="NpLab prompt_box", default='', widget=None, ): app = get_qt_app() if widget is None: - widget = QtWidgets.QWidget() - text, ok = QtWidgets.QInputDialog.getText(widget, title, text, text=default) - if ok: - return text + widget = QtWidgets.QInputDialog() + widget.setInputMode(QtWidgets.QInputDialog.TextInput) + widget.setWindowTitle(title) + widget.setLabelText(text) + widget.setTextValue(default) + if widget.exec_() == QtWidgets.QDialog.Accepted: + returnValue = widget.textValue() + else: + returnValue = False + widget.deleteLater() + return returnValue else: - return ok + text, ok = QtWidgets.QInputDialog().getText(widget, title, text, text=default) + if ok: + return text + else: + return ok From 7b50dc23c4c01f4f200e0085f05146e504397be5 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 11:21:28 +0900 Subject: [PATCH 12/19] Might need reverting. Removing old config code --- nplab/instrument/ccd/__init__.py | 31 ++----------------------------- nplab/instrument/ccd/pixis.py | 2 ++ 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/nplab/instrument/ccd/__init__.py b/nplab/instrument/ccd/__init__.py index e146dbbd..12627e47 100644 --- a/nplab/instrument/ccd/__init__.py +++ b/nplab/instrument/ccd/__init__.py @@ -12,45 +12,18 @@ class CCD(Instrument): + _CONFIG_EXTENSION = '.h5' + def __init__(self): super(CCD, self).__init__() self._wavelengths = None self.reference = None self.background = None - self._config_file = None self.latest_image = None - def __del__(self): - try: - self._config_file.close() - except AttributeError: - pass # if it's not present, we get an exception - which doesn't matter. - def read_image(self): raise NotImplementedError - @property - def config_file(self): - """ - Open the config file for the current spectrometer and return it, creating if it's not - there. - """ - if self._config_file is None: - f = inspect.getfile(self.__class__) - d = os.path.dirname(f) - self._config_file = h5py.File(os.path.join(d, 'config.h5')) - self._config_file.attrs['date'] = datetime.datetime.now().strftime("%H:%M %d/%m/%y") - return self._config_file - - def update_config(self, name, data): - f = self.config_file - if name not in f: - f.create_dataset(name, data=data) - else: - dset = f[name] - dset[:] = data - f.flush() - def read_background(self): """Acquire a new spectrum and use it as a background measurement.""" self.background = self.read_image() diff --git a/nplab/instrument/ccd/pixis.py b/nplab/instrument/ccd/pixis.py index 59ae54b0..d9d61964 100755 --- a/nplab/instrument/ccd/pixis.py +++ b/nplab/instrument/ccd/pixis.py @@ -29,6 +29,8 @@ def __init__(self, msg): class Pixis256E(CCD,Camera): + _CONFIG_EXTENSION = '.h5' + def __init__(self): super(Pixis256E, self).__init__() try: From b80cb99676e3440b1e18e9e9fb9fd2bf7170e8a7 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 12:50:13 +0900 Subject: [PATCH 13/19] config_file for ease of use --- nplab/instrument/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index dc40cb5a..ed91fade 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -275,13 +275,17 @@ def _config_filename(self, name=None, extension=None): filename = os.path.join(d, filename) return filename - def save_config(self, filename=None, mode='named'): - """Saves instrument configuration to JSON - :param filename: string + def save_config(self, config=None, filename=None, mode='named'): + """Saves instrument configuration + :param config: + :param filename: + :param mode: + :return: """ # Get filename filename = self._config_filename(filename) - config = self.get_config(mode) + 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): @@ -326,7 +330,9 @@ def load_config(self, filename=None): config[key] = value else: raise ValueError('Unrecognised extension: %s' % ext) - self.config = config + return config + + config_file = property(load_config, save_config) def update_config(self, name, data, filename=None): # Get filename From 1b3fcc12e741059b19ab06ae7cb6cb1fce51db04 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 13:23:25 +0900 Subject: [PATCH 14/19] Might need reverting. Updated spectrometer config and tested with Seabreeze --- nplab/instrument/spectrometer/__init__.py | 34 +--------------------- nplab/instrument/spectrometer/seabreeze.py | 14 ++++----- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/nplab/instrument/spectrometer/__init__.py b/nplab/instrument/spectrometer/__init__.py index f9cc6bfe..6558d852 100644 --- a/nplab/instrument/spectrometer/__init__.py +++ b/nplab/instrument/spectrometer/__init__.py @@ -42,6 +42,7 @@ class Spectrometer(Instrument): variable_int_enabled = DumbNotifiedProperty(False) filename = DumbNotifiedProperty("spectrum") + _CONFIG_EXTENSION = '.h5' def __init__(self): super(Spectrometer, self).__init__() @@ -60,7 +61,6 @@ def __init__(self): self.averaging_enabled = False self.spectra_deque = deque(maxlen = 1) self.absorption_enabled = False - self._config_file = None self.stored_references = {} self.stored_backgrounds = {} @@ -72,38 +72,6 @@ def __init__(self): self.delay = 0 self.time_series_name = 'time_series_%d' - - def __del__(self): - try: - self._config_file.close() - except AttributeError: - pass #if it's not present, we get an exception - which doesn't matter. - - def open_config_file(self): - """Open the config file for the current spectrometer and return it, creating if it's not there""" - if self._config_file is None: - f = inspect.getfile(self.__class__) - d = os.path.dirname(f) - self._config_file = DataFile(h5py.File(os.path.join(d, 'config.h5'))) - 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 not in f: - f.create_dataset(name, data=data ,attrs = attrs) - else: - dset = f[name] - dset[...] = data - f.flush() - def get_model_name(self): """The model name of the spectrometer.""" if self._model_name is None: diff --git a/nplab/instrument/spectrometer/seabreeze.py b/nplab/instrument/spectrometer/seabreeze.py index 6a9ee9d1..602f834e 100644 --- a/nplab/instrument/spectrometer/seabreeze.py +++ b/nplab/instrument/spectrometer/seabreeze.py @@ -203,15 +203,11 @@ def _close(self, force=False): check_error(e) self._isOpen = False - def open_config_file(self): - if self._config_file is None: - f = inspect.getfile(self.__class__) - d = os.path.dirname(f) - self._config_file = DataFile(h5py.File(os.path.join(d, self.model_name+'_'+self.serial_number+'_config.h5'))) - 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 _config_filename(self, name=None, extension=None): + if name is None: + name = self.model_name+'_'+self.serial_number+'_config' + return super(OceanOpticsSpectrometer, self)._config_filename(name, extension) + def get_API_version(self): N = 32 # make a buffer for the DLL to return a string into s = ctypes.create_string_buffer(N) From fa7c63e96608afa8ec4da70b019c8aa76861d9cd Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 13:27:26 +0900 Subject: [PATCH 15/19] Debugged update_config. No prompt if file exists already. Handle empty files --- nplab/instrument/__init__.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index ed91fade..5203dd49 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -338,18 +338,14 @@ def update_config(self, name, data, filename=None): # Get filename filename = self._config_filename(filename) - # 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, 'a') as config_file: - config = json.load(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) From b894efff24d2185c7e84fadbdb2bb5ff9a882ba8 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 28 Oct 2020 13:53:46 +0900 Subject: [PATCH 16/19] Cleanup --- nplab/instrument/__init__.py | 105 ++++++++++++++++------------------- 1 file changed, 48 insertions(+), 57 deletions(-) diff --git a/nplab/instrument/__init__.py b/nplab/instrument/__init__.py index 5203dd49..40c5a2b5 100644 --- a/nplab/instrument/__init__.py +++ b/nplab/instrument/__init__.py @@ -12,7 +12,7 @@ """ 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 @@ -33,20 +33,21 @@ 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 - config_property_names = () + 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__?) self._logger = logging.getLogger('Instrument.' + str(type(self)).split('.')[-1].split('\'')[0]) @classmethod @@ -67,7 +68,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: return instances[0] else: if create: @@ -106,6 +107,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`. """ @@ -114,17 +116,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=[], @@ -184,50 +186,33 @@ 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'): - # f = inspect.getfile(self.__class__) - # d = os.path.dirname(f) - # self._config_file = nplab.datafile.DataFile(h5py.File(os.path.join(d, 'config.h5'), '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) - def get_config(self, mode='named'): - """Configuration dictionary - Iterates over self.config_property_names and gets the property values + """Returns the configuration dictionary - :return: 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: - configuration[name] = getattr(self, name) + 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('_'): + 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: - with tempfile.TemporaryFile('w') as f: # this check is done here rather than in self.save_config because we dump the whole configuration in self.save_config + # 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: @@ -239,6 +224,10 @@ def get_config(self, mode='named'): 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) @@ -248,19 +237,17 @@ def set_config(self, configuration): config = property(get_config, set_config) def _config_filename(self, name=None, extension=None): - """Utility function + """Returns valid file path - Ensures name is a JSON path, and if it's not an absolute path, it points it to the location of the Python file - for the current class - - :param name: string. Can be just the filename, a filename with an extension, or a relative/absolute path - :return: + :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 - # Ensure name has expected extension + 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 @@ -276,11 +263,10 @@ def _config_filename(self, name=None, extension=None): return filename def save_config(self, config=None, filename=None, mode='named'): - """Saves instrument configuration - :param config: - :param filename: - :param mode: - :return: + """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) @@ -311,9 +297,9 @@ def save_config(self, config=None, filename=None, mode='named'): raise ValueError('Unrecognised extension: %s' % ext) def load_config(self, filename=None): - """Loads configuration from JSON - :param filename: string - :return: + """Loads configuration from file + :param filename: str. Passed to self._config_filename + :return: dict """ # Get filename filename = self._config_filename(filename) @@ -335,12 +321,17 @@ def load_config(self, filename=None): 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': - # Dumps the configuration dictionary to a 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) From 54cbfdb681a1dc911ea3b82d6a4d02e9166a68a2 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Wed, 18 Nov 2020 09:18:03 +0900 Subject: [PATCH 17/19] Andor configuration loading using properties --- nplab/instrument/camera/Andor/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nplab/instrument/camera/Andor/__init__.py b/nplab/instrument/camera/Andor/__init__.py index b644d055..79ef300e 100644 --- a/nplab/instrument/camera/Andor/__init__.py +++ b/nplab/instrument/camera/Andor/__init__.py @@ -34,9 +34,9 @@ def __init__(self, settings_filepath=None, camera_index=None, config_file=None, self.detector_shape = self.DetectorShape # passing the Andor parameter to the CameraRoiScale class if config_file is not None: - self.load_config(config_file) + self.config = self.load_config(config_file) else: - self.load_config('default_config') + self.config = self.load_config('default_config') def __del__(self): # Need to explicitly call this method so that the shutdown procedure is followed correctly From 4ca63bb636b9af94d027a1432ec26e9dacc29ee7 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Sun, 28 Mar 2021 18:31:12 +0900 Subject: [PATCH 18/19] Default config always points to the correct location --- nplab/instrument/camera/Andor/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nplab/instrument/camera/Andor/__init__.py b/nplab/instrument/camera/Andor/__init__.py index a87a6876..55d4dcd0 100644 --- a/nplab/instrument/camera/Andor/__init__.py +++ b/nplab/instrument/camera/Andor/__init__.py @@ -12,6 +12,7 @@ from nplab.ui.ui_tools import UiTools from weakref import WeakSet import time +import inspect class Andor(CameraRoiScale, AndorBase): @@ -36,7 +37,12 @@ def __init__(self, settings_filepath=None, camera_index=None, config_file=None, if config_file is not None: self.config = self.load_config(config_file) else: - self.config = self.load_config('default_config') + filename = 'default_config' + if not os.path.isabs(filename): + f = inspect.getfile(self.__class__) + d = os.path.dirname(f) + filename = os.path.join(d, filename) + self.config = self.load_config(filename) def __del__(self): # Need to explicitly call this method so that the shutdown procedure is followed correctly From ad1fde5436e3664f108f1f7265602e38e40b63a9 Mon Sep 17 00:00:00 2001 From: YagoDel Date: Sun, 28 Mar 2021 18:37:06 +0900 Subject: [PATCH 19/19] Bug --- nplab/instrument/camera/Andor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nplab/instrument/camera/Andor/__init__.py b/nplab/instrument/camera/Andor/__init__.py index 55d4dcd0..30c81997 100644 --- a/nplab/instrument/camera/Andor/__init__.py +++ b/nplab/instrument/camera/Andor/__init__.py @@ -39,7 +39,7 @@ def __init__(self, settings_filepath=None, camera_index=None, config_file=None, else: filename = 'default_config' if not os.path.isabs(filename): - f = inspect.getfile(self.__class__) + f = inspect.getfile(Andor) d = os.path.dirname(f) filename = os.path.join(d, filename) self.config = self.load_config(filename)