697 lines
31 KiB
Python
697 lines
31 KiB
Python
from PyQt6.QtGui import *
|
|
from PyQt6.QtWidgets import *
|
|
from PyQt6.QtCore import *
|
|
|
|
import multiprocessing
|
|
import multiprocessing.managers
|
|
|
|
import time
|
|
from datetime import datetime
|
|
import traceback,sys,os
|
|
import numpy as np
|
|
import pyqtgraph as pg
|
|
from scripts import import_txt
|
|
|
|
import pyqtgraph.opengl as gl
|
|
|
|
from scipy.optimize import curve_fit
|
|
import collections
|
|
|
|
# Get the current script's directory
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# Get the parent directory by going one level up
|
|
parent_dir = os.path.dirname(current_dir)
|
|
# Add the parent directory to sys.path
|
|
sys.path.append(parent_dir)
|
|
|
|
from design_files.BT_3D_Plot_design import Ui_MainWindow
|
|
|
|
class Vector:
|
|
def __init__(self, vec_0, vec_direction, length, width, color, view):
|
|
#transform axis such that it fits the real setup
|
|
vec_direction_new = np.array([1,1,1], dtype=float)
|
|
vec_direction_new[0] = vec_direction[1]
|
|
vec_direction_new[1] = vec_direction[0] #plus or minus on the right side?
|
|
vec_direction_new[2] = vec_direction[2]
|
|
self.vec_0 = vec_0
|
|
self.vec_direction = vec_direction_new
|
|
self.length = length
|
|
self.width = width
|
|
self.color = color
|
|
self.view = view
|
|
|
|
#After initialization draw Vector
|
|
self.draw()
|
|
|
|
def Calc_Vec1_Vec2(self): #Calculate start- and end-vector of the vector to be drawn
|
|
vec_direction_length = np.sqrt(self.vec_direction[0]**2+self.vec_direction[1]**2+self.vec_direction[2]**2)
|
|
if vec_direction_length == 0:
|
|
direction_normalized = np.array([0,0,0])
|
|
else:
|
|
direction_normalized = self.vec_direction/vec_direction_length
|
|
|
|
self.vec1 = self.vec_0 - direction_normalized*self.length/2
|
|
self.vec2 = self.vec_0 + direction_normalized*self.length/2
|
|
|
|
self.headvec1 = np.empty(shape=(0,3))
|
|
self.headvec2 = np.empty(shape=(0,3))
|
|
|
|
Headsteps = 5
|
|
headlength = self.length/5
|
|
headsegment = headlength/Headsteps
|
|
|
|
for i in range(0,Headsteps):
|
|
vec1x = self.vec2 + i*headsegment*direction_normalized
|
|
vec2x = self.vec2 + (i+1)*headsegment*direction_normalized
|
|
|
|
self.headvec1 = np.append(self.headvec1, [vec1x], axis=0)
|
|
self.headvec2 = np.append(self.headvec2, [vec2x], axis=0)
|
|
|
|
def draw(self):
|
|
|
|
self.Calc_Vec1_Vec2()
|
|
|
|
#Draw Line of Vector
|
|
linedata = np.array([self.vec1,self.vec2])
|
|
self.Line = gl.GLLinePlotItem()
|
|
self.Line.setData(pos=linedata, mode="lines", color=np.array([self.color,self.color]), width=self.width)
|
|
self.Line.setGLOptions('translucent')
|
|
self.view.addItem(self.Line)
|
|
|
|
#Draw Head of Vector
|
|
self.headline1 = gl.GLLinePlotItem()
|
|
self.headline2 = gl.GLLinePlotItem()
|
|
self.headline3 = gl.GLLinePlotItem()
|
|
self.headline4 = gl.GLLinePlotItem()
|
|
self.headline5 = gl.GLLinePlotItem()
|
|
|
|
self.headlinearray = [self.headline1, self.headline2, self.headline3, self.headline4, self.headline5]
|
|
|
|
i=0
|
|
for headline in self.headlinearray:
|
|
headlinedata = np.array([self.headvec1[i,:],self.headvec2[i,:]])
|
|
headlinewidth = (1-i/5)*20
|
|
headline.setData(pos=headlinedata, mode="lines", color=np.array([self.color,self.color]), width=headlinewidth)
|
|
headline.setGLOptions('translucent')
|
|
self.view.addItem(headline)
|
|
i=i+1
|
|
|
|
def set_changes(self): #Everytime a setting has changed this function should be called to update changes in the plot
|
|
|
|
#ChangeLine
|
|
linedata = np.array([self.vec1,self.vec2])
|
|
self.Line.setData(pos=linedata, mode="lines", color=np.array([self.color,self.color]), width=self.width)
|
|
|
|
#ChangeHead
|
|
i=0
|
|
for headline in self.headlinearray:
|
|
# print(str(np.shape(self.headvec1)) +" " +str(np.shape(self.headvec2)))
|
|
headlinedata = np.array([self.headvec1[i,:],self.headvec2[i,:]])
|
|
headlinewidth = (1-i/5)*20
|
|
headline.setData(pos=headlinedata, mode="lines", color=np.array([self.color,self.color]), width=headlinewidth)
|
|
i=i+1
|
|
|
|
def change_direction(self, vec_direction):
|
|
|
|
#transform axis such that it fits the real setup
|
|
vec_direction_new = np.array([1,1,1], dtype=float)
|
|
vec_direction_new[0] = vec_direction[1]
|
|
vec_direction_new[1] = vec_direction[0] #plus or minus on the right side?
|
|
vec_direction_new[2] = vec_direction[2]
|
|
self.vec_direction = vec_direction_new
|
|
self.Calc_Vec1_Vec2()
|
|
self.set_changes()
|
|
|
|
def change_length(self, length):
|
|
self.length = length
|
|
self.Calc_Vec1_Vec2()
|
|
self.set_changes()
|
|
|
|
def change_width(self, width):
|
|
self.width = width
|
|
self.Line.setData(width=self.width)
|
|
self.set_changes()
|
|
|
|
def change_color(self, color):
|
|
self.color = color
|
|
self.Line.setData(color=np.array([self.color,self.color]))
|
|
self.set_changes()
|
|
|
|
class Plane:
|
|
def __init__(self, vec0, vec1, vec2, color, view): #vec0 base edge of plane, vec1 and vec2 edges relative to vec0
|
|
self.vec0 = vec0
|
|
self.vec1 = vec1
|
|
self.vec2 = vec2
|
|
self.color = color
|
|
self.view = view
|
|
self.draw()
|
|
|
|
def draw(self):
|
|
VecData = [self.vec0, self.vec0+self.vec1, self.vec0+self.vec2, self.vec0+self.vec1+self.vec2, self.vec0+self.vec1, self.vec0+self.vec2]
|
|
PlaneMeshData = gl.MeshData()
|
|
PlaneMeshData.setVertexes(verts=VecData)
|
|
|
|
self.PlaneItem = gl.GLMeshItem(meshdata=PlaneMeshData, smooth=False, glOptions="translucent")
|
|
self.PlaneItem.setColor(self.color)
|
|
self.view.addItem(self.PlaneItem)
|
|
|
|
def change_color(self, color):
|
|
self.color = color
|
|
self.PlaneItem.setColor(self.color)
|
|
|
|
class Quader:
|
|
def __init__(self, vec0, vec1, vec2, vec3, color, view): #vec0 base edge of quader, vec1 and vec2 and vec3 edges relative to vec0
|
|
self.vec0 = vec0
|
|
self.vec1 = vec1
|
|
self.vec2 = vec2
|
|
self.vec3 = vec3
|
|
self.color = color
|
|
self.view = view
|
|
self.draw()
|
|
|
|
def draw(self):
|
|
self.colorfactor1 = np.array([0.8,0.8,0.8,1])
|
|
self.colorfactor2 = np.array([0.6,0.6,0.6,1])
|
|
self.Plane1 = Plane(self.vec0, self.vec1, self.vec2, self.color, self.view)
|
|
self.Plane2 = Plane(self.vec0, self.vec2, self.vec3, self.color*self.colorfactor1, self.view)
|
|
self.Plane3 = Plane(self.vec0, self.vec1, self.vec3, self.color*self.colorfactor2, self.view)
|
|
self.Plane4 = Plane(self.vec0+self.vec1+self.vec2+self.vec3, -self.vec1, -self.vec2, self.color, self.view)
|
|
self.Plane5 = Plane(self.vec0+self.vec1+self.vec2+self.vec3, -self.vec3, -self.vec2, self.color*self.colorfactor1, self.view)
|
|
self.Plane6 = Plane(self.vec0+self.vec1+self.vec2+self.vec3, -self.vec3, -self.vec1, self.color*self.colorfactor2, self.view)
|
|
|
|
def change_color(self,color):
|
|
self.color = color
|
|
self.Plane1.change_color(self.color)
|
|
self.Plane2.change_color(self.color*self.colorfactor1)
|
|
self.Plane3.change_color(self.color*self.colorfactor2)
|
|
self.Plane4.change_color(self.color)
|
|
self.Plane5.change_color(self.color*self.colorfactor1)
|
|
self.Plane6.change_color(self.color*self.colorfactor2)
|
|
|
|
|
|
class Niobium:
|
|
def __init__(self, vec0, vec1, vec2, vec3, color, view): #vec0 base edge of quader, vec1 and vec2 and vec3 edges relative to vec0
|
|
self.vec0 = vec0
|
|
self.vec1 = vec1
|
|
self.vec2 = vec2
|
|
self.vec3 = vec3
|
|
self.color = color
|
|
self.view = view
|
|
self.NoSegments = 8
|
|
self.draw()
|
|
|
|
def draw(self):
|
|
self.Quader1 = Quader(self.vec0+self.vec3/self.NoSegments*7, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader2 = Quader(self.vec0+self.vec3/self.NoSegments*6, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader3 = Quader(self.vec0+self.vec3/self.NoSegments*5, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader4 = Quader(self.vec0+self.vec3/self.NoSegments*4, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader5 = Quader(self.vec0+self.vec3/self.NoSegments*3, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader6 = Quader(self.vec0+self.vec3/self.NoSegments*2, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader7 = Quader(self.vec0+self.vec3/self.NoSegments*1, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.Quader8 = Quader(self.vec0+self.vec3/self.NoSegments*0, self.vec1, self.vec2, self.vec3/self.NoSegments, self.color, self.view)
|
|
self.QuaderList = [self.Quader1, self.Quader2, self.Quader3, self.Quader4,
|
|
self.Quader5, self.Quader6, self.Quader7, self.Quader8]
|
|
|
|
def change_color(self, ColorArray):
|
|
i=0
|
|
for Quader in self.QuaderList:
|
|
Quader.change_color(ColorArray[i])
|
|
i=i+1
|
|
|
|
|
|
def linearfunction(x,a,b): #Linear function for fitting purposes
|
|
return a*x+b
|
|
|
|
|
|
class WorkerSignals(QObject):
|
|
'''
|
|
Defines the signals available from a running worker thread.
|
|
Supported signals are:
|
|
finished: No data
|
|
error: tuple (exctype, value, traceback.format_exc() )
|
|
result: object data returned from processing, anything
|
|
progress: int indicating % progress
|
|
'''
|
|
finished = pyqtSignal()
|
|
error = pyqtSignal(tuple)
|
|
result = pyqtSignal(object)
|
|
progress = pyqtSignal(list)
|
|
|
|
|
|
class Worker(QRunnable):
|
|
'''
|
|
Worker thread
|
|
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
|
|
:param callback: The function callback to run on this worker thread. Supplied args and
|
|
kwargs will be passed through to the runner.
|
|
:type callback: function
|
|
:param args: Arguments to pass to the callback function
|
|
:param kwargs: Keywords to pass to the callback function
|
|
'''
|
|
|
|
def __init__(self, fn, *args, **kwargs):
|
|
super(Worker, self).__init__()
|
|
|
|
# Store constructor arguments (re-used for processing)
|
|
self.fn = fn
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.signals = WorkerSignals()
|
|
|
|
# Add the callback to our kwargs
|
|
self.kwargs['progress_callback'] = self.signals.progress
|
|
|
|
@pyqtSlot()
|
|
def run(self):
|
|
'''
|
|
Initialise the runner function with passed args, kwargs.
|
|
'''
|
|
|
|
# Retrieve args/kwargs here; and fire processing using them
|
|
try:
|
|
result = self.fn(*self.args, **self.kwargs)
|
|
except:
|
|
traceback.print_exc()
|
|
exctype, value = sys.exc_info()[:2]
|
|
self.signals.error.emit((exctype, value, traceback.format_exc()))
|
|
else:
|
|
self.signals.result.emit(result) # Return the result of the processing
|
|
finally:
|
|
self.signals.finished.emit() # Done
|
|
|
|
def get_float(Qline,default = 0): #gets value from QLineEdit and converts it to float. If text is empty or cannot be converted, it returns "default" which is 0, if not specified
|
|
try:
|
|
out = float(Qline.text())
|
|
except:
|
|
out = default
|
|
return(out)
|
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
def __init__(self, *args, **kwargs):
|
|
# Get the current script's directory
|
|
self.current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# Get the parent directory by going one level up
|
|
self.parent_dir = os.path.dirname(current_dir)
|
|
|
|
#import Gui from QT designer file
|
|
super(MainWindow, self).__init__(*args, **kwargs)
|
|
self.setupUi(self)
|
|
|
|
#setup T plot
|
|
|
|
self.graphWidget.setBackground('w')
|
|
self.graphWidget.setTitle("T gradient")
|
|
self.graphWidget.setLabel('left', 'z (cm)')
|
|
self.graphWidget.setLabel('bottom', 'T (K)')
|
|
|
|
temp = [0,1]
|
|
pen1 = pg.mkPen(color=(255, 0, 0), width=2) #Pen for temperature plotting
|
|
pen2 = pg.mkPen(color=(40, 40, 40), width=1 ) #Pen for Tc (dashed line) plotting
|
|
pen3 = pg.mkPen(color=(255, 100, 100), width=1 ) #Pen for linear gradient fit
|
|
|
|
|
|
self.plot_1 = self.graphWidget.plot(temp,[1,0],pen = pen1, name = 'T', symbol ='x', symbolPen ='r', symbolBrush = 0.2)
|
|
self.plot_2 = self.graphWidget.plot(temp,[1,0],pen = pen2, name = 'Tc')
|
|
self.plot_3 = self.graphWidget.plot(temp,[1,0],pen = pen3, name = 'T Gradient')
|
|
|
|
|
|
#setup 3D BT Plot
|
|
|
|
self.openGLWidget.setBackgroundColor("white")
|
|
|
|
#Niobium Sample
|
|
#Dimensions in cm
|
|
Nbx = 6
|
|
Nby = 10
|
|
Nbz = 0.3
|
|
|
|
vec0 = np.array([-Nbz/2,-Nbx/2,-Nby/2])
|
|
vec1 = np.array([0,0,Nby])
|
|
vec2 = np.array([0,Nbx,0])
|
|
vec3 = np.array([Nbz,0,0])
|
|
|
|
self.Niobium1 = Niobium(vec0,vec3,vec2,vec1, np.array([0.2,0.2,0.2,1]), self.openGLWidget)
|
|
|
|
xdiff = 1 #Distance between niobium sample and B-vectors
|
|
# yVectorArray = np.linspace(-3,3,3)
|
|
yVectorArray = [1.3,0,-1.3,1.3,0,-1.3,1.3,0,-1.3,1.3,0,-1.3,1.3,0,-1.3]
|
|
# zVectorArray = np.linspace(-5,5,5)
|
|
zVectorArray = [3.2,3.2,3.2,1.6,1.6,1.6,0,0,0,-1.6,-1.6,-1.6,-3.2,-3.2,-3.2]
|
|
|
|
self.vectorcolor = [0.2,0.8,0.2,1]
|
|
self.vectorwidth = 4
|
|
self.VectorArray = []
|
|
# for y in yVectorArray:
|
|
# for z in zVectorArray:
|
|
# self.VectorArray.append(Vector(np.array([xdiff,y,z]), np.array([1,1,1]), 1, self.vectorwidth, self.vectorcolor,self.openGLWidget))
|
|
|
|
for i in range(0,15):
|
|
y = yVectorArray[i]
|
|
z = zVectorArray[i]
|
|
self.VectorArray.append(Vector(np.array([xdiff,y,z]), np.array([1,1,1]), 1, self.vectorwidth, self.vectorcolor,self.openGLWidget))
|
|
|
|
|
|
#set up pyQT threadpool
|
|
self.threadpool = QThreadPool()
|
|
|
|
#define and standard threads.
|
|
|
|
#define signals and slots
|
|
self.actionSet_default.triggered.connect(self.set_default)
|
|
self.actionReset_default.triggered.connect(self.read_default)
|
|
self.button_connect.clicked.connect(self.start_meas)
|
|
self.checkBox_disableplots.stateChanged.connect(self.set_displot)
|
|
|
|
self.T_scale_min.editingFinished.connect(self.set_T_scale_min)
|
|
self.T_scale_max.editingFinished.connect(self.set_T_scale_max)
|
|
self.Tc.editingFinished.connect(self.set_Tc_value)
|
|
self.Time_Slider.valueChanged.connect(self.set_Qslider_pos)
|
|
self.Saved_seconds.editingFinished.connect(self.set_Saved_seconds_value)
|
|
self.checkBox_livedata.stateChanged.connect(self.set_ShowLiveData)
|
|
self.Play_Pause.clicked.connect(self.set_Replay_Play)
|
|
self.Replay_speed.editingFinished.connect(self.set_Replay_speed_value)
|
|
self.Show_sc_phase.stateChanged.connect(self.set_Show_sc_phase_value)
|
|
self.Show_gradient_fit.stateChanged.connect(self.plot_gradient_hide)
|
|
|
|
|
|
|
|
#define constants
|
|
self.BT_Storage = collections.deque([]) #store magnetic field and temperature data
|
|
self.BT_Replay_Storage = collections.deque([]) #store magnetic field and temperature data from BT_Storage deque for Replay functionality
|
|
self.Qslider_pos = 1 #Position of the replay Qslider in [0,100]
|
|
self.ShowLiveData = True #If True then live data is plotted in the 3D plot. If False then Replay-Mode is activated
|
|
self.Replay_Play = False #Play / Pause state for replay function
|
|
self.Replay_speed_value = 1 #Replay speed
|
|
self.Show_sc_phase_value = True #If True: Show sc area in 3D plot
|
|
self.t = [time.time()] #store timestamps
|
|
self.Npoints = 200 #number of point to plot
|
|
self.Tscale_min = 0 #Max T value for colors in 3d plot
|
|
self.Tscale_max = 20 #Max T value for colors in 3d plot
|
|
self.Tc_value = 9.2 #Value of Tc for plotting dashed line and coloring superconducting state
|
|
self.Saved_seconds_value = 30 #Number of seconds in which the last measurements are saved for replay functionality
|
|
self.CernoxPositions = np.linspace(1/16*10,15/16*10,8) #Cernox positions in cm
|
|
self.running = True #true while app is running
|
|
self.disable_plot = False #constant to disable plot to improve performance. Is changed by checkbox checkBox_disableplots
|
|
self.timing_save = 5 #save intervall [s]
|
|
self.set_old = [0,0,1] #variable to save the 'old' set values to compare them to the global variables. It differs from set_new in the first iteration. This ensures that new parameters are send to the device
|
|
self.set_new = [0,0,0] #variable to save the new set values to compare them to the old ones
|
|
self.lines_config_float = [self.T_scale_min, self.T_scale_max, self.B_scale_min, self.B_scale_max, self.Tc, self.Replay_speed, self.Saved_seconds]#is used for config file
|
|
self.lines_config_strings = []#is used for config file
|
|
self.checkboxes_config = [self.checkBox_disableplots,self.Show_sc_phase, self.Show_gradient_fit]#is used for config file
|
|
|
|
#read default values from config and set them in gui
|
|
# self.read_default()
|
|
|
|
|
|
def start_meas(self):
|
|
self.bla=0 #Variable for dummy measurements
|
|
|
|
#establish connection to global variables}
|
|
try: #try to connect to global variables
|
|
manager = multiprocessing.managers.BaseManager(address=('localhost',5001), authkey=b'')
|
|
manager.connect()
|
|
# manager.register('sync_LS_218')
|
|
manager.register('sync_converted')
|
|
# self.sync_LS_218 = manager.sync_LS_218()
|
|
self.sync_converted = manager.sync_converted()
|
|
except: #open global variables, if no connection can be made (i.e. it is not running). Then connect to it
|
|
# subprocess.call(['D:\\Python instrument drivers\\env\\Scripts\\python.exe', 'D:\\Python instrument drivers\\StandAlones\\global_variables.py'])
|
|
self.global_vars = QProcess()
|
|
self.global_vars.start(self.current_dir+"\\.venv\\Scripts\\python.exe", [self.current_dir+'\\global_variables_TF.py'])
|
|
manager.connect()
|
|
# manager.register('sync_LS_218')
|
|
manager.register('sync_converted')
|
|
# self.sync_LS_218 = manager.sync_LS_218()
|
|
self.sync_converted = manager.sync_converted()
|
|
print('!!!\nI opened global variables myself. If you close me, global variables will shut down too. Consider starting global variables in own instance for more security\n!!!')
|
|
|
|
|
|
#fill in variables, in case they are not defined in global variables
|
|
self.sync_converted.update({'T':[0,0,0,0,0,0,0,0]})
|
|
|
|
|
|
self.worker = Worker(self.update_all)
|
|
self.worker.signals.progress.connect(self.update_gui)
|
|
self.threadpool.start(self.worker)
|
|
|
|
def update_all(self, progress_callback):
|
|
#get values from device and write them to global variables. Checks if global variables changed from last iteration. Also pass it to upddate_gui with emit(T)
|
|
|
|
|
|
while self.running == True:
|
|
|
|
#Get magnetic field data from AMR-sensors:
|
|
AMR_x = self.sync_converted.get('AMR_x')
|
|
AMR_y = self.sync_converted.get('AMR_y')
|
|
AMR_z = self.sync_converted.get('AMR_z')
|
|
|
|
Array_B = [] #List with [[AMR1_x, AMR1_y, AMR1_z], [AMR2_x, AMR2_y, AMR2_z], [AMR3_x, AMR3_y, AMR3_z], ...]
|
|
for i in range(0,15):
|
|
B = np.array([AMR_x[i], AMR_y[i], AMR_z[i]])
|
|
Array_B.append(B)
|
|
|
|
|
|
#Get temperature data from sync_converted dict
|
|
Array_T = self.sync_converted.get('T')
|
|
|
|
#DUMMY DATA FOR TEST
|
|
# Array_B = []
|
|
# for i in range(0,15):
|
|
# B = np.array([1+0.5*np.sin(self.bla),np.sin(self.bla),np.cos(self.bla)])
|
|
# Array_B.append(B)
|
|
|
|
# Array_T = []
|
|
# for i in range(0,8):
|
|
# T = 10+5*np.sin(i*0.5+self.bla*0.2)
|
|
# Array_T.append(T)
|
|
|
|
self.bla = self.bla + 0.1
|
|
|
|
time.sleep(0.03)
|
|
progress_callback.emit([Array_B, Array_T]) #Emits list of all B and T measurements
|
|
# self.set_old = self.set_new[:] #List needs to be sliced so that only values are taken and not just a pointer is created
|
|
|
|
|
|
# del(self.BK) #disconnect device when self.running is set to False
|
|
|
|
def update_gui(self, List):
|
|
#Convert List into original format
|
|
Array_B = List[0]
|
|
Array_T = List[1]
|
|
currenttime = time.time()
|
|
|
|
#save newest data + timestamp in queues
|
|
self.BT_Storage.append([currenttime, Array_B, Array_T])
|
|
|
|
#check for Saved_seconds limit in BT_Storage. In case of older measurements -> Delete them
|
|
needfordelete = True
|
|
i = 0
|
|
while needfordelete and i<=len(self.BT_Storage): #Just if needfordelete = True: Check and delete old entries
|
|
timestamp = self.BT_Storage[i][0]
|
|
|
|
if timestamp < currenttime - self.Saved_seconds_value: #if timestamp is older than current time minus saved_seconds_value
|
|
self.BT_Storage.popleft() #Delete oldest entry
|
|
else:
|
|
needfordelete = False #If all entries are within the time interval: No need for continue with deleting-for-loop
|
|
|
|
i=i+1
|
|
|
|
|
|
if self.ShowLiveData == True: #Show live measured data
|
|
self.update_Plots([currenttime, Array_B, Array_T])
|
|
self.Replay_Play = False
|
|
else: #Show replay data (according to position of Qslider)
|
|
if self.Replay_Play == False: #"Slider Mode" without playback
|
|
self.latest_time_in_Replay_Storage = self.BT_Replay_Storage[-1][0] #show the newest timestamp in BT_Replay_Storage
|
|
self.oldest_time_in_Replay_Storage = self.BT_Replay_Storage[0][0] #show the newest timestamp in BT_Replay_Storage
|
|
timediff = self.latest_time_in_Replay_Storage - self.oldest_time_in_Replay_Storage
|
|
self.slider_time = self.latest_time_in_Replay_Storage -(1-self.Qslider_pos)*timediff
|
|
index = self.get_deque_index_closest_to_timestamp(self.BT_Replay_Storage, self.slider_time) #index closest to slider_time
|
|
Array_B = self.BT_Replay_Storage[index][1]
|
|
Array_T = self.BT_Replay_Storage[index][2]
|
|
self.update_Plots([self.slider_time, Array_B, Array_T])
|
|
|
|
self.last_pause_time = time.time()
|
|
|
|
else: #"Playback Mode" with set playback speed
|
|
|
|
play_time = self.slider_time + (currenttime - self.last_pause_time)*self.Replay_speed_value
|
|
if play_time >= self.latest_time_in_Replay_Storage: #If play_time reaches the end of BT_Replay_Storage start from beginning
|
|
self.last_pause_time = time.time()
|
|
|
|
index = self.get_deque_index_closest_to_timestamp(self.BT_Replay_Storage, play_time) #index closest to play_time
|
|
Array_B = self.BT_Replay_Storage[index][1]
|
|
Array_T = self.BT_Replay_Storage[index][2]
|
|
self.update_Plots([play_time, Array_B, Array_T])
|
|
|
|
|
|
def update_Plots(self,value):
|
|
|
|
timestamp = value[0]
|
|
Array_B = value[1]
|
|
Array_T = value[2]
|
|
|
|
B_label_Array = [self.B_3,self.B_2,self.B_1,
|
|
self.B_6,self.B_5,self.B_4,
|
|
self.B_9,self.B_8,self.B_7,
|
|
self.B_12,self.B_11,self.B_10,
|
|
self.B_15,self.B_14,self.B_13]
|
|
|
|
|
|
for i,Vector in enumerate(self.VectorArray):
|
|
B = Array_B[i]
|
|
|
|
Vector.change_direction(B)
|
|
# Vector.change_length(1+0.5*np.sin(self.bla+i/10))
|
|
#Change B in textlabel
|
|
B_total = np.linalg.norm(B)
|
|
B_label_Array[i].setText(str(np.round(B_total,2)))
|
|
|
|
colorarray = []
|
|
T_label_Array = [self.T_1,self.T_2,self.T_3,self.T_4,self.T_5,self.T_6,self.T_7,self.T_8]
|
|
|
|
for i,T_label in enumerate(T_label_Array):
|
|
T = Array_T[i]
|
|
if self.Show_sc_phase_value == True and T <= self.Tc_value:
|
|
red = (T-self.Tscale_min)/(self.Tscale_max - self.Tscale_min)
|
|
blue = 1-(T-self.Tscale_min)/(self.Tscale_max - self.Tscale_min)
|
|
color = np.array([red*0.6,0.6,0.5,1])
|
|
else:
|
|
red = (T-self.Tscale_min)/(self.Tscale_max - self.Tscale_min)
|
|
blue = 1-(T-self.Tscale_min)/(self.Tscale_max - self.Tscale_min)
|
|
color = np.array([red,0,blue,1])
|
|
colorarray.append(color)
|
|
#Change T in textlabel
|
|
T_label.setText(str(np.round(T,2)))
|
|
|
|
|
|
self.Niobium1.change_color(colorarray)
|
|
|
|
|
|
#Update T Plot
|
|
self.plot_1.setData(Array_T,np.flip(self.CernoxPositions))
|
|
self.plot_2.setData([self.Tc_value, self.Tc_value],[0,10]) #Tc Line
|
|
|
|
#Linear fit for temperature gradient
|
|
|
|
GradientParams, GradientCov = curve_fit(linearfunction, self.CernoxPositions, Array_T)
|
|
GradientStd = np.diag(GradientCov)
|
|
self.Line_T_gradient.setText(str(np.round(GradientParams[0],2)) +" +- " +str(np.round(GradientStd[0],2)))
|
|
#Plot Gradient Fit
|
|
self.plot_3.setData(linearfunction(self.CernoxPositions,*GradientParams),np.flip(self.CernoxPositions))
|
|
|
|
#set plotted time in time-label
|
|
self.line_Time_in_plot.setText(str(time.strftime('%H:%M:%S', time.localtime(timestamp))))
|
|
|
|
|
|
def get_deque_index_closest_to_timestamp(self, deque, timestamp): #with timestamps in the 0.st column of deque
|
|
timediffarray = np.empty(shape=(1,0))
|
|
for value in deque:
|
|
timediff = np.abs(value[0]-timestamp) #timedifference in seconds
|
|
timediffarray = np.append(timediffarray, timediff)
|
|
|
|
min_index = np.argmin(timediffarray) #search for the index with the lowest difference in time to timestamp
|
|
return min_index
|
|
|
|
|
|
def set_Qslider_pos(self, value):
|
|
self.Qslider_pos = value/100
|
|
# print(self.Qslider_pos)
|
|
|
|
def set_ShowLiveData(self):
|
|
self.ShowLiveData = self.checkBox_livedata.isChecked()
|
|
if self.ShowLiveData == False:
|
|
self.BT_Replay_Storage = self.BT_Storage.copy() #Write current BT_Storage buffer into Replay_Storage which isnt changed every iteration
|
|
print("saved")
|
|
|
|
def set_Replay_Play(self): #Change state of Replay_Play from True (Play) to False (Pause) and reverse
|
|
if self.Replay_Play == True:
|
|
self.Replay_Play = False
|
|
else:
|
|
self.Replay_Play = True
|
|
|
|
def set_Replay_speed_value(self):
|
|
self.Replay_speed_value = get_float(self.Replay_speed)
|
|
|
|
def set_Show_sc_phase_value(self):
|
|
self.Show_sc_phase_value = self.Show_sc_phase.isChecked()
|
|
|
|
def set_T_scale_min(self):
|
|
self.Tscale_min = get_float(self.T_scale_min)
|
|
# print(self.T_scale_min)
|
|
|
|
def set_T_scale_max(self):
|
|
self.Tscale_max = get_float(self.T_scale_max)
|
|
|
|
def set_Tc_value(self):
|
|
self.Tc_value = get_float(self.Tc)
|
|
|
|
def set_Saved_seconds_value(self):
|
|
self.Saved_seconds_value = get_float(self.Saved_seconds)
|
|
|
|
def set_displot(self):
|
|
#sets variable to disable plot so checkbox state does not need be read out every iteration
|
|
self.disable_plot = self.checkBox_disableplots.isChecked()
|
|
|
|
|
|
def plot_gradient_hide(self):
|
|
#shows or hides gradient fit plot according to the checkbox
|
|
if self.Show_gradient_fit.isChecked() == True:
|
|
self.plot_3.show()
|
|
else:
|
|
self.plot_3.hide()
|
|
|
|
|
|
def set_default(self):
|
|
#saves current set values to txt file in subdirectory configs. All entries that are saved are defined in self.lines_config
|
|
#Overwrites old values in config file.
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
path = current_dir+'\\configs\\BT_3D_Plot_config.txt' #To make shure the config file is at the right place, independent from where the program is started the location of the file is retrieved
|
|
file = open(path,'w')
|
|
for l in self.lines_config_float:
|
|
temp = f"{get_float(l)}"
|
|
file.write(temp+'\t')
|
|
for l in self.lines_config_strings:
|
|
file.write(l.text()+'\t')
|
|
for c in self.checkboxes_config:
|
|
file.write(str(c.isChecked())+'\t')
|
|
file.write('\n')
|
|
file.close
|
|
|
|
def read_default(self):
|
|
#reads default values from config file in subdirectory config and sets the values in gui. Then self.change is set to true so values are send
|
|
#to device. (If no config file exists, it does nothing.)
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
path = current_dir+'\\configs\\BT_3D_Plot_config.txt' #To make shure the config file is read from the right place, independent from where the program is started the location of the file is retrieved
|
|
try: #exit function if config file does not exist
|
|
vals = import_txt.read_raw(path)
|
|
except:
|
|
print('no config file found on')
|
|
print(path)
|
|
return
|
|
formats = ['.2f', '.2f', '.2f','.2f','.2f','.2f','.2f']
|
|
|
|
for l,v,f in zip(self.lines_config_float,vals[0],formats):
|
|
v = float(v) #convert string in txt to float, so number can be formatted according to "formats" when it's set
|
|
l.setText(format(v,f))
|
|
|
|
for l,v in zip(self.lines_config_strings,vals[0][len(self.lines_config_float):]):
|
|
l.setText(v)
|
|
|
|
for c,v in zip(self.checkboxes_config,vals[0][len(self.lines_config_float)+len(self.lines_config_strings):]):
|
|
c.setChecked(v == 'True')
|
|
|
|
self.change = True
|
|
|
|
def closeEvent(self,event): #when window is closed self.running is set to False, so all threads stop
|
|
self.running = False
|
|
time.sleep(1)
|
|
event.accept()
|
|
|
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
window = MainWindow()
|
|
window.show()
|
|
app.exec() |