330 lines
12 KiB
Python
330 lines
12 KiB
Python
"""
|
|
run this before using this library:
|
|
ipython -i prctrl_interactive.py
|
|
"""
|
|
version = "0.1"
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
|
|
from datetime import datetime as dtime
|
|
from os import path, makedirs
|
|
import threading as mt
|
|
import multiprocessing as mp
|
|
|
|
import argparse
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
if __package__ is None:
|
|
# make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change
|
|
__package__ = "prsctrl"
|
|
from os import path
|
|
filepath = path.realpath(path.abspath(__file__))
|
|
sys.path.insert(0, path.dirname(path.dirname(filepath)))
|
|
|
|
|
|
# import device modules
|
|
from .devices import shutter as mod_shutter
|
|
from .devices import lock_in as mod_lock_in
|
|
from .devices import lamp as mod_lamp
|
|
from .devices import monochromator as mod_monochromator
|
|
# import base classes
|
|
from .devices.lock_in import Lock_In_Amp
|
|
from .devices.shutter import Shutter
|
|
from .devices.lamp import Lamp
|
|
from .devices.monochromator import Monochromator
|
|
|
|
# from .measurement import measure as _measure
|
|
from .utility.data_collector import PrsDataCollector
|
|
from .utility.config_file import ConfigFile
|
|
from .utility.device_select import select_device_interactive, connect_device_from_config_or_interactive
|
|
from .update_funcs import Monitor
|
|
|
|
import logging
|
|
log = logging.getLogger(__name__)
|
|
|
|
# CONFIGURATION
|
|
_runtime_vars = {
|
|
"last-measurement": ""
|
|
}
|
|
# defaults, these may be overridden by a config file
|
|
settings = {
|
|
"datadir": path.expanduser("~/Desktop/PR/data"),
|
|
"name": "interactive-test",
|
|
"flush_after": 3000,
|
|
"use_buffer": False,
|
|
}
|
|
|
|
cfilename: str = "photoreflectance.yaml"
|
|
config_path: str = ""
|
|
config_file: ConfigFile = ConfigFile("")
|
|
|
|
test = False
|
|
|
|
# DEVICES
|
|
# global variable for the instrument/client returned by pyvisa/bleak
|
|
lockin: Lock_In_Amp|None = None
|
|
shutter: Shutter|None = None
|
|
lamp: Lamp|None = None
|
|
mcm: Monochromator|None = None
|
|
|
|
data_collector = PrsDataCollector(data_path=settings["datadir"], data_name="interactive", dirname="interactive_test", add_number_if_dir_exists=True)
|
|
t0 = 0
|
|
data = None
|
|
md = None
|
|
|
|
from .test_measurement import _measure_both_sim
|
|
def measure_both_sim(**kwargs):
|
|
return _measure_both_sim(mcm, lockin, shutter, **kwargs)
|
|
|
|
def monitor(script: str|int=0, interval: float|None=None, metadata:dict={}, flush_after: int|None=None, use_buffer: bool|None=None, max_measurements=None, stop_on_script_end: bool=False, max_points_shown=None):
|
|
"""
|
|
Monitor the voltage with matplotlib.
|
|
- Opens a matplotlib window and takes measurements depending on settings["interval"]
|
|
- Waits for the user to press a key
|
|
|
|
If use_buffer=False, uses python's time.sleep() for waiting the interval, which is not very precise.
|
|
With use_buffer=True, the timing of the voltage data readings will be very precise, however,
|
|
the led updates may deviate up to <interval>.
|
|
|
|
The data is automatically saved to "<date>_<time>_<name>" in the data directory.
|
|
|
|
Parameters
|
|
----------
|
|
script : str|int
|
|
Path to a led script file, or a constant value between 0 and 100 for the LED.
|
|
interval : float|None
|
|
Time between measurements.
|
|
If None, the value is taken from the settings.
|
|
metadata : dict
|
|
Metadata to append to the data header.
|
|
The set interval as well as the setting for 'name' and 'led' are automatically added.
|
|
flush_after : int|None
|
|
Flush the data to disk after <flush_after> readings
|
|
If None, the value is taken from the settings.
|
|
use_buffer : bool
|
|
If True, use the voltage measurement device's internal buffer for readings, which leads to more accurate timings.
|
|
If None, the value is taken from the settings.
|
|
max_points_shown : int|None
|
|
how many points should be shown at once. None means infinite
|
|
max_measurements : int|None
|
|
maximum number of measurements. None means infinite
|
|
stop_on_script_end : bool, optional
|
|
Stop measurement when the script end is reached
|
|
"""
|
|
global _runtime_vars, data_collector, dev, led
|
|
global data, md
|
|
_runtime_vars["last_measurement"] = dtime.now().isoformat()
|
|
if interval is None: interval = settings["interval"]
|
|
if flush_after is None: flush_after = settings["flush_after"]
|
|
if use_buffer is None: use_buffer = settings["use_buffer"]
|
|
# set metadata
|
|
metadata["interval"] = str(interval)
|
|
metadata["name"] = settings["name"]
|
|
metadata["led"] = settings["led"]
|
|
metadata["led_script"] = str(script)
|
|
print(f"Starting measurement with:\n\tinterval = {interval}s\nUse <C-c> to stop. Save the data using 'data.save_csv()' afterwards.")
|
|
plt.ion()
|
|
plt_monitor = Monitor(max_points_shown=max_points_shown)
|
|
led_script = LedScript(script=script, auto_update=True, verbose=True)
|
|
data_collector = PrsDataCollector(metadata=metadata, data_path=settings["datadir"], data_name=settings["name"])
|
|
# data_collector.clear()
|
|
data_queue = mp.Queue()
|
|
command_queue = mp.Queue()
|
|
# Argument order must match the definition
|
|
proc_measure = mt.Thread(target=_measure, args=(dev,
|
|
led,
|
|
led_script,
|
|
data_collector,
|
|
interval,
|
|
flush_after,
|
|
use_buffer,
|
|
max_measurements,
|
|
stop_on_script_end,
|
|
False, # verbose
|
|
command_queue,
|
|
data_queue
|
|
))
|
|
proc_measure.start()
|
|
try:
|
|
while proc_measure.is_alive():
|
|
while not data_queue.empty():
|
|
# print(data_queue.qsize(), "\n\n")
|
|
current_data = data_queue.get(block=False)
|
|
i, tval, vval, led_val = current_data
|
|
plt_monitor.update(i, tval, vval, led_val)
|
|
|
|
except KeyboardInterrupt:
|
|
pass
|
|
command_queue.put("stop")
|
|
proc_measure.join()
|
|
print("Measurement stopped" + " "*50)
|
|
led_script.stop_updating() # stop watching for file updates (if enabled)
|
|
data_collector.save_csv(verbose=True)
|
|
data, metadata = data_collector.get_data()
|
|
fig = data_plot(data, CPD=True, LED=True)
|
|
plt.ioff()
|
|
fig_path = path.join(data_collector.path, data_collector.dirname + ".pdf")
|
|
fig.savefig(fig_path)
|
|
|
|
|
|
# DATA
|
|
def data_load(dirname:str) -> tuple[np.ndarray, dict]:
|
|
"""
|
|
Load data in directory <dirname> in the data directory as numpy array.
|
|
Sets the `data` and `md` variables
|
|
|
|
Parameters
|
|
----------
|
|
dirname : str
|
|
Absolute path to the directory containing the measurement or directory name in the data directory.
|
|
"""
|
|
global data, md
|
|
if path.isabs(dirname):
|
|
dirpath = dirname
|
|
else:
|
|
dirpath = path.join(settings["datadir"], dirname)
|
|
data, md = PrsDataCollector.load_data_from_dir(dirpath, verbose=True)
|
|
|
|
# SETTINGS
|
|
def set(setting, value):
|
|
global settings, config_path
|
|
if setting in settings:
|
|
if type(value) != type(settings[setting]):
|
|
print(f"set: setting '{setting}' currently holds a value of type '{type(settings[setting])}'")
|
|
return
|
|
settings[setting] = value
|
|
config_file.set(setting, value)
|
|
|
|
def name(s:str):
|
|
global settings
|
|
settings["name"] = s
|
|
|
|
def save_settings():
|
|
global settings
|
|
for k, v in settings.items():
|
|
config_file.set(k, v)
|
|
config_file.save()
|
|
|
|
def load_settings():
|
|
global settings, config_path
|
|
settings = config_file.get_values()
|
|
settings["datadir"] = path.expanduser(settings["datadir"]) # replace ~
|
|
|
|
def help(topic=None):
|
|
if topic == None:
|
|
print("""
|
|
Functions:
|
|
monitor - take measurements with live monitoring in a matplotlib window
|
|
data_load - load data from a directory
|
|
data_plot - plot a data array
|
|
Run 'help(function)' to see more information on a function
|
|
|
|
Available topics:
|
|
imports
|
|
settings
|
|
Run 'help("topic")' to see more information on a topic""")
|
|
|
|
elif topic in [settings, "settings"]:
|
|
print(f"""Settings:
|
|
name: str - name of the measurement, determines filename
|
|
led: str - name/model of the LED that is being used
|
|
datadir: str - output directory for the csv files
|
|
interval: int - interval (inverse frequency) of the measurements, in seconds
|
|
beep: bool - whether the device should beep or not
|
|
|
|
|
|
Functions:
|
|
name("<name>") - short for set("name", "<name>")
|
|
set("setting", value) - set a setting to a value
|
|
save_settings() - store the settings in the config file
|
|
load_settings() - load settings from a file
|
|
|
|
Upon startup, settings are loaded from the config file.
|
|
The global variable 'config_path' determines the path used by save/load_settings. Use -c '<path>' to set another path.
|
|
The search path is:
|
|
<working-dir>/{cfilename}
|
|
$XDG_CONFIG_HOME/{cfilename}
|
|
~/.config/photoreflectance/{cfilename}
|
|
|
|
The current file path is:
|
|
{config_path}
|
|
""")
|
|
elif topic == "imports":
|
|
print("""Imports:
|
|
numpy as np
|
|
pandas as pd
|
|
matplotlib.pyplot as plt
|
|
os.path """)
|
|
else:
|
|
print(topic.__doc__.strip(" ").strip("\n"))
|
|
|
|
|
|
def connect_devices():
|
|
global lockin, shutter, lamp, mcm
|
|
lockin = mod_lock_in.connect_device(*select_device_interactive(mod_lock_in.list_devices(), "Select Lock-In-Amplifier: "))
|
|
shutter = mod_shutter.connect_device(*select_device_interactive(mod_shutter.list_devices(), "Select Shutter: "))
|
|
lamp = mod_lamp.connect_device(*select_device_interactive(mod_lamp.list_devices(), "Select Lamp: "))
|
|
mcm = mod_monochromator.connect_device(*select_device_interactive(mod_monochromator.list_devices(), "Select Monochromator: "))
|
|
|
|
def init():
|
|
global lockin, shutter, lamp, mcm, settings, config_path, config_file
|
|
print(r""" __ .__
|
|
_____________ ______ _____/ |________| |
|
|
\____ \_ __ \/ ___// ___\ __\_ __ \ |
|
|
| |_> > | \/\___ \\ \___| | | | \/ |__
|
|
| __/|__| /____ >\___ >__| |__| |____/
|
|
|__| \/ \/ """ + f"""{version}
|
|
Interactive Shell for Photoreflectance measurements
|
|
---
|
|
Enter 'help()' for a list of commands""")
|
|
parser = argparse.ArgumentParser(
|
|
prog="prsctrl",
|
|
description="measure photoreflectance",
|
|
)
|
|
backend_group = parser.add_mutually_exclusive_group(required=False)
|
|
parser.add_argument("-c", "--config", action="store", help="alternate path to config file")
|
|
args = vars(parser.parse_args())
|
|
from os import environ
|
|
|
|
# Load config file
|
|
if path.isfile(cfilename):
|
|
config_path = cfilename
|
|
elif 'XDG_CONFIG_HOME' in environ.keys():
|
|
config_path = path.join(environ["XDG_CONFIG_HOME"], "prsctrl", cfilename)
|
|
else:
|
|
config_path = path.join(path.expanduser("~/.config/prsctrl"), cfilename)
|
|
if args["config"]:
|
|
config_path = args["config"]
|
|
|
|
config_file = ConfigFile(config_path, init_values=settings)
|
|
load_settings()
|
|
|
|
# setup logging
|
|
log_path = path.expanduser(config_file.get_or("path_log", "~/.cache/prsctrl-interactive.log"))
|
|
makedirs(path.dirname(log_path), exist_ok=True)
|
|
logging.basicConfig(
|
|
level=logging.WARN,
|
|
format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
|
|
handlers=[
|
|
logging.FileHandler(log_path),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
|
|
if not path.isdir(settings["datadir"]):
|
|
makedirs(settings["datadir"])
|
|
|
|
# init the devices
|
|
shutter = connect_device_from_config_or_interactive(config_file, "shutter", "Shutter", mod_shutter, log=log)
|
|
lockin = connect_device_from_config_or_interactive(config_file, "lock-in", "Lock-In Amplifier", mod_lock_in, log=log)
|
|
lamp = connect_device_from_config_or_interactive(config_file, "lamp", "Lamp", mod_lamp, log=log)
|
|
mcm = connect_device_from_config_or_interactive(config_file, "monochromator", "Monochromator", mod_monochromator, log=log)
|
|
|
|
# atexit.register(_backend.exit, dev)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
init()
|