diff --git a/src/Basic_Path/main.py b/src/Basic_Path/main.py new file mode 100644 index 0000000..52e7663 --- /dev/null +++ b/src/Basic_Path/main.py @@ -0,0 +1,50 @@ +# author: Axel Fischer +# created at: 07.05.2019 +# company/institute: sweep-me.net + +import os + +class Main(): + + # please define variables and units as returned by the function 'main' + variables = ["Folder", "File name", "File extension", "File exists"] + units = ["", "", "", ""] + + # the function 'main' will be loaded as the function that can be seen inside the module + # the arguments of the function tell SweepMe! which kind of data you expect and what are the default values + # SweepMe! will generate the input of the graphical user interface accordingly + def main( + self, + # must the first argument and should not be removed + + Path_to_evaluate = (), + # a tuple let you choose from all available SweepMe! values from a ComboBox + + + ): # angry? If you find a bug or need support, please write to support@sweep-me.net + + + ''' +
Possible data types:
+If further types are needed, please contact us, e.g. write to support@sweep-me.net
+ ''' + + # please define variables and units as returned by the function 'main' + variables = ["Data", "Variable 1", "Variable 2"] + units = ["", "unit1", "unit2"] + + # here we can define the arguments presented in the user interface that are later handed over to function 'main' + # the keys are the argument names, the values are the default values for the user interface + arguments = {"Test data": (), # a tuple let you choose from all available SweepMe! values from a ComboBox + # Caution: please note that test_data will be a numpy array of values, + # even if the data contains just one value + "Test integer": 42, # an integer let you insert an integer + "Test float": math.pi, # a float let you insert an float + "Test boolean": True, # a bool let you choose between True and False from a ComboBox + "Test selection": ["Choice1", "Choice2"], # A list of strings let you choose one of the items in a ComboBox + "Test string": "A text you can modify", # a string let you enter a string + "Test directory path": pathlib.Path("."), # a pathlib.Path() object with a path to a directory let you + # press a button to open a QFileDialog to select another directory + "Test file path": pathlib.Path("." + os.sep + "debug.log"), # a pathlib.Path() object with a path to + # a file let you press a button to open a QFileDialog to select another file, e.g. to load a certain calibration or setting file + } + + def prepare_run(self): + """ + This function is called at the beginning before the measurement thread is started. + Thus, this function runs in the GUI thread of SweepMe!. + """ + print("prepare_run") + + def prepare_stop(self): + """ + This function is called at the end after the measurement thread is finished. + Thus, this function runs in the GUI thread of SweepMe!. + """ + print("prepare_stop") + + def connect(self): + """ + This functions is called at the start of the measurement. + """ + print("connect") + + def disconnect(self): + """ + This functions is called at the end of the measurement. + """ + print("disconnect") + + def initialize(self): + """ + This functions is called at the start of the measurement. + """ + print("initialize") + + print("Arguments entered or selected by the user:") + print(self.main_arguments) # early access to the arguments + + def deinitialize(self): + """ + This functions is called at the end of the measurement. + """ + print("deinitialize") + + def configure(self): + """ + This functions is called when the CustomFunction module gets part of an active branch. + """ + print("configure") + + def unconfigure(self): + """ + This functions is called when the CustomFunction module is no more part of an active branch. + """ + print("unconfigure") + + def signin(self): + """ + This functions is called when a module above CustomFunction does a further iteration step. + """ + print("signin") + + def signout(self): + """ + This functions is called when a module above CustomFunction ends with the iteration step. + """ + print("signout") + + def main(self, **kwargs): + """ + This function is called in the measurement thread at the 'process' step of the drivers. + i.e. after measurement data has been returned. + It gets the arguments as a dictionary as defined by the static attribute 'arguments' + """ + + print("Keyword arguments:\n", kwargs) + + test_data = kwargs["Test data"] + test_int = kwargs["Test integer"] + test_float = kwargs["Test float"] + test_bool = kwargs["Test boolean"] + test_selection = kwargs["Test selection"] + test_string = kwargs["Test string"] + test_directory = kwargs["Test directory path"] + test_file = kwargs["Test file path"] + + # now do whatever you want ... + # here, the values of the arguments are simply printed to show the user selection + print() + print("Output of CustomFunction example function:") + + print("data:", test_data) # Caution: Please note that test_data is always an array of values, even if it contains just one element + print("data type:", type(test_data)) # Caution: Please note that test_data is always an array of values, even if it contains just one element + print("data, last element:", test_data[-1]) # Caution: Please note that test_data is always an array of values, even if it contains just one element + + if test_data[-1] is None: + print("Please, select a key for 'test_data' to hand it over to this function") + else: + print("data, flattened:", test_data.flatten()) # Caution: Please note that test_data is always an array of values, even if it contains just one element + + print("int:", test_int) + print("float:", test_float) + print("bool:", test_bool) + print("selection:", test_selection) + print("string:", test_string) + print("path directory:", repr(test_directory), str(test_directory)) # It is a Pathlib object that can be easily converted to string + print("path file:", repr(test_file), str(test_file)) # It is a Pathlib object that can be easily converted to string + print() + + + # ... and return your result according to 'variables' and 'units' defined at the beginning of this file + return test_data[-1], test_int, test_float + + +if __name__ == "__main__": + + # This part of the code only runs if this file is directly run with python + # You need to install pysweepme to make the import section work, + # use command line with "pip install pysweepme" + + script = Main() + + arguments = { + "Test data": np.array([1,3,5]), + "Test integer": 11, + "Test float": math.pi/2.0, + "Test boolean": False, + "Test selection": "Choice2", + "Test string": "Some text that is different from the default", + "Test directory path": pathlib.Path("C:"), + "Test file path": pathlib.Path("." + os.sep + "debug.log"), + } + + # not really needed, just to show one can call also other functions than main + script.connect() + + # handing over the dictionary 'arguments' to be used like keyword arguments + values = script.main(**arguments) + + print("Returned values:", values) + + # not really needed, just to show one can call also other functions than main + script.disconnect() + + print("CustomFunction script 'Example' finished.") \ No newline at end of file diff --git a/src/Example_GUI/main.py b/src/Example_GUI/main.py new file mode 100644 index 0000000..83c58a4 --- /dev/null +++ b/src/Example_GUI/main.py @@ -0,0 +1,267 @@ +# author: +# created at: +# company/institute: + +from ErrorMessage import error +import os +import time +import datetime +from PySide2 import QtWidgets, QtGui, QtCore +import matplotlib.dates as mdates + +# you can import any module that is shipped with SweepMe!, see credits.html +# Any other module must be put into the folder of your function and loaded by adding the path the python environment using the following two lines: +# from FolderManager import addFolderToPATH +# addFolderToPATH() + +class Main(): + + # please define variables and units as returned by the function 'main' + variables = ["Dial value"] + units = [""] + + ## Attention: this function is called in the main GUI thread so that the widget is automatically + ## created in the correct thread + def renew_widget(self, widget = None): + """ gets the widget from the module and returns the same widget or creates a new one""" + + if widget is None: + # if the widget has not been created so far, we create it now and store it as self.widget + self.widget = Widget() + else: + # the second time a run is started, we can use the widget that is handed over to 'renew_widget' to store it as self.widget + self.widget = widget + + # return the actual widget to inform the module which one has to be inserted into the DockWidget of the Dashboard + return self.widget + + def initialize(self): + + self.widget.plot_widget.clear_data() + + def main(self, x = (), y = (), ): + ''' + This example shows how to create a basic GUI widget. Please use open/modify to see the code and modify it to your needs. + Press 'Reload' to make sure that your widget is renewed after you did a modification. + ''' + + + # Use of a signal to transfer the values to from the Measurement thread to the GUI thread + self.widget.renewValues.emit("%1.3g" % x[-1], "%1.3g" % y[-1]) + + # Using a thread-safe function to retrieve the value of the dial + dial_value = self.widget.get_dial_value() + + # here we update the plot with the new value + self.widget.updateData.emit(datetime.datetime.now(), dial_value) + + return dial_value + + +## the widget to be displayed +class Widget(QtWidgets.QWidget): + + renewValues = QtCore.Signal(str, str) + updateData = QtCore.Signal(object, float) + + def __init__(self): + + super().__init__() + + layout = self.create_MainLayout() + + self.setLayout(layout) + + # here, the custom signal is linked + self.renewValues.connect(self.renew_values) + self.updateData.connect(self.plot_widget.update_data) + + self._dial_value = self.dial.value() + + + def create_MainLayout(self): + """ returns a main layout that includes all widgets to be shown""" + + grid = QtWidgets.QGridLayout() + + lbl = QtWidgets.QLabel("x:") + lbl.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(lbl, 0, 0) + self.lbl_value1 = QtWidgets.QLabel("1.0") + self.lbl_value1.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(self.lbl_value1, 0, 1) + + lbl = QtWidgets.QLabel("y:") + lbl.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(lbl, 1, 0) + self.lbl_value2 = QtWidgets.QLabel("1.0") + self.lbl_value2.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(self.lbl_value2, 1, 1) + + lbl = QtWidgets.QLabel("Dial:") + lbl.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(lbl, 2, 0) + self.lbl_value3 = QtWidgets.QLabel("1.0") + self.lbl_value3.setFont(QtGui.QFont("Open Sans", 30, QtGui.QFont.Bold)) + grid.addWidget(self.lbl_value3, 2, 1) + + self.dial = QtWidgets.QDial() + self.dial.valueChanged[int].connect(self.update_dial_value) + grid.addWidget(self.dial, 0, 2, 3, 1) + + self.lbl_value3.setText(str(self.dial.value())) + + self.plot_widget = PlotWidget(self) + grid.addWidget(self.plot_widget, 3, 0, 1, 3) + + return grid + + def set_value1(self, value): + + self.lbl_value1.setText(str(value)) + + def set_value2(self, value): + + self.lbl_value2.setText(str(value)) + + def renew_values(self, val1, val2): + + self.lbl_value1.setText(str(val1)) + self.lbl_value2.setText(str(val2)) + + def update_dial_value(self, val): + self.lbl_value3.setText(str(val)) + self._dial_value = val + + def get_dial_value(self): + return self._dial_value + + + +from matplotlib import pyplot as plt + +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT + +from matplotlib.figure import Figure +from matplotlib import rcParams as mplParams + + +class PlotWidget(QtWidgets.QWidget): + + def __init__(self, parent = None): + + super(PlotWidget, self).__init__(parent) + + layout = self.create_MainLayout() + + self.setLayout(layout) + + self.plotCanvas.axes = self.plotCanvas.fig.add_subplot(111) + self.plotCanvas.axes.set_title("Plot") + self.plotCanvas.axes.set_xlabel("Time") + self.plotCanvas.axes.set_ylabel("Dial value") + + self.line = self.plotCanvas.axes.plot([],[], linewidth = 1, marker = 'o', markersize = 1) + + self.plotCanvas.draw() + + + self.x, self.y = [],[] + + + def create_MainLayout(self): + + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) + self.setSizePolicy(sizePolicy) + self.gridLayout = QtWidgets.QGridLayout(self) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setVerticalSpacing(0) + self.frame = QtWidgets.QFrame(self) + self.frame.setEnabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setSizeIncrement(QtCore.QSize(0, 0)) + self.gridLayout.addWidget(self.frame, 0, 0, 1, 1) + + self.layout = QtWidgets.QVBoxLayout(self.frame) + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + + self.plotCanvas = MyMplCanvas(self.frame) + self.layout.addWidget(self.plotCanvas) + + + def update_data(self, x, y): + + self.x.append(x) + self.y.append(float(y)) + + self.plotCanvas.axes.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) + self.plotCanvas.fig.autofmt_xdate() + + + plt.setp(self.line, xdata = self.x, ydata = self.y) + + try: + self.plotCanvas.axes.relim() + self.plotCanvas.axes.autoscale_view() + + self.plotCanvas.fig.tight_layout(pad=0.2, w_pad=0.1, h_pad=0.1) + self.plotCanvas.draw() + except: + error() + + def clear_data(self): + + self.x, self.y = [],[] + + + +class NavigationToolbar(NavigationToolbar2QT): + + # only display the buttons we need + toolitems = [t for t in NavigationToolbar2QT.toolitems if + t[0] in ('Home', 'Pan', 'Zoom', 'Save')] # 'Subplots' + + def __init__(self, canvas, widget, coordinates): + super(__class__, self).__init__(canvas, widget, coordinates) + self.setVisible(False) + + +class MyMplCanvas(FigureCanvas): + + # this class creates a matplotlib-plot on a canvas as a widget + def __init__(self, parent=None): + + self.fig = Figure() + self.fig.patch.set_alpha(0.0) + + FigureCanvas.__init__(self, self.fig) + FigureCanvas.setSizePolicy(self, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + + self.setParent(parent) + + self.mpl_toolbar = NavigationToolbar(self.fig.canvas, self, coordinates=True) + + self.fig.tight_layout(pad=0.2, w_pad=0.1, h_pad=0.1) + + + def enterEvent(self, event): + self.mpl_toolbar.setVisible(True) + self.mpl_toolbar.setMinimumWidth(100) + self.mpl_toolbar.move(self.width()-self.mpl_toolbar.width(),20) + + def leaveEvent(self, event): + self.mpl_toolbar.setVisible(False) + + + + \ No newline at end of file diff --git a/src/Example_keyword_arguments/main.py b/src/Example_keyword_arguments/main.py new file mode 100644 index 0000000..fad8fc8 --- /dev/null +++ b/src/Example_keyword_arguments/main.py @@ -0,0 +1,67 @@ +# author: +# created at: +# company/institute: + +class Main(): + + """ + This example shows how to define arguments using the static attribute 'arguments'. + This way the number of arguments can be changed. + To receive an unknown number of arguments, take Option4 that uses **kwargs. + Then kwargs is nothing else than an ordinary dictionary where you find for each argument key + the corresponding value. + """ + + arguments = {"Number argument": 1, "String argument": "2", "Boolean argument": True} + variables = ["Variable1", "Variable 2"] + units = ["unit1", "unit2"] + + # The following options are just for comparison between the old style + # and the new style to define arguments + # Please use Option4 and remove the other ones. + + # Option1: the old style of defining arguments + # def main(self, first=1, second="2", third=True): + # print(first, second, third) + # return first, second, third + + # Option2: the old style but without defaults here + # def main(self, first, second, third): + # print(first, second, third) + # return first, second, third + + # Option3: DOES NOT WORK, receiving an arbitrary number of arguments via args + # def main(self, *args): + # print(args) + # return args + + # Option4: receiving an arbitrary number of arguments via kwargs + def main(self, **kwargs): + print("Arguments:", kwargs) + + # Just an example how to process the arguments and return it + var1 = kwargs["Number argument"] * 2.0 + var2 = kwargs["String argument"] + "_returned" + return var1, var2 + + +if __name__ == "__main__": + + # This part of the code only runs if this file is directly run with python + # You need to install pysweepme to make the import section work, + # use command line with "pip install pysweepme" + + script = Main() + + arguments = { + "first": 2, + "second": "3", + "third": False, + } + + # handing over the dictionary 'arguments' to be used like keyword arguments + values = script.main(**arguments) + + print("Returned values:", values) + + print("CustomFunction script 'Example_keyword_arguments' finished.") \ No newline at end of file diff --git a/src/Example_minimal/main.py b/src/Example_minimal/main.py new file mode 100644 index 0000000..b4127bc --- /dev/null +++ b/src/Example_minimal/main.py @@ -0,0 +1,37 @@ +# author: +# created at: +# company/institute: + +class Main(): + + """ +This script can be used to add a widget to the dashboard that either displays green PASS or a red FAIL as a signal
for a user whether a test was successful or not.
Please use the Parameter syntax {...} to handover a value to the script.
Supported data types for PASS are:
Supported data types for FAIL are:
All other parameters will raise an exception.
+ """ + + # please define variables and units as returned by the function 'main' + variables = [] + units = [] + + arguments = { + "State": "", + } + + ## Attention: this function is called in the main GUI thread so that the widget is automatically + ## created in the correct thread + def renew_widget(self, widget = None): + """ gets the widget from the module and returns the same widget or creates a new one""" + + if widget is None: + # if the widget has not been created so far, we create it now and store it as self.widget + self.widget = Widget() + else: + # the second time a run is started, we can use the widget that is handed over to 'renew_widget' + # to store it as self.widget + self.widget = widget + + # return the widget to inform the module which one has to be inserted Widget into the Dashboard + return self.widget + + def initialize(self): + + self.widget.reset_signal.emit() + + def main(self, **kwargs): + + state = kwargs["State"] + + if isinstance(state, str): + state = state.lower() + + if state in ["true", True, 1, "1", "pass"]: + self.widget.pass_signal.emit() + elif state in ["false", False, 0, "0", "fail"]: + self.widget.fail_signal.emit() + else: + msg=f"State {state!r} cannot be interpreted as pass or fail." + raise ValueError(msg) + + return None + + +class Widget(QtWidgets.QWidget): + + pass_signal = QtCore.Signal() + fail_signal = QtCore.Signal() + reset_signal = QtCore.Signal() + toggle_signal = QtCore.Signal() + + def __init__(self): + + super().__init__() + + # Set up the main layout and label + self.layout = QtWidgets.QVBoxLayout() + self.label = QtWidgets.QLabel() + self.label.setAlignment(QtCore.Qt.AlignCenter) + self.layout.addWidget(self.label) + self.setLayout(self.layout) + # self.layout.setContentsMargins(1, 1, 1, 1) + self.layout.setContentsMargins(0, 0, 0, 0) + + self.pass_signal.connect(self.set_pass) + self.fail_signal.connect(self.set_fail) + self.reset_signal.connect(self.reset) + self.toggle_signal.connect(self.toggle_state) + + # Initialize to start state + self.reset() + + def reset(self): + """Set the widget to Start state.""" + self.label.setText("PASS/FAIL") + self.label.setStyleSheet(""" + QLabel { + background-color: grey; + color: darkgrey; + font-size: 96px; + font-weight: bold; + padding: 2px; + border-radius: 2px; + } + """) + + def set_pass(self): + """Set the widget to PASS state.""" + self.label.setText("PASS") + self.label.setStyleSheet(""" + QLabel { + background-color: limegreen; + color: white; + font-size: 96px; + font-weight: bold; + padding: 2px; + border-radius: 2px; + } + """) + + def set_fail(self): + """Set the widget to FAIL state.""" + self.label.setText("FAIL") + self.label.setStyleSheet(""" + QLabel { + background-color: red; + color: black; + font-size: 96px; + font-weight: bold; + padding: 2px; + border-radius: 2px; + } + """) + + def toggle_state(self): + """Toggle between PASS and FAIL states.""" + if self.label.text() == "PASS": + self.set_fail() + else: + self.set_pass() diff --git a/src/Math_Gradient/main.py b/src/Math_Gradient/main.py new file mode 100644 index 0000000..562fbde --- /dev/null +++ b/src/Math_Gradient/main.py @@ -0,0 +1,53 @@ +# author: Axel Fischer (sweep-me.net) +# started: 13.10.18 + +import numpy as np +import os +import pathlib +import math + +# please defines variables and units as returned by the function 'main' +variables = ["Gradient"] +units = [""] + +global xlast +xlast = None + +global ylast +ylast = None + + +# the function 'main' will be loaded as function +def main( + x = (), # x value + y = (), # y value + ): # angry? If you find a bug or need support for another type, please contact us + + # just right at the start of the function, you set the help text which will be displayed as description in the GUI + ''' +This function makes use of Windows User32 library to create different message boxes. There are different types and depending on the clicked button different values are returned. Please find a list of possible types and their return values in brackets below. All message boxes are blocking and the user has to press one of the buttons to continue. To customize the message box, you can change title and text that are supporting unicode characters as well.
Type and return values: