Beispiel #1
0
    def update(self):
        """
        Update toolbar

        :return:
        """
        NavigationToolbar.update(self)
class CanvasFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, -1, 'CanvasFrame', size=(550, 350))

        self.figure = Figure()
        self.axes = self.figure.add_subplot()
        t = np.arange(0.0, 3.0, 0.01)
        s = np.sin(2 * np.pi * t)

        self.axes.plot(t, s)
        self.canvas = FigureCanvas(self, -1, self.figure)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
        self.SetSizer(self.sizer)
        self.Fit()

        self.add_toolbar()  # comment this out for no toolbar

    def add_toolbar(self):
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        # By adding toolbar in sizer, we are able to put it at the bottom
        # of the frame - so appearance is closer to GTK version.
        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        # update the axes menu on the toolbar
        self.toolbar.update()
class PlotPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        self.fig = Figure((5, 4), 75)
        self.canvas = FigureCanvas(self, -1, self.fig)
        self.toolbar = NavigationToolbar(self.canvas)  # matplotlib toolbar
        self.toolbar.Realize()
        # self.toolbar.set_active([0,1])

        # Now put all into a sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        # This way of adding to sizer allows resizing
        sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        # Best to allow the toolbar to resize!
        sizer.Add(self.toolbar, 0, wx.GROW)
        self.SetSizer(sizer)
        self.Fit()

    def init_plot_data(self):
        a = self.fig.add_subplot(111)

        x = np.arange(120.0) * 2 * np.pi / 60.0
        y = np.arange(100.0) * 2 * np.pi / 50.0
        self.x, self.y = np.meshgrid(x, y)
        z = np.sin(self.x) + np.cos(self.y)
        self.im = a.imshow(z, cmap=cm.RdBu)  # , interpolation='nearest')

        zmax = np.max(z) - ERR_TOL
        ymax_i, xmax_i = np.nonzero(z >= zmax)
        if self.im.origin == 'upper':
            ymax_i = z.shape[0] - ymax_i
        self.lines = a.plot(xmax_i, ymax_i, 'ko')

        self.toolbar.update()  # Not sure why this is needed - ADS

    def GetToolBar(self):
        # You will need to override GetToolBar if you are using an
        # unmanaged toolbar in your frame
        return self.toolbar

    def OnWhiz(self, evt):
        self.x += np.pi / 15
        self.y += np.pi / 20
        z = np.sin(self.x) + np.cos(self.y)
        self.im.set_array(z)

        zmax = np.max(z) - ERR_TOL
        ymax_i, xmax_i = np.nonzero(z >= zmax)
        if self.im.origin == 'upper':
            ymax_i = z.shape[0] - ymax_i
        self.lines[0].set_data(xmax_i, ymax_i)

        self.canvas.draw()
class PlotPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)

        self.fig = Figure((5, 4), 75)
        self.canvas = FigureCanvas(self, -1, self.fig)
        self.toolbar = NavigationToolbar(self.canvas)  # matplotlib toolbar
        self.toolbar.Realize()
        # self.toolbar.set_active([0,1])

        # Now put all into a sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        # This way of adding to sizer allows resizing
        sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        # Best to allow the toolbar to resize!
        sizer.Add(self.toolbar, 0, wx.GROW)
        self.SetSizer(sizer)
        self.Fit()

    def init_plot_data(self):
        a = self.fig.add_subplot(111)

        x = np.arange(120.0) * 2 * np.pi / 60.0
        y = np.arange(100.0) * 2 * np.pi / 50.0
        self.x, self.y = np.meshgrid(x, y)
        z = np.sin(self.x) + np.cos(self.y)
        self.im = a.imshow(z, cmap=cm.RdBu)  # , interpolation='nearest')

        zmax = np.max(z) - ERR_TOL
        ymax_i, xmax_i = np.nonzero(z >= zmax)
        if self.im.origin == 'upper':
            ymax_i = z.shape[0] - ymax_i
        self.lines = a.plot(xmax_i, ymax_i, 'ko')

        self.toolbar.update()  # Not sure why this is needed - ADS

    def GetToolBar(self):
        # You will need to override GetToolBar if you are using an
        # unmanaged toolbar in your frame
        return self.toolbar

    def OnWhiz(self, evt):
        self.x += np.pi / 15
        self.y += np.pi / 20
        z = np.sin(self.x) + np.cos(self.y)
        self.im.set_array(z)

        zmax = np.max(z) - ERR_TOL
        ymax_i, xmax_i = np.nonzero(z >= zmax)
        if self.im.origin == 'upper':
            ymax_i = z.shape[0] - ymax_i
        self.lines[0].set_data(xmax_i, ymax_i)

        self.canvas.draw()
Beispiel #5
0
class Plots_Panel(wx.Panel):
    """

    Panel to hold matplotlib figure. There are three plots inside a grid, big one
    for temperature vs. deformation and smaller ones for time vs. deformation and
    time vs. temperature.

    """

    #--------------------------------------------------------------------------#
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)

        self.init_plots() #make figure
        self.PlotsCanvas = FigCanvas(self, wx.ID_ANY, self.fig)

        self.toolbar = NavigationToolbar(self.PlotsCanvas)
        self.toolbar.Realize()
        
        #correct toolbar size
        tw, th = self.toolbar.GetSizeTuple()
        fw, fh = self.PlotsCanvas.GetSizeTuple()
        
        

        # Sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.PlotsCanvas, 1, wx.EXPAND | wx.GROW)
        sizer.Add(self.toolbar, 0, wx.BOTTOM | wx.GROW)
        self.toolbar.SetSize(wx.Size(fw, th))
        self.toolbar.update()
        self.SetSizerAndFit(sizer)

    #--------------------------------------------------------------------------#
    def init_plots(self):

        self.fig = Figure((-1,7.5))
        self.fig.subplots_adjust(left=0.05, wspace=.3, hspace=3) #sub plot spacing

        gs = matplotlib.gridspec.GridSpec(8,3)
        self.ax1 = self.fig.add_subplot(gs[:,0:2])
        self.ax2 = self.fig.add_subplot(gs[0:4,2])
        self.ax3 = self.fig.add_subplot(gs[4:8,2])

        self.ax1.set_xlabel(u'Temperatura ($^\circ$C)')
        self.ax1.set_ylabel(u'Deformación (mm)')
        self.ax2.set_xlabel(u'Tiempo (s)')
        self.ax2.set_ylabel(u'Deformación (mm)')
        self.ax3.set_xlabel(u'Tiempo (s)')
        self.ax3.set_ylabel(u'Temperatura ($^\circ$C)')
Beispiel #6
0
class Plots_Panel(wx.Panel):
    """

    Panel to hold matplotlib figure. There are three plots inside a grid, big one
    for temperature vs. deformation and smaller ones for time vs. deformation and
    time vs. temperature.

    """

    #--------------------------------------------------------------------------#
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)

        self.init_plots()  #make figure
        self.PlotsCanvas = FigCanvas(self, wx.ID_ANY, self.fig)

        self.toolbar = NavigationToolbar(self.PlotsCanvas)
        self.toolbar.Realize()

        #correct toolbar size
        tw, th = self.toolbar.GetSizeTuple()
        fw, fh = self.PlotsCanvas.GetSizeTuple()

        # Sizers
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.PlotsCanvas, 1, wx.EXPAND | wx.GROW)
        sizer.Add(self.toolbar, 0, wx.BOTTOM | wx.GROW)
        self.toolbar.SetSize(wx.Size(fw, th))
        self.toolbar.update()
        self.SetSizerAndFit(sizer)

    #--------------------------------------------------------------------------#
    def init_plots(self):

        self.fig = Figure((-1, 7.5))
        self.fig.subplots_adjust(left=0.05, wspace=.3,
                                 hspace=3)  #sub plot spacing

        gs = matplotlib.gridspec.GridSpec(8, 3)
        self.ax1 = self.fig.add_subplot(gs[:, 0:2])
        self.ax2 = self.fig.add_subplot(gs[0:4, 2])
        self.ax3 = self.fig.add_subplot(gs[4:8, 2])

        self.ax1.set_xlabel(u'Temperatura ($^\circ$C)')
        self.ax1.set_ylabel(u'Deformación (mm)')
        self.ax2.set_xlabel(u'Tiempo (s)')
        self.ax2.set_ylabel(u'Deformación (mm)')
        self.ax3.set_xlabel(u'Tiempo (s)')
        self.ax3.set_ylabel(u'Temperatura ($^\circ$C)')
Beispiel #7
0
def AddToolbar(parent, sizer):
    toolbar = NavigationToolbar(parent)
    toolbar.Realize()
    if wx.Platform == '__WXMAC__':
        # Mac platform (OSX 10.3, MacPython) does not seem to cope with
        # having a toolbar in a sizer. This work-around gets the buttons
        # back, but at the expense of having the toolbar at the top
        parent.SetToolBar(toolbar)
    else:
        # On Windows platform, default window size is incorrect, so set
        # toolbar width to figure width.
        tw, th = toolbar.GetSizeTuple()
        fw, fh = parent.GetSizeTuple()
        # By adding toolbar in sizer, we are able to put it at the bottom
        # of the frame - so appearance is closer to GTK version.
        # As noted above, doesn't work for Mac.
        toolbar.SetSize(wx.Size(fw, th))
        sizer.Add(toolbar, 0, wx.LEFT | wx.EXPAND)
    # update the axes menu on the toolbar
    toolbar.update()
    return toolbar
Beispiel #8
0
def AddToolbar(parent,sizer):
  toolbar = NavigationToolbar(parent)
  toolbar.Realize()
  if wx.Platform == '__WXMAC__':
    # Mac platform (OSX 10.3, MacPython) does not seem to cope with
    # having a toolbar in a sizer. This work-around gets the buttons
    # back, but at the expense of having the toolbar at the top
    parent.SetToolBar(toolbar)
  else:
    # On Windows platform, default window size is incorrect, so set
    # toolbar width to figure width.
    tw, th = toolbar.GetSizeTuple()
    fw, fh = parent.GetSizeTuple()
    # By adding toolbar in sizer, we are able to put it at the bottom
    # of the frame - so appearance is closer to GTK version.
    # As noted above, doesn't work for Mac.
    toolbar.SetSize(wx.Size(fw, th))
    sizer.Add(toolbar, 0, wx.LEFT | wx.EXPAND)
  # update the axes menu on the toolbar
  toolbar.update()
  return toolbar
Beispiel #9
0
class AppFrame(wx.Frame):
	def __init__(self):
		setBaseDB('dass.db')
		initializeBaseDB()
		
		def makeSP(name, labels, statictexts = None):
			panel = wx.Panel(self.sidePanel, -1)
			box = wx.StaticBox(panel,-1, name)
			sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
			
			panel.rangeValue = {}

			for name, min_value, value, max_value in labels:
				sp = SpinPanel(panel, name, min_value, value, max_value, self.OnSpinback)
				panel.rangeValue[name] = sp
				sizer.Add(sp, 0, wx.EXPAND)

			print "done"
			panel.SetSizer(sizer)
			return panel

		wx.Frame.__init__(self, None, title = "CLUSTERING ALGORITHM")

		################ Adding Drawing Panel ################

		self.displayPanel = DisplayPanel(self)
		self.figure = Figure()
		self.axes = self.figure.add_subplot(111)
		self.axes.set_axis_bgcolor('white')
		self.canvas = Canvas(self.displayPanel, -1, self.figure)

		################ Connecting the canvas to events for selecting intial centroid & nuclei ###################
		self.figure.canvas.mpl_connect('button_press_event', self.onGraphClick)
		self.figure.canvas.mpl_connect('pick_event', self.onNucleiPick)
		################ Connecting the canvas to events for selecting intial centroid & nuclei ###################
		
		sizer = wx.BoxSizer(wx.HORIZONTAL)
		sizer.Add(self.canvas, 1, wx.EXPAND)
		self.displayPanel.SetSizer(sizer)
		self.displayPanel.Fit()
		self.toolbar = NavigationToolbar(self.canvas)
		self.SetToolBar(self.toolbar)
		self.toolbar.Realize()
		tw, th = self.toolbar.GetSizeTuple()
		fw, fh = self.canvas.GetSizeTuple()
		self.toolbar.SetSize(wx.Size(fw, th))
		self.toolbar.update()
		self.redraw_timer = wx.Timer(self)
		self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
		self.stopStart = threading.Event()
		self.data = None
		self.centroids = []
		self.clusterIds = None
		self.colors = None
		self.k = 0
		self.iterTimer = 0
		self.iterations = 20
		self.sidePanel = wx.Panel(self)
		self.pointToPick = 0
		self.selectedFeaturesCount = 0
		self.isPickCentroids = False

		################ Adding Drawing Panel ################

		################ defining features for clustering algorithm ################

		self.dictionary = {'Area':'AREA', 'Perimeter':'PERIMETER',
							'Roundness':'ROUNDNESS','Equi-Diameter':'EQUI_DIAMETER',
							'Orientation':'ORIENTATION',
							'Convex Area':'CONVEX_AREA', 'Solidity': 'SOLIDITY',
							'Major Axis':'MAJOR_AXIS_LEN','Minor Axis': 'MINOR_AXIS_LEN',
							'Eccentricity':'ECCENTRICITY', 'Min Enclosing Rad':'CIR_RADIUS',
							'Shape Index':'SHAPE_INDEX','Border Index':'BORDER_INDEX','Aspect Ratio':'ASPECT_RATION', 
							'Mean Pixel Intensity':'MEAN_PIXEL_DEN',
							'Max Pixel Intensity':'MAX_PIXEL_DEN','Min Pixel Intensity':'MIN_PIXEL_DEN' 
							}

		featureList = ['Area', 'Perimeter', 'Roundness', 'Equi-Diameter','Orientation', 
						'Convex Area', 'Solidity', 'Major Axis', 'Minor Axis',
						'Eccentricity','Min Enclosing Rad','Shape Index','Border Index','Aspect Ratio',
						'Mean Pixel Intensity','Max Pixel Intensity', 'Min Pixel Intensity']

		################ defining features for clustering algorithm ################


		################ Adding File Open Dialog to Show the tiles Info ################ 

		self.chooseImagePanel = wx.Panel(self.sidePanel, -1)
		box = wx.StaticBox(self.chooseImagePanel, -1, 'Choose Image')
		self.subPanel = wx.Panel(self.chooseImagePanel, -1)

		sizer = wx.BoxSizer(wx.VERTICAL)
		self.filebrowser = filebrowse.FileBrowseButtonWithHistory(
							self.subPanel, -1, size=(450, -1), changeCallback = self.updateHistory)

		button = wx.Button(self.subPanel, -1, "View Image")
		button.Bind(wx.EVT_BUTTON, self.viewImage)

		sizer.Add(self.filebrowser,0,wx.EXPAND)
		sizer.Add(button,0,wx.ALL|wx.RIGHT|wx.ALIGN_RIGHT,5)
		self.subPanel.SetSizer(sizer)

		sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
		sizer.Add(self.subPanel)
		self.chooseImagePanel.SetSizer(sizer)

		################ Adding File Open Dialog to Show the tiles Info ################ 

		################ Adding Algorithm Options Info ################ 

		self.algorithmPanel = wx.Panel(self.sidePanel, -1)
		box = wx.StaticBox(self.algorithmPanel, -1, 'Algorithms')
		self.subPanel = wx.Panel(self.algorithmPanel, -1)
		sizer = wx.BoxSizer(wx.HORIZONTAL)
		
		self.algorithm1 = wx.RadioButton(self.subPanel, label="K-MEANS", style = wx.RB_GROUP)		
		self.algorithm2 = wx.RadioButton(self.subPanel, label="OPTICS")
		self.algorithm1.Bind(wx.EVT_RADIOBUTTON, self.kmeansSelected)
		self.algorithm2.Bind(wx.EVT_RADIOBUTTON, self.opticsSelected)

		sizer.Add(self.algorithm1 ,0,wx.ALL|wx.RIGHT|wx.ALIGN_LEFT, 5)
		sizer.Add((1,1),1)
		sizer.Add((1,1),1)
		sizer.Add(self.algorithm2 ,1,wx.ALL|wx.RIGHT|wx.ALIGN_RIGHT,5)
		self.subPanel.SetSizer(sizer)

		sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
		sizer.Add(self.subPanel)
		self.algorithmPanel.SetSizer(sizer)

		################ Adding Algorithm Options Info ################ 

		################ Adding Features Panel ################ 

		self.featuresPanel = wx.Panel(self.sidePanel, -1)
		box = wx.StaticBox(self.featuresPanel, -1, 'Features')
		self.subPanel = wx.Panel(self.featuresPanel, -1)
		sizer = wx.GridSizer(rows = 6, cols = 3)

		global featureCB

		for feature in featureList:
			cb = wx.CheckBox(self.subPanel, label=feature)
			cb.Bind(wx.EVT_CHECKBOX, self.featuresSelected)
			featureCB[feature] = cb
			sizer.Add(cb, 0, wx.BOTTOM|wx.LEFT, 2)
		
		self.subPanel.SetSizer(sizer)

		sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
		sizer.Add(self.subPanel)
		self.featuresPanel.SetSizer(sizer)

		################ Adding Features Panel ################ 

		################ Adding Feature1, Feature2 Range Value ################ 

		self.feature1 = makeSP('FEATURE 1 RANGES',
							  (('Minimum', 1, 1, 3600),
								('Maximum', 1, 3600, 3600)))

		self.feature2 = makeSP('FEATURE 2 RANGES',
							  (('Minimum', 1, 1, 3600),
								('Maximum', 1, 3600, 3600)))

		################ Adding Feature1, Feature2 Range Value ################ 

		################ Adding all the panels to the main window ################ 

		self.optionPanel = OptionsPanel(self,self.sidePanel, self.displayPanel)
		self.buttonStart = wx.Button(self.sidePanel, -1, "Start")
		self.buttonStart.Bind(wx.EVT_BUTTON, self.startProcess)

		buttonClose = wx.Button(self.sidePanel, -1, "Close")
		buttonClose.Bind(wx.EVT_BUTTON, self.stopProcess)

		self.buttonGenerate = wx.Button(self.sidePanel, -1, "Generate Image")
		self.buttonGenerate.Bind(wx.EVT_BUTTON, self.generateImage)
		self.buttonReset = wx.Button(self.sidePanel, -1, "Show Image/Reset")
		self.buttonReset.Bind(wx.EVT_BUTTON, self.resetProcess)

		self.feature1.Enable(False)
		self.feature2.Enable(False)
		self.buttonStart.Enable(False)
		self.buttonGenerate.Enable(False)
		self.algorithmPanel.Enable(False)
		self.optionPanel.Enable(False)
		self.buttonReset.Enable(False)

		panelSizer = wx.BoxSizer(wx.VERTICAL)
		panelSizer.Add(self.chooseImagePanel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		panelSizer.Add(self.featuresPanel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		panelSizer.Add(self.feature1, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		panelSizer.Add(self.feature2, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		panelSizer.Add(self.algorithmPanel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		panelSizer.Add(self.optionPanel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)

		sizer = wx.BoxSizer(wx.HORIZONTAL)
		sizer.Add(self.buttonStart ,0,wx.ALL|wx.RIGHT|wx.ALIGN_LEFT, 5)
		# sizer.Add((1,1),1)
		sizer.Add(self.buttonGenerate ,0,wx.ALL|wx.RIGHT|wx.ALIGN_CENTER,5)
		# sizer.Add((1,1),1)
		sizer.Add(self.buttonReset,0,wx.ALL|wx.RIGHT|wx.ALIGN_CENTER,5)
		
		sizer.Add(buttonClose ,0,wx.ALL|wx.RIGHT|wx.ALIGN_RIGHT,5)

		panelSizer.Add(sizer,0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		# panelSizer.Add(buttonStart, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		# panelSizer.Add(buttonClose, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5)
		self.sidePanel.SetSizer(panelSizer)

		mainSizer = wx.BoxSizer(wx.HORIZONTAL)
		mainSizer.Add(self.displayPanel, 1, wx.EXPAND)
		mainSizer.Add(self.sidePanel, 0, wx.EXPAND)
		self.SetSizer(mainSizer)

		mainSizer.Fit(self)
		rw, rh = self.displayPanel.GetSize()
		sw, sh = self.sidePanel.GetSize()
		fw, fh = self.GetSize()
		h = max(600, fh)
		w = h + fw - rw

		if verbose:
			print 'Display Panel Size', (rw, rh)
			print 'Side Panel Size', (sw, sh)
			print 'Frame Size', (fw, fh)

		self.SetSize((w,h))
		self.Show()

		################ Adding all the panels to the main window ################

	################################################################################# 
	# featureSelected - event handler called when the feature check box is selected #
	#################################################################################
	
	def featuresSelected(self,event):
		if event.IsChecked():
			self.selectedFeaturesCount += 1
			if self.selectedFeaturesCount > 1 :
				self.optionPanel.Enable(True)
				self.algorithmPanel.Enable(True)
				self.buttonGenerate.Enable(True)
				self.buttonReset.Enable(True)
				self.feature1.Enable(True)
				self.feature2.Enable(True)
				if (self.algorithm1.GetValue()):
					self.buttonStart.Enable(True)

			if self.selectedFeaturesCount == 1:
				self.feature1.Enable(True)
				for key in featureCB.keys():
					if featureCB[key].GetValue() and key == "Mean Pixel Intensity":
						self.optionPanel.Enable(True)
						self.algorithmPanel.Enable(True)
						self.buttonGenerate.Enable(True)
						self.buttonStart.Enable(True)
						print key

		else:
			self.selectedFeaturesCount -= 1
			if self.selectedFeaturesCount <=1 :
				self.optionPanel.Enable(False)
				self.algorithmPanel.Enable(False)
				self.buttonStart.Enable(False)
				self.buttonGenerate.Enable(False)
				self.buttonReset.Enable(False)						
				self.feature1.Enable(False)
				self.feature2.Enable(False)
				self.axes.clear()
				self.canvas.draw()
			if self.selectedFeaturesCount == 1:
				for key in featureCB.keys():
					if featureCB[key].GetValue() and key == "Mean Pixel Intensity":
						self.optionPanel.Enable(True)
						self.algorithmPanel.Enable(True)
						self.buttonGenerate.Enable(True)
						self.buttonStart.Enable(True)
						print key
				self.feature1.Enable(True)


	def OnSpinback(self, name, value):
		if verbose:
			print 'On Spin Back', name, value



	################################################
	# stopProcess - event handler for close button #
	################################################
	
	def stopProcess(self,event):
		closeConnBaseDB()
		sys.exit()


	############################################################
	# resetProcess - event handler for reset/show image button #
	############################################################

	def resetProcess(self,event):
		self.axes.clear()
		self.canvas.draw()
		self.pointToPick = int(self.optionPanel.textBox.GetValue())
		if self.algorithm1.GetValue():
			self.buttonStart.Enable(True)
		else:
			self.buttonStart.Enable(False)

		featureList = []
		for key in featureCB.keys():
			if featureCB[key].GetValue():
				featureList.append(key)

		print featureList
		self.filename = self.filebrowser.GetValue().split('/')[-1].split('.')[0]
		self.fullFilename = self.filebrowser.GetValue()
		datalist = getFeatures(self.filename, self.dictionary[featureList[0]],
					 self.dictionary[featureList[1]])
		self.data = vstack([(f1,f2) for (f,n, f1, f2) in datalist])
		self.init_plot(self.data, self.k, featureList[0], featureList[1])
		self.centroids = []
		
	#####################################################################################
	# updateHistory - event handler for file open dialog to store recently opened files #
	#####################################################################################

	def updateHistory(self, event):

		self.featuresPanel.Enable(True)
		value = self.filebrowser.GetValue()
		print 'Update History',value
		if not value:
			return
		history = self.filebrowser.GetHistory()
		if value not in history:
			history.append(value)
			self.filebrowser.SetHistory(history)
			self.filebrowser.GetHistoryControl().SetStringSelection(value)


	##################################################################
	# viewImage - event handler for View Image button which displays #
	# 			  the image selected in the open dialog  #
	##################################################################
	def viewImage(self,event):
		print 'View Image'
		imageFile = self.filebrowser.GetValue()
		print imageFile
		win = wx.Frame(None, title = imageFile, size=(500,500), 
						style = wx.DEFAULT_FRAME_STYLE ^wx.RESIZE_BORDER) 
		ipanel = wx.Panel(win, -1)

		image = wx.ImageFromBitmap(wx.Bitmap(imageFile))
		image = image.Scale(500,500, wx.IMAGE_QUALITY_HIGH)
		bitmap = wx.BitmapFromImage(image)

		control = wx.StaticBitmap(ipanel,-1,bitmap)
		control.SetPosition((10,10))
		win.Show(True)


	#################################################################
	# generateImage - event handler for Generate Image image button #
	#################################################################

	def generateImage(self,event):
		print 'generate Image'
		self.generate_image()

	##############################################################################
	# generate_image -  method for generating an image of the tiles	   	     #
	#		    with mapping of clustered output i.e. each		     #
	#		    cluster is mapped in the tile image with specific colors # 
	##############################################################################

	def generate_image(self):
	#Red,Green,Blue,Marooon,Cyan,Yellow,Magenta,Purple,Navy,Gray(BGR format NOT RGB)
		#colors = {0:(0,0,255),1:(0,255,0),2:(255,0,0),3:(0,0,128),4:(255,255,0),5:(0,255,255),6:(255,0,255),7:(128,0,128)
		#		,8:(128,0,0),9:(128,128,128)}
		colors = [(0,0,255), (0,255,0), (255,0,0)]
		for i in range(self.k):
			colors.append((random.choice(range(256)), random.choice(range(256)),random.choice(range(256))))
		conn = getConnBaseDB()
		#Query DB to get boundary values for this image.
		c = conn.cursor()
		c.execute("SELECT BOUNDARY_VALS FROM Features WHERE IMAGE_NAME = '%s'" %self.filename)
		rows = c.fetchall()
		contour_list = []
		for row in rows:
			boundary_vals = row[0].strip().split(';')
			boundary_vals.pop()
			boundary_vals = [[int(float(n)) for n in i.split(',')] for i in boundary_vals]
			contr = np.array(boundary_vals)
			contour_list.append(contr)

		print self.fullFilename
		im = cv2.imread(self.fullFilename)
		for index,i in enumerate(self.clusterIds):
			cv2.drawContours(im,contour_list,index,colors[i],-1)

		outputfile = "Output_"+self.filename+"_clustered_"+str(int(time.time()))+".tif"
		cv2.imwrite(outputfile,im);


		print "'Done Displaying the image"
		imageFile = outputfile
                print imageFile
                win = wx.Frame(None, title = imageFile, size=(500,500),
                                                style = wx.DEFAULT_FRAME_STYLE ^wx.RESIZE_BORDER)
                ipanel = wx.Panel(win, -1)

		image = wx.ImageFromBitmap(wx.Bitmap(imageFile))
		image = image.Scale(500,500, wx.IMAGE_QUALITY_HIGH)
		
		bitmap = wx.BitmapFromImage(image)
		control = wx.StaticBitmap(ipanel,-1,bitmap)
		control.SetPosition((10,10))
		win.Show(True)

		#cv2.imshow('image',im)
		#print "show window"
		#cv2.waitKey()
		print "byee"
		#closeConnBaseDB()
		#return(im)



	######################################################################
	# pickInputCentroids -  method for indicating whether to pick intial #
	#			centroids or not 			     #
	######################################################################

	def pickInputCentroids(self,userCentroid):
		self.pointToPick = int(self.optionPanel.textBox.GetValue())
		if userCentroid:
			self.buttonStart.Enable(False)
		else:
			self.buttonStart.Enable(True)


	##########################################################################
	# opticsSelected - radio button event for selecting OPTICS algortithm 	 #
	##########################################################################
	def opticsSelected(self,event):
		self.optionPanel.showUpdates.Enable(False)
		self.optionPanel.chooseCentroid.Enable(False)
		self.buttonStart.Enable(True)
		self.buttonGenerate.Enable(False)


	#######################################################################
	# kmeansSelected -  adio button event for selecting KMEANS algortithm #
	#######################################################################
	def kmeansSelected(self,event):
		self.optionPanel.showUpdates.Enable(True)
		self.optionPanel.chooseCentroid.Enable(True)
		self.buttonGenerate.Enable(True)
		if self.optionPanel.chooseCentroid.GetValue() and self.pointToPick == 0:
			self.buttonStart.Enable(True)
		elif not self.optionPanel.chooseCentroid.GetValue():
			self.buttonStart.Enable(True)
		


	######################################################################
	# onGraphClick -  mouse click event handler for selecting intial set #
	#				   of centroids from user  	     #
	######################################################################
	def onGraphClick(self,event):
		print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
			event.button, event.x, event.y, event.xdata, event.ydata)

		print self.optionPanel.chooseCentroid.GetValue()
		print self.pointToPick

		if self.optionPanel.chooseCentroid.GetValue() and self.pointToPick > 0:
			self.pointToPick -= 1
			#self.axes.plot(event.xdata,event.ydata, color='b',picker=5)
			self.axes.plot(event.xdata,event.ydata,color='r', marker='H',markersize=20.0,picker=5)
			self.canvas.draw()
			self.centroids.append([event.xdata,event.ydata])
			#np.concatenate((self.centroids,[event.xdata,event.ydata]))
			if self.pointToPick == 0 :
				self.buttonStart.Enable(True)



	######################################################################
	# onNucleiPick -  mouse click event handler for showing the metadata #
	#				  of selected nuclei 		     #
	######################################################################
	def onNucleiPick(self,event):
		thisline = event.artist
		xdata = thisline.get_xdata()
		ydata = thisline.get_ydata()
		ind = event.ind
		print thisline
		print event
		featureList = []
		featureKeys = ['IMAGE FILE','ID','AREA','PERIMETER','ROUNDNESS','EQUI_DIAMETER','CONVEX_AREA','SOLIDITY','MAJOR_AXIS_LEN','MINOR_AXIS_LEN','ORIENTATION','ECCENTRICITY',
				'CIR_RADIUS','SHAPE_INDEX','BORDER_INDEX','ASPECT_RATIO','MAX_PIXEL_DEN','MIN_PIXEL_DEN']
		for key in featureCB.keys():
			if featureCB[key].GetValue():
				featureList.append(key)
	
		print "xdata",xdata[ind][0]
		print "ydata",ydata[ind][0]
	
		dataList = getAllFeatures(self.filename, self.dictionary[featureList[0]],
			self.dictionary[featureList[1]],float(xdata[ind][0]),float(ydata[ind][0]));
		
		print dataList
		data = dataList[0]
		print len(data)
		win = wx.Frame(None, title = 'Nuclei Metadata', size=(500,500),
                                                style = wx.DEFAULT_FRAME_STYLE ^wx.RESIZE_BORDER)
		
		print "Creating New Frame"
		sizer = wx.GridSizer(rows = len(data), cols=2)	
		ipanel = wx.Panel(win, -1)
		for i in range (0,len(data)):
			label = wx.StaticText(ipanel, -1, featureKeys[i])
			sizer.Add(label, 0, wx.BOTTOM|wx.LEFT, 2)
			labCtrl = wx.TextCtrl(ipanel, -1, str(data[i]), style = wx.TE_READONLY)
			sizer.Add(labCtrl, 0, wx.BOTTOM|wx.LEFT, 2)
		ipanel.SetSizer(sizer)
		boxSizer = wx.BoxSizer(wx.VERTICAL)
		boxSizer.Add(ipanel)
		win.SetSizer(boxSizer)
		win.Fit()	
		win.Show(True)


	###########################################################################
	# startProcess -  event handler for start button 			  #
	#		  It initiates the process of clustering  		  #
	###########################################################################		
	def startProcess(self, event):
		print 'in kmeans'
		featureList =[]
		isShowUpdate = False
		k = self.optionPanel.textBox.GetValue()
		isShowUpdate = self.optionPanel.showUpdates.GetValue()
		isKmeans = self.algorithm1.GetValue()
		isOptics = self.algorithm2.GetValue()
		self.isPickCentroids = self.optionPanel.chooseCentroid.GetValue()

		print 'spin_panels', spinPanels.keys()
		for key in featureCB.keys():
			if featureCB[key].GetValue():
				featureList.append(key)
				print featureCB[key].GetValue()

		print "Feature List = ",featureList
		print "k-value = ",k
		print "Show Update = ",isShowUpdate
		print "Kmeans Selected= ",isKmeans
		print "OPTICS Selected= ",self.algorithm2.GetValue()

		self.buttonReset.Enable(True)
		self.buttonStart.Enable(False)

		for key in spinPanels.keys():
			print self.feature1.rangeValue[key].sc.GetValue()
			print self.feature2.rangeValue[key].sc.GetValue()

		print self.filebrowser.GetValue()
		self.filename = self.filebrowser.GetValue().split('/')[-1].split('.')[0]
		self.fullFilename = self.filebrowser.GetValue()
		if isKmeans:	
			#start the kmean process
			self.k = int(k)
			if not 'Mean Pixel Intensity' in featureList and len(featureList) > 2:
				print 'Performing kmeans for multiple features'
				fList = []
				for item in featureList:
					fList.append(self.dictionary[item])

				dataList = getFeaturesList(self.filename,fList)
				#print dataList
				
				list1 = []
				list2 = []
				for index,j in enumerate(fList):
					list1.append('f'+str(index))
				
				list2 = ['f','n'] + list1
				new_list = [map(float,list2[2:])  for list2 in dataList]
				#new_list = [(float(j) for j in i)for i in new_list]
					
				#print "new_list",new_list
				#self.data = vstack([list1  for list2 in dataList])
				self.data = vstack(new_list)
				#print self.data	
				self.init_plot(self.data, self.k, featureList[0], featureList[1])
				self.cluster_kmeansMul(fList, self.k, False)

			elif not 'Mean Pixel Intensity' in featureList and len(featureList) == 2:
				print self.filename
				datalist = getFeatures(self.filename, self.dictionary[featureList[0]],
							 self.dictionary[featureList[1]])
				#print datalist
				self.data = vstack([(f1,f2) for (f,n, f1, f2) in datalist])
				#Intial clusters from selecting points -- start


				#Intial clusters from selecting points -- end
				self.animation = isShowUpdate
				self.init_plot(self.data, self.k, featureList[0], featureList[1])
				time.sleep(1)
				if not self.animation:
					self.cluster_kmeans(datalist, self.k, False)
				else:
					print "isPickCentroids =",self.isPickCentroids
					print "initial centroisds = ", self.centroids
					if not self.isPickCentroids:
						self.centroids = self.init_cluster()
					self.iterTimer = 0
					self.redraw_timer.Start(2000)
			else:
				self.data = vstack(self.helper_mean())
				self.pixel_kmeans()
		elif isOptics:
			print 'Calling OPTICS'
			self.gen_optic_cluster(featureList[0], featureList[1])
		else:
			print 'Select an algorithm'



	######################################################################
	# gen_optic_cluster -  method for executing OPTICS algorithm 	     #
	######################################################################

	def gen_optic_cluster(self, f1, f2):
		# generate some spatial data with varying densities
		np.random.seed(0)
		con = getConnBaseDB()
		X = []
		cur = con.cursor()
		stuple = (self.filename,)
		cur.execute("SELECT "+self.dictionary[f1]+", "+self.dictionary[f2]+" FROM Features WHERE IMAGE_NAME = '%s'" % stuple)
		rows = cur.fetchall()
		for row in rows:
			X.append([row[0], row[1]])
		print len(X)
		#plot scatterplot of points
		X = np.asarray(X)
		self.data = X
		#fig = plt.figure()
		#ax = fig.add_subplot(111)
		self.axes.clear()
		self.xlabel = f1
		self.ylabel = f2
		self.axes.set_xlabel(self.xlabel)
		self.axes.set_ylabel(self.ylabel)
		self.axes.set_title('Intial scatter plot before clustering')
		pylab.setp(self.axes.get_xticklabels(), fontsize=8)
		pylab.setp(self.axes.get_yticklabels(), fontsize=8)
		self.axes.plot(X[:,0], X[:,1], 'bo')
		self.canvas.draw()
		#plt.savefig('Graph.png', dpi=None, facecolor='w', edgecolor='w',
		#	orientation='portrait', papertype=None, format=None,
		#	transparent=False, bbox_inches=None, pad_inches=0.1)
		#plt.show()



		#run the OPTICS algorithm on the points, using a smoothing value (0 = no smoothing)
		RD, CD, order = OP.optics(X,9)

		RPlot = []
		RPoints = []
			
		for item in order:
				RPlot.append(RD[item]) #Reachability Plot
				RPoints.append([X[item][0],X[item][1]]) #points in their order determined by OPTICS

		#hierarchically cluster the data
		rootNode = AutoC.automaticCluster(RPlot, RPoints)

		#print Tree (DFS)
		#AutoC.printTree(rootNode, 0)

		#graph reachability plot and tree
		#AutoC.graphTree(rootNode, RPlot)

		#array of the TreeNode objects, position in the array is the TreeNode's level in the tree
		array = AutoC.getArray(rootNode, 0, [0])

		#get only the leaves of the tree
		leaves = AutoC.getLeaves(rootNode, [])
		print 'leaves length = ',len(leaves)
		#graph the points and the leaf clusters that have been found by OPTICS
		#fig = plt.figure()
		#ax = fig.add_subplot(111)
		self.axes.clear()
		self.xlabel = f1
		self.ylabel = f2
		self.axes.set_xlabel(self.xlabel)
		self.axes.set_ylabel(self.ylabel)
		self.axes.set_title('Final clusters formed')
		pylab.setp(self.axes.get_xticklabels(), fontsize=8)
		pylab.setp(self.axes.get_yticklabels(), fontsize=8)
		self.axes.plot(X[:,0], X[:,1], 'yo')
		#colors = cycle('gmkrcbgrcmk')
		colors = ['r','g','b']
		colors.extend([(random.random(), 
			random.random(), random.random()) for i in range(len(leaves)-3)])
		for item, c in zip(leaves, colors):
				node = []
				for v in range(item.start,item.end):
					node.append(RPoints[v])
				node = np.array(node)
				self.axes.plot(node[:,0],node[:,1], color=c,marker='o',linestyle='None',picker=5)
		self.canvas.draw()
		#plt.savefig('Graph2.png', dpi=None, facecolor='w', edgecolor='w',
			#orientation='portrait', papertype=None, format=None,
			#transparent=False, bbox_inches=None, pad_inches=0.1)
		#plt.show()


	############################################################################
	# pixel_kmeans -  method for executing kmeans algorithm for image related  #
	#				  features (mean pixel density). 	   #
	############################################################################
	def pixel_kmeans(self, feature1Bound=None,
						feature2Bound=None,iter=None):
		random.seed(time.time())
		km_obj = KMeans(n_clusters=self.k, init='random', n_init=1,
                        max_iter=self.iterations, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
		km_obj.fit(self.data)
		self.centroids = km_obj.cluster_centers_
		self.clusterIds = km_obj.labels_
		#self.centroids,_ = kmeans2(self.data, self.k)
		print len(self.centroids)
		#self.clusterIds,_ = vq(self.data,self.centroids)
		sets = set(self.clusterIds)
		print sets
		self.generate_image()
		#TODO: If initial centroid not given only
		#self.centroids,_ = kmeans2(self.data, self.k, thresh=1e-05, minit='random')
		#lastCentroids = None
		#print self.centroids
		#print '\n'
		#i = 0
		#while not array_equal(lastCentroids,self.centroids) and i < self.iterations:
		#	i+=1
		#	lastCentroids = vstack(list(self.centroids)[:])
		#	self.centroids,_ = kmeans2(self.data, lastCentroids, iter=1, thresh=1e-05, minit='matrix')
		#	self.clusterIds,_ = vq(self.data,self.centroids)
		#self.redraw(-1)
		#print i, self.iterations
		#print self.centroids


	#######################################################################
	# helper_mean -  helper method for getting mean pixel density feature #
	#				 data for clustering.  	 	      #
	#######################################################################
	def helper_mean(self):
		conn = getConnBaseDB()
		c = conn.cursor()
		c.execute("SELECT MEAN_PIXEL_DEN FROM Features WHERE IMAGE_NAME = '%s'" %self.filename)
		rows = c.fetchall()
		data_list = []
		for row in rows:
			item = str(row[0])
			match = re.search(r'\((.*)\)',item)
			if match:
				data = match.group(1).split(',')
				data_list.append(data)
		data_list = [[float(j) for j in i ] for i in data_list]			
		#dbsetup.closeConnBaseDB()
		return data_list


	#######################################################################
	# init_cluster -  method for getting the initial set of centroids for #
	#				  kmeans CLUSTERING		      #
	#######################################################################
	def init_cluster(self):
		print "init_cluster function"
		km_obj = KMeans(n_clusters=self.k, init='random', n_init=1,
                        max_iter=1, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
                km_obj.fit(self.data)
		centroids = km_obj.cluster_centers_
		return centroids


	######################################################################
	# timer_kmeans - method performs kmeans clustering and is invoked    #
	#		 when show updates option is enabled		     #
	######################################################################
	def timer_kmeans(self):
		lastCentroids = vstack(list(self.centroids)[:])
		print "timer Kmeans"
		print "last Centroids", lastCentroids
		km_obj = KMeans(n_clusters=self.k, init=lastCentroids, n_init=1,
                        max_iter=1, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
                km_obj.fit(self.data)
		self.centroids = km_obj.cluster_centers_
		self.clusterIds = km_obj.labels_
		self.redraw(self.iterTimer)
		return array_equal(lastCentroids,self.centroids)


	#############################################################################
	# on_redraw_timer - method for calling the plot method to draw the clusters #
	#		    during each update in kmeans process 		    #
	#		    Called only when show updates method is selected 	    #
	#############################################################################
	def on_redraw_timer(self, event):
		print "self.iterTimer",self.iterTimer
		if self.iterTimer < self.iterations:
			changed = self.timer_kmeans()
			self.iterTimer+=1
			if self.iterTimer == self.iterations-1 or changed:
				self.redraw_timer.Stop()
				#self.redraw(self.data,self.centroids, clusterIds, self.k, -1)
				self.redraw(-1)



	#######################################################################
	# init_plot -  method for displaying the intial plot of the data with #
	#			   selected features.			      #
	#######################################################################
	def init_plot(self,data,k, feature1, feature2):
		self.axes.clear()
		x = array([d[0] for d in data])
		y = array([d[1] for d in data])
		maxX = max(x)
		minX = min(x)
		maxY = max(y)
		minY = min(y)
		self.xlabel = feature1
		self.ylabel = feature2
		self.axes.set_xlabel(self.xlabel)
		self.axes.set_ylabel(self.ylabel)
		self.axes.set_title('Intial scatter plot before clustering')
		pylab.setp(self.axes.get_xticklabels(), fontsize=8)
		pylab.setp(self.axes.get_yticklabels(), fontsize=8)
		self.axes.plot(x,y,'bo')
		self.canvas.draw()
		self.colors = ['r','g','b']
		self.colors.extend([(random.random(), 
			random.random(), random.random()) for i in range(k)])




	#############################################################################
	# cluster_kmeansMul -  method performing kmeans for more than two features  # 
	#			selected and writes the cluster output to text file # 
	#############################################################################
	def cluster_kmeansMul(self, featureList,feature1Bound=None,
						feature2Bound=None,iter=None):
		random.seed(time.time())
		outputfile = "Output.txt"
		fh = open(outputfile, "w")
		
		km_obj = KMeans(n_clusters=self.k, init='random', n_init=1,
                        max_iter=self.iterations, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
		km_obj.fit(self.data)
		self.centroids = km_obj.cluster_centers_
		self.clusterIds = km_obj.labels_	

		fh.write(str(self.centroids.tolist()))
		fh.write("Current clusters formed\n")
		for j in range(self.k):
			fh.write("Data points of cluster "+str(j)+"\n")
			for l in range(len(self.data)):
				if self.clusterIds[l] == j:
					fh.write(str(self.data[l])+"\n")
		#fh.write(str(self.clusterIds.tolist()))
		#print self.data
		fh.close()
		print 'done'
		self.redraw(-1)
		



	########################################################################
	# cluster_kmeans -  method performing kmeans algorithm on data for     #
	#					selected features. This called if two features are #
	#					selected.										   #
	########################################################################

	def cluster_kmeans(self, feature1Bound=None,
						feature2Bound=None,iter=None):
		random.seed(time.time())
		if not self.isPickCentroids:
                        km_obj = KMeans(n_clusters=self.k, init='random', n_init=1,
                        max_iter=self.iterations, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
                        km_obj.fit(self.data)
                else :
                        km_obj = KMeans(n_clusters=self.k, init=self.centroids, n_init=1,
                        max_iter=self.iterations, tol=0.0001, precompute_distances=True,
                         n_jobs=-1, random_state=None)
			km_obj.fit(self.data)
                self.centroids = km_obj.cluster_centers_
                self.clusterIds = km_obj.labels_ #vq(self.data,self.centroids)
                self.redraw(-1)
		#print i, self.iterations
		#print self.centroids


	#####################################################################
	# redraw - method for invoking actual plot drawing module			#
	#####################################################################
	def redraw(self, iteration):
		 wx.CallAfter(self.redraw_actual, iteration)



	######################################################################
	# redraw_actual -   method for plotting the cluster output onto the  #
	#					scatter plot and display the number of clusters  #
	#					alomg with the centroids. 						 #	
	######################################################################
	def redraw_actual(self, iteration):
		self.axes.clear()
		self.axes.set_xlabel(self.xlabel)
		self.axes.set_ylabel(self.ylabel)
		if iteration == -1:
			self.axes.set_title('Final clusters formed')
		else:
			self.axes.set_title('Clusters during kmeans iteration '+str(iteration))
		pylab.setp(self.axes.get_xticklabels(), fontsize=8)
		pylab.setp(self.axes.get_yticklabels(), fontsize=8)
		for i in range(self.k):
			self.axes.plot(self.data[self.clusterIds==i,0],self.data[self.clusterIds==i,1],color=self.colors[i],marker='o',linestyle='None',picker=5)
			self.axes.plot(self.centroids[i,0],self.centroids[i,1],color=self.colors[i], marker='H',markersize=20.0,picker=5)
		print(self.centroids)		
#for i in range(len(self.data)):  
			#self.axes.plot([self.data[i,0],self.centroids[self.clusterIds[i],0]],[self.data[i,1],self.centroids[self.clusterIds[i],1]], color=self.colors[self.clusterIds[i]],picker=5)
		self.canvas.draw()
Beispiel #10
0
class MultimeterTab(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        # instance variables -------------------------------------------------------------------------------------------
        self.DUT_choice = 'f5560A'
        self.DMM_choice = 'f8588A'
        self.dmm = dmm(self)

        self.t = threading.Thread()
        self.flag_complete = True  # Flag indicates any active threads (False) or thread completed (True)
        self.user_input = {
            'autorange': True,
            'always_voltage': True,
            'mode': 0,
            'amplitude': '',
            'rms': 0,
            'frequency': '',
        }

        self.frame = parent
        self.left_panel = wx.Panel(self, wx.ID_ANY)
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # LEFT Panel ---------------------------------------------------------------------------------------------------
        self.combo_DUT_choice = wx.ComboBox(
            self.left_panel,
            wx.ID_ANY,
            choices=["Fluke 5560A", "Fluke 5730A"],
            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_DMM_choice = wx.ComboBox(
            self.left_panel,
            wx.ID_ANY,
            choices=["Fluke 884xA", "Fluke 8588A"],
            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_DUT_report = wx.TextCtrl(self.left_panel,
                                           wx.ID_ANY,
                                           "",
                                           style=wx.TE_READONLY)
        self.text_DMM_report = wx.TextCtrl(self.left_panel,
                                           wx.ID_ANY,
                                           "",
                                           style=wx.TE_READONLY)
        self.label_source = wx.StaticText(self.left_panel, wx.ID_ANY,
                                          "Fluke 5560A")

        self.btn_connect = wx.Button(self.left_panel, wx.ID_ANY, "Connect")
        self.btn_config = wx.Button(self.left_panel, wx.ID_ANY, "Config")
        self.checkbox_autorange = wx.CheckBox(self.left_panel, wx.ID_ANY,
                                              "Auto Range")

        self.text_amplitude = wx.TextCtrl(self.left_panel, wx.ID_ANY, "10uA")
        self.combo_rms_or_peak = wx.ComboBox(self.left_panel,
                                             wx.ID_ANY,
                                             choices=["RMS", "Peak"],
                                             style=wx.CB_DROPDOWN
                                             | wx.CB_READONLY)
        self.text_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1000")

        self.checkbox_always_voltage = wx.CheckBox(self.left_panel, wx.ID_ANY,
                                                   "Always Voltage")
        self.spreadsheet = MyGrid(self.left_panel)
        self.btn_cleardata = wx.Button(self.left_panel, wx.ID_ANY,
                                       "Clear Data")
        self.checkbox_errorbar = wx.CheckBox(self.left_panel, wx.ID_ANY,
                                             "Error Bars")

        self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN")
        self.combo_mode = wx.ComboBox(self.left_panel,
                                      wx.ID_ANY,
                                      choices=["Single", "Sweep"],
                                      style=wx.CB_DROPDOWN)
        self.btn_breakpoints = wx.Button(self.left_panel, wx.ID_ANY,
                                         "Breakpoints")

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1,
                                          1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.x, self.y, self.std = np.NaN, np.NaN, np.NaN
        self.errorbars = False
        self.ax1 = self.figure.add_subplot(111)
        self.line, (self.err_top,
                    self.err_btm), (self.bars, ) = self.ax1.errorbar(
                        np.zeros(1),
                        np.zeros(1),
                        yerr=np.zeros(1),
                        fmt='o',
                        ecolor='red',
                        capsize=4)
        # BINDINGS =====================================================================================================
        # Configure Instruments ----------------------------------------------------------------------------------------
        on_DUT_selection = lambda event: self._get_DUT_choice(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DUT_selection,
                  self.combo_DUT_choice)

        on_DMM_selection = lambda event: self._get_DMM_choice(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DMM_selection,
                  self.combo_DMM_choice)

        on_connect = lambda event: self.on_connect_instr(event)
        self.Bind(wx.EVT_BUTTON, on_connect, self.btn_connect)

        on_config = lambda event: self.config(event)
        self.Bind(wx.EVT_BUTTON, on_config, self.btn_config)

        on_toggle_autorange = lambda event: self.toggle_autorange(event)
        self.Bind(wx.EVT_CHECKBOX, on_toggle_autorange,
                  self.checkbox_autorange)

        on_toggle_always_voltage = lambda event: self.toggle_always_voltage(
            event)
        self.Bind(wx.EVT_CHECKBOX, on_toggle_always_voltage,
                  self.checkbox_always_voltage)

        on_cleardata = lambda event: self.cleardata(event)
        self.Bind(wx.EVT_BUTTON, on_cleardata, self.btn_cleardata)

        on_toggle_errorbar = lambda event: self.toggle_errorbar(event)
        self.Bind(wx.EVT_CHECKBOX, on_toggle_errorbar, self.checkbox_errorbar)

        # Run Measurement (start subprocess) ---------------------------------------------------------------------------
        on_run_event = lambda event: self.on_run(event)
        self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start)

        on_open_breakpoints = lambda event: open_breakpoints()
        self.Bind(wx.EVT_BUTTON, on_open_breakpoints, self.btn_breakpoints)

        on_combo_select = lambda event: self.lock_controls(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_select, self.combo_mode)

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()
        self.__do_table_header()
        self.cleardata(wx.Event)

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.canvas.SetMinSize((700, 490))

        self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255))

        self.text_DUT_report.SetMinSize((200, 23))
        self.text_DMM_report.SetMinSize((200, 23))
        self.checkbox_autorange.SetValue(1)

        self.combo_DUT_choice.SetSelection(0)
        self.combo_DUT_choice.SetMinSize((87, 23))
        self.combo_DMM_choice.SetSelection(1)
        self.combo_DMM_choice.SetMinSize((87, 23))
        self.btn_connect.SetMinSize((87, 23))
        self.btn_start.SetMinSize((87, 23))
        self.btn_breakpoints.SetMinSize((87, 23))

        self.combo_rms_or_peak.SetSelection(0)
        self.combo_mode.SetSelection(0)
        self.checkbox_always_voltage.SetValue(0)
        self.checkbox_errorbar.SetValue(0)

        self.spreadsheet.CreateGrid(60, 3)
        self.spreadsheet.SetRowLabelSize(40)
        self.spreadsheet.SetColLabelValue(0, 'Frequency')
        self.spreadsheet.SetColLabelValue(1, 'Value')
        self.spreadsheet.SetColLabelValue(2, 'STD')
        self.spreadsheet.SetMinSize((300, 215))

        self.combo_mode.SetMinSize((110, 23))

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_left_panel = wx.GridBagSizer(0, 0)
        grid_sizer_left_sub_btn_row = wx.GridBagSizer(0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)

        # LEFT PANEL ===================================================================================================
        # TITLE --------------------------------------------------------------------------------------------------------
        row = 0
        label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "MULTIMETER")
        label_1.SetFont(
            wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_1, (row, 0), (1, 2), 0, 0)

        row += 1
        static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_1.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # INSTRUMENT INFO  ---------------------------------------------------------------------------------------------
        row += 1
        grid_sizer_left_panel.Add(self.combo_DUT_choice, (row, 0), (1, 1),
                                  wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_DUT_report, (row, 1), (1, 2),
                                  wx.BOTTOM | wx.LEFT, 5)

        row += 1
        grid_sizer_left_panel.Add(self.combo_DMM_choice, (row, 0), (1, 1),
                                  wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_DMM_report, (row, 1), (1, 2),
                                  wx.BOTTOM | wx.LEFT, 5)

        row += 1
        grid_sizer_left_panel.Add(self.btn_connect, (row, 0), (1, 1),
                                  wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.btn_config, (row, 1), (1, 1),
                                  wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.checkbox_autorange, (row, 2), (1, 1),
                                  wx.ALIGN_CENTRE_VERTICAL | wx.BOTTOM, 5)

        # f5560A SETUP -------------------------------------------------------------------------------------------------
        row += 1
        self.label_source.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(self.label_source, (row, 0), (1, 2), wx.TOP,
                                  10)

        row += 1
        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        label_amplitude = wx.StaticText(self.left_panel, wx.ID_ANY,
                                        "Amplitude:")
        grid_sizer_left_panel.Add(label_amplitude, (row, 0), (1, 1),
                                  wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_amplitude, (row, 1), (1, 1),
                                  wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.combo_rms_or_peak, (row, 2), (1, 1),
                                  wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_frequency = wx.StaticText(self.left_panel, wx.ID_ANY,
                                        "Frequency (Ft):")
        grid_sizer_left_panel.Add(label_frequency, (row, 0), (1, 1),
                                  wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_frequency, (row, 1), (1, 1),
                                  wx.LEFT, 5)
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)")
        grid_sizer_left_panel.Add(label_Hz, (row, 2), (1, 1),
                                  wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        row += 1
        label_measure = wx.StaticText(self.left_panel, wx.ID_ANY, "Measure")
        label_measure.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_measure, (row, 0), (1, 1), wx.TOP, 10)
        grid_sizer_left_panel.Add(self.checkbox_always_voltage, (row, 1),
                                  (1, 3), wx.ALIGN_BOTTOM | wx.LEFT, 10)

        # RESULTS ------------------------------------------------------------------------------------------------------
        row += 1
        static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_3.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        grid_sizer_left_panel.Add(self.spreadsheet, (row, 0), (1, 3),
                                  wx.ALIGN_LEFT | wx.RIGHT | wx.EXPAND, 5)

        row += 1
        grid_sizer_left_panel.Add(self.btn_cleardata, (row, 0), (1, 1),
                                  wx.LEFT | wx.TOP, 5)
        grid_sizer_left_panel.Add(self.checkbox_errorbar, (row, 1), (1, 1),
                                  wx.LEFT | wx.TOP, 5)
        grid_sizer_left_panel.AddGrowableRow(11)

        # BUTTONS ------------------------------------------------------------------------------------------------------
        row += 1
        static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_4.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_4, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        grid_sizer_left_sub_btn_row.Add(self.btn_start, (0, 0), (1, 1),
                                        wx.ALIGN_CENTER_VERTICAL)
        grid_sizer_left_sub_btn_row.Add(self.combo_mode, (0, 1), (1, 1),
                                        wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        grid_sizer_left_sub_btn_row.Add(self.btn_breakpoints, (0, 2), (1, 1),
                                        wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        grid_sizer_left_panel.Add(grid_sizer_left_sub_btn_row, (row, 0),
                                  (1, 3), wx.ALIGN_TOP | wx.BOTTOM, 13)

        self.left_panel.SetSizer(grid_sizer_left_panel)

        # PLOT PANEL ===================================================================================================
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)

        # add to main panel --------------------------------------------------------------------------------------------
        grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    # GET SELECTED INSTRUMENTS =========================================================================================
    def _get_instr_choice(self, type, selection, text_ctrl):
        # Get [NEW] instr choice ---------------------------------------------------------------------------------------
        print(f"The {selection} has been selected.")
        new_choice = 'f' + selection.strip('Fluke ')

        # Get [PREVIOUS] instr choice ----------------------------------------------------------------------------------
        if type.lower() == 'dut':
            current_choice = self.dmm.DUT_choice
        elif type.lower() == 'dmm':
            current_choice = self.dmm.DMM_choice
        else:
            raise ValueError(
                "invalid instrument type. Please specify whether 'dut' or 'dmm'."
            )

        # Conditionally refresh GUI ------------------------------------------------------------------------------------
        if not self.dmm.M.connected:
            # if instruments are not connected, then user is free to change the DMM choice
            pass

        elif self.dmm.DUMMY_DATA:
            # if using dummy data, then we only need to set DMM choice to True before running
            self.dmm.DMM_choice = self.DMM_choice

        elif new_choice != current_choice and self.dmm.M.connected:
            # if the selected instrument does not currently match the remote instrument connected, there's a problem.
            self.dmm.M.connected = False
            print(
                f"[WARNING] the {selection} is NOT the current remote instrument connected!"
            )
            # set text box color to red (chantilly: #EAB9C1)
            text_ctrl.SetBackgroundColour(wx.Colour(234, 185, 193))
            self.left_panel.Refresh()

        elif new_choice == self.dmm.DMM_choice:
            # if the selected instrument does match the remote instrument connected, reset the color if necessary
            self.dmm.M.connected = True
            # Reset color (white smoke: #F0F0F0)
            text_ctrl.SetBackgroundColour(wx.Colour(240, 240, 240))
            self.left_panel.Refresh()

        return new_choice

    def _get_DUT_choice(self, evt):
        selection = self.combo_DUT_choice.GetValue()
        self.DUT_choice = self._get_instr_choice(
            type='dut', selection=selection, text_ctrl=self.text_DUT_report)
        self.label_source.SetLabelText(str(selection))

        return self.DUT_choice

    def _get_DMM_choice(self, evt):
        selection = self.combo_DMM_choice.GetValue()
        self.DMM_choice = self._get_instr_choice(
            type='dmm', selection=selection, text_ctrl=self.text_DMM_report)

        return self.DMM_choice

    # CONFIGURE INSTR FOR MEASUREMENT ==================================================================================
    def config(self, evt):
        dlg = InstrumentDialog(
            self,
            [self.DUT_choice, self.DMM_choice],
            None,
            wx.ID_ANY,
        )
        dlg.ShowModal()
        dlg.Destroy()

    def get_instruments(self):
        config_dict = ReadConfig()

        dut = config_dict[self.DUT_choice]
        dmm = config_dict[self.DMM_choice]

        instruments = {
            self.DUT_choice: {
                'address': dut['address'],
                'port': dut['port'],
                'gpib': dut['gpib'],
                'mode': dut['mode']
            },
            self.DMM_choice: {
                'address': dmm['address'],
                'port': dmm['port'],
                'gpib': dmm['gpib'],
                'mode': dmm['mode']
            }
        }

        return instruments

    def set_ident(self, idn_dict):
        self.text_DUT_report.SetValue(idn_dict['DUT'])  # DUT
        self.text_DMM_report.SetValue(idn_dict['DMM'])  # current DMM

    def on_connect_instr(self, evt):
        wait = wx.BusyCursor()
        msg = "Establishing remote connections to instruments."
        busyDlg = wx.BusyInfo(msg, parent=self)

        print(
            '\nResetting connection. Closing communication with any connected instruments'
        )
        self.text_DUT_report.Clear()
        self.text_DMM_report.Clear()
        self.dmm.DUT_choice = self.DUT_choice
        self.dmm.DMM_choice = self.DMM_choice
        # self.thread_this(self.dmm.connect, (self.get_instruments(),))
        self.dmm.connect(self.get_instruments(), )

        busyDlg = None
        del wait

    # ------------------------------------------------------------------------------------------------------------------
    def lock_controls(self, evt):
        choice = self.combo_mode.GetSelection()
        if choice == 1:
            self.text_amplitude.Disable()
            self.combo_rms_or_peak.Disable()
            self.text_frequency.Disable()
        else:
            self.text_amplitude.Enable()
            self.combo_rms_or_peak.Enable()
            self.text_frequency.Enable()

    def toggle_controls(self):
        if self.text_amplitude.IsEnabled():
            self.text_amplitude.Disable()
            self.combo_rms_or_peak.Disable()
            self.text_frequency.Disable()
        else:
            self.text_amplitude.Enable()
            self.combo_rms_or_peak.Enable()
            self.text_frequency.Enable()

    def toggle_autorange(self, evt):
        if self.checkbox_autorange.IsChecked():
            print("[Update] Auto Ranging turned ON for Fluke 884xA.")
        else:
            print("[Update] Auto Ranging turned OFF for Fluke 884xA.")

    def toggle_always_voltage(self, evt):
        DMM_choice = {self.DMM_choice}

        if self.checkbox_always_voltage.IsChecked():
            print(
                f"[Update] {DMM_choice} will always measure voltage. Use external shunt if sourcing current."
            )
        else:
            print(
                f"[Update] {DMM_choice} will perform direct measurement of the DUT."
            )

    # ------------------------------------------------------------------------------------------------------------------
    def get_values(self):
        autorange = bool(self.checkbox_autorange.GetValue())
        always_voltage = bool(self.checkbox_always_voltage.GetValue())
        mode = self.combo_mode.GetSelection()
        rms = self.combo_rms_or_peak.GetSelection()

        amp_string = self.text_amplitude.GetValue()
        freq_string = self.text_frequency.GetValue()

        self.user_input = {
            'autorange': autorange,
            'always_voltage': always_voltage,
            'mode': mode,
            'amplitude': amp_string,
            'rms': rms,
            'frequency': freq_string,
        }

    # ------------------------------------------------------------------------------------------------------------------
    def thread_this(self, func, arg=()):
        self.t = threading.Thread(target=func, args=arg, daemon=True)
        self.t.start()

    # ------------------------------------------------------------------------------------------------------------------
    def on_run(self, evt):
        self.get_values()

        if not self.t.is_alive() and self.flag_complete:
            # start new thread
            self.thread_this(self.dmm.start, (self.user_input, ))
            self.checkbox_autorange.Disable()
            self.checkbox_always_voltage.Disable()
            self.btn_start.SetLabel('STOP')

        elif self.t.is_alive() and self.user_input['mode'] == 1:
            # stop sweep
            # https://stackoverflow.com/a/36499538
            self.t.do_run = False
            self.checkbox_autorange.Enable()
            self.checkbox_always_voltage.Enable()
            self.btn_start.SetLabel('RUN')
        else:
            print('thread already running.')

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('MULTIMETER')
        self.ax1.set_xlabel('FREQUENCY (kHz)')
        self.ax1.set_ylabel('AMPLITUDE')
        self.ax1.grid(True)
        self.figure.tight_layout()

    def toggle_errorbar(self, evt):
        if self.errorbars:
            self.errorbars = False
            print("[Update] Error bars have been turned OFF")
        else:
            self.errorbars = True
            print("[Update] Error bars have been turned ON")

        if hasattr(self.x, 'size'):
            y_err = self.std / np.sqrt(self.x.size)
            self.plot(yerr=y_err)

    def update_plot(self, x, y, std):
        self.results_update([x, y, std])
        # TODO: np.NaN is always index 0. Should this be fixed?
        self.x = np.append(self.x, x)
        self.y = np.append(self.y, y)
        self.std = np.append(self.std, std)

        yerr = self.std / np.sqrt(self.x.size)

        self.plot(yerr)

    def plot(self, yerr=None):
        if self.errorbars and yerr is not None:
            yerr_top = self.y + yerr
            yerr_btm = self.y - yerr

            self.line.set_data(self.x, self.y)
            self.err_top.set_data(self.x, yerr_top)
            self.err_btm.set_data(self.x, yerr_btm)

            new_segments = [
                np.array([[x, yt], [x, yb]])
                for x, yt, yb in zip(self.x, yerr_top, yerr_btm)
            ]
            self.bars.set_segments(new_segments)
        else:
            self.line.set_data(self.x, self.y)
            self.err_top.set_ydata(None)
            self.err_btm.set_ydata(None)

            new_segments = []
            self.bars.set_segments(new_segments)

        self.plot_redraw()

    def plot_redraw(self):
        try:
            self.ax1.relim()  # recompute the ax.dataLim
        except ValueError:
            yerr = self.err_top.get_ydata()
            print(
                f'Are the lengths of x: {len(self.x)}, y: {len(self.y)}, and yerr: {len(yerr)} mismatched?'
            )
            raise
        self.ax1.autoscale()

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()

        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def cleardata(self, evt):
        self.x, self.y, self.std = np.NaN, np.NaN, np.NaN
        self.spreadsheet.cleardata()

        self.plot()

    def __do_table_header(self):
        header = ['frequency', 'value', 'std']
        self.spreadsheet.append_rows(header)

    def results_update(self, row):
        """
        :param row: of type list
        :return: True iff rows successfully appended to spreadsheet (grid)
        """
        # self.text_rms_report.SetLabelText(str(y))
        # self.text_frequency_report.SetLabelText(str(x))
        if isinstance(row, list):
            self.spreadsheet.append_rows(row)
            return True
        else:
            raise ValueError('Row to be appended not of type list.')

    def error_dialog(self, error_message):
        print(error_message)
        dial = wx.MessageDialog(None, str(error_message), 'Error',
                                wx.OK | wx.ICON_ERROR)
        dial.ShowModal()
Beispiel #11
0
class DistortionAnalyzerTab(wx.Panel):
    def __init__(self, parent, frame):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        # instance variables -------------------------------------------------------------------------------------------
        self.flag_complete = True  # Flag indicates any active threads (False) or thread completed (True)
        self.t = threading.Thread()
        self.da = da(self)
        self.user_input = {}

        self.DUT_choice = 'f5560A'
        self.DMM_choice = 'f8588A'

        self.parent = parent
        self.frame = frame

        self.left_panel = wx.Panel(self, wx.ID_ANY)
        self.left_sub_panel = wx.Panel(self.left_panel, wx.ID_ANY)  # amplitude/frequency panel
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # PANELS =======================================================================================================
        # LEFT Panel ---------------------------------------------------------------------------------------------------
        self.combo_DUT_choice = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                            choices=["Fluke 5560A", "Fluke 5730A"],
                                            style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_DUT_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.text_DMM_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Fluke 5560A")

        self.btn_connect = wx.Button(self.left_panel, wx.ID_ANY, "Connect")
        self.btn_config = wx.Button(self.left_panel, wx.ID_ANY, "Config")

        self.checkbox_1 = wx.CheckBox(self.left_panel, wx.ID_ANY, "Local")
        self.text_amplitude = wx.TextCtrl(self.left_sub_panel, wx.ID_ANY, "10uA")
        self.combo_rms_or_peak = wx.ComboBox(self.left_sub_panel, wx.ID_ANY,
                                             choices=["RMS", "Peak"],
                                             style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.text_frequency = wx.TextCtrl(self.left_sub_panel, wx.ID_ANY, "1000")

        self.combo_mainlobe = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                          choices=["Relative", "Absolute"],
                                          style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_mainlobe.SetToolTip("Mainlobe width can be set relative to the signal frequency\n"
                                       "or as an absolute width independent of signal frequency")
        self.text_mainlobe = wx.TextCtrl(self.left_panel, wx.ID_ANY, "100")
        self.label_mainlobe = wx.StaticText(self.left_panel, wx.ID_ANY, "MLW (Hz)")
        self.label_mainlobe.SetToolTip("Main Lobe Width")

        self.combo_filter = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                        choices=["None", "100kHz", "2MHz", "2.4MHz", "3MHz"],
                                        style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_coupling = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                          choices=["AC1M", "AC10M", "DC1M", "DC10M", "DCAuto"],
                                          style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.label_fs_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--")
        self.label_samples_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--")
        self.label_aperture_report = wx.StaticText(self.left_panel, wx.ID_ANY, "--")
        self.text_rms_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.text_thdn_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.text_thd_report = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)

        self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN")
        self.combo_selected_test = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                               choices=["Single", "Sweep",
                                                        "Single w/ shunt", "Sweep w/ shunt",
                                                        "Continuous"],
                                               style=wx.CB_DROPDOWN)
        self.btn_breakpoints = wx.Button(self.left_panel, wx.ID_ANY, "Breakpoints")

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1, 1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.ax1 = self.figure.add_subplot(211)
        self.ax2 = self.figure.add_subplot(212)

        self.temporal, = self.ax1.plot([], [], linestyle='-')
        self.spectral, = self.ax2.plot([], [], color='#C02942')

        # BINDINGS =====================================================================================================
        # Configure Instruments ----------------------------------------------------------------------------------------
        on_DUT_selection = lambda event: self._get_DUT_choice(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_DUT_selection, self.combo_DUT_choice)

        on_connect = lambda event: self.on_connect_instr(event)
        self.Bind(wx.EVT_BUTTON, on_connect, self.btn_connect)

        on_config = lambda event: self.config(event)
        self.Bind(wx.EVT_BUTTON, on_config, self.btn_config)

        on_mainlobe = lambda event: self.on_mainlobe_change(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_mainlobe, self.combo_mainlobe)

        # Run Measurement (start subprocess) ---------------------------------------------------------------------------
        on_run_event = lambda event: self.on_run(event)
        self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start)

        on_open_breakpoints = lambda event: open_breakpoints()
        self.Bind(wx.EVT_BUTTON, on_open_breakpoints, self.btn_breakpoints)

        on_toggle = lambda event: self.toggle_panel(event)
        self.Bind(wx.EVT_CHECKBOX, on_toggle, self.checkbox_1)

        on_combo_select = lambda event: self.lock_controls(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_select, self.combo_selected_test)

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.canvas.SetMinSize((700, 490))

        self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255))

        self.left_panel.SetMinSize((310, 502))
        # self.left_sub_panel.SetBackgroundColour(wx.Colour(255, 0, 255))
        self.plot_panel.SetMinSize((700, 502))

        self.combo_DUT_choice.SetSelection(0)
        self.combo_DUT_choice.SetMinSize((87, 23))
        self.btn_connect.SetMinSize((87, 23))
        self.btn_start.SetMinSize((87, 23))
        self.btn_breakpoints.SetMinSize((87, 23))

        self.text_DUT_report.SetMinSize((200, 23))
        self.text_DMM_report.SetMinSize((200, 23))
        self.canvas.SetMinSize((700, 490))

        self.checkbox_1.SetValue(0)
        self.combo_rms_or_peak.SetSelection(0)

        self.combo_mainlobe.SetSelection(1)
        self.combo_mainlobe.SetMinSize((87, 23))

        self.combo_filter.SetSelection(1)
        self.combo_filter.SetMinSize((110, 23))
        self.combo_coupling.SetSelection(0)
        self.combo_coupling.SetMinSize((110, 23))

        self.combo_selected_test.SetSelection(0)
        self.combo_selected_test.SetMinSize((110, 23))

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)

        grid_sizer_left_panel = wx.GridBagSizer(0, 0)
        grid_sizer_left_sub_panel = wx.GridBagSizer(0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)

        # LEFT PANEL ===================================================================================================
        # TITLE --------------------------------------------------------------------------------------------------------
        row = 0
        label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "DISTORTION ANALYZER")
        label_1.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3), 0, 0)

        row += 1
        static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_1.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # INSTRUMENT INFO  ---------------------------------------------------------------------------------------------
        row += 1
        grid_sizer_left_panel.Add(self.combo_DUT_choice, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_DUT_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_DMM = wx.StaticText(self.left_panel, wx.ID_ANY, "Fluke 8588A")
        label_DMM.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_DMM, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_DMM_report, (row, 1), (1, 2), wx.BOTTOM | wx.LEFT, 5)

        row += 1
        grid_sizer_left_panel.Add(self.btn_connect, (row, 0), (1, 1), wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.btn_config, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.checkbox_1, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        # f5560A SETUP -------------------------------------------------------------------------------------------------
        row += 1
        self.label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(self.label_source, (row, 0), (1, 2), wx.TOP, 10)

        row += 1
        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # SUB PANEL  ---------------------------------------------------------------------------------------------------
        row += 1
        label_amplitude = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "Amplitude:")
        label_amplitude.SetMinSize((87, 16))
        grid_sizer_left_sub_panel.Add(label_amplitude, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_sub_panel.Add(self.text_amplitude, (0, 1), (1, 1),
                                      wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_sub_panel.Add(self.combo_rms_or_peak, (0, 2), (1, 1),
                                      wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        label_frequency = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "Frequency (Ft):")
        label_frequency.SetMinSize((87, 16))
        grid_sizer_left_sub_panel.Add(label_frequency, (1, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_sub_panel.Add(self.text_frequency, (1, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        label_Hz = wx.StaticText(self.left_sub_panel, wx.ID_ANY, "(Hz)")
        grid_sizer_left_sub_panel.Add(label_Hz, (1, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        self.left_sub_panel.SetSizer(grid_sizer_left_sub_panel)
        grid_sizer_left_panel.Add(self.left_sub_panel, (row, 0), (1, 3), wx.LEFT, 0)

        # Measurement --------------------------------------------------------------------------------------------------
        row += 1
        label_measure = wx.StaticText(self.left_panel, wx.ID_ANY, "Measurement")
        label_measure.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_measure, (row, 0), (1, 3), wx.TOP, 10)

        row += 1
        static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_3.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        grid_sizer_left_panel.Add(self.combo_mainlobe, (row, 0), (1, 1), 0, 0)
        grid_sizer_left_panel.Add(self.text_mainlobe, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.label_mainlobe, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_filter = wx.StaticText(self.left_panel, wx.ID_ANY, "Filter:")
        grid_sizer_left_panel.Add(label_filter, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.combo_filter, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_coupling = wx.StaticText(self.left_panel, wx.ID_ANY, "Coupling:")
        grid_sizer_left_panel.Add(label_coupling, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.combo_coupling, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_fs = wx.StaticText(self.left_panel, wx.ID_ANY, "Fs:")
        grid_sizer_left_panel.Add(label_fs, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.label_fs_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_samples = wx.StaticText(self.left_panel, wx.ID_ANY, "Samples:")
        grid_sizer_left_panel.Add(label_samples, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.label_samples_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_aperture = wx.StaticText(self.left_panel, wx.ID_ANY, "Aperture:")
        grid_sizer_left_panel.Add(label_aperture, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.label_aperture_report, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        # REPORT -------------------------------------------------------------------------------------------------------
        row += 1
        static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_4.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_4, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        label_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:")
        grid_sizer_left_panel.Add(label_rms, (row, 0), (1, 1), 0, 0)
        grid_sizer_left_panel.Add(self.text_rms_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_thdn = wx.StaticText(self.left_panel, wx.ID_ANY, "THD+N:")
        grid_sizer_left_panel.Add(label_thdn, (row, 0), (1, 1), 0, 0)
        grid_sizer_left_panel.Add(self.text_thdn_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        row += 1
        label_thd = wx.StaticText(self.left_panel, wx.ID_ANY, "THD:")
        grid_sizer_left_panel.Add(label_thd, (row, 0), (1, 1), 0, 0)
        grid_sizer_left_panel.Add(self.text_thd_report, (row, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        # BUTTONS ------------------------------------------------------------------------------------------------------
        row += 1
        static_line_9 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_9.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_9, (row, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        grid_sizer_left_panel.Add(self.btn_start, (row, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL)
        grid_sizer_left_panel.Add(self.combo_selected_test, (row, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        grid_sizer_left_panel.Add(self.btn_breakpoints, (row, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        self.left_panel.SetSizer(grid_sizer_left_panel)

        # PLOT PANEL ===================================================================================================
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)

        # add to main panel --------------------------------------------------------------------------------------------
        grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 0)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    # GET SELECTED INSTRUMENTS =========================================================================================
    def _get_instr_choice(self, type, selection, text_ctrl):
        # Get [NEW] instr choice ---------------------------------------------------------------------------------------
        print(f"The {selection} has been selected.")
        new_choice = 'f' + selection.strip('Fluke ')

        # Get [PREVIOUS] instr choice ----------------------------------------------------------------------------------
        if type.lower() == 'dut':
            current_choice = self.da.DUT_choice
        elif type.lower() == 'dmm':
            current_choice = self.da.DMM_choice
        else:
            raise ValueError("invalid instrument type. Please specify whether 'dut' or 'dmm'.")

        # Conditionally refresh GUI ------------------------------------------------------------------------------------
        if not self.da.M.connected:
            # if instruments are not connected, then user is free to change the DMM choice
            pass

        elif self.da.DUMMY_DATA:
            # if using dummy data, then we only need to set DMM choice to True before running
            self.da.DMM_choice = self.DMM_choice

        elif new_choice != current_choice and self.da.M.connected:
            # if the selected instrument does not currently match the remote instrument connected, there's a problem.
            self.da.M.connected = False
            print(f"[WARNING] the {selection} is NOT the current remote instrument connected!")
            # set text box color to red (chantilly: #EAB9C1)
            text_ctrl.SetBackgroundColour(wx.Colour(234, 185, 193))
            self.left_panel.Refresh()

        elif new_choice == self.da.DMM_choice:
            # if the selected instrument does match the remote instrument connected, reset the color if necessary
            self.da.M.connected = True
            # Reset color (white smoke: #F0F0F0)
            text_ctrl.SetBackgroundColour(wx.Colour(240, 240, 240))
            self.left_panel.Refresh()

        return new_choice

    def _get_DUT_choice(self, evt):
        selection = self.combo_DUT_choice.GetValue()
        self.DUT_choice = self._get_instr_choice(type='dut', selection=selection, text_ctrl=self.text_DUT_report)
        self.label_source.SetLabelText(str(selection))

        return self.DUT_choice

    # ------------------------------------------------------------------------------------------------------------------
    def config(self, evt):
        dlg = InstrumentDialog(self, [self.DUT_choice, self.DMM_choice], None, wx.ID_ANY, )
        dlg.ShowModal()
        dlg.Destroy()

    def get_instruments(self):
        config_dict = ReadConfig()

        dut = config_dict[self.DUT_choice]
        dmm = config_dict[self.DMM_choice]

        instruments = {self.DUT_choice: {'address': dut['address'], 'port': dut['port'],
                                         'gpib': dut['gpib'], 'mode': dut['mode']},
                       'f8588A': {'address': dmm['address'], 'port': dmm['port'],
                                  'gpib': dmm['gpib'], 'mode': dmm['mode']}}

        return instruments

    def set_ident(self, idn_dict):
        self.text_DUT_report.SetValue(idn_dict['DUT'])  # DUT
        self.text_DMM_report.SetValue(idn_dict['DMM'])  # current DMM

    def on_connect_instr(self, evt):
        wait = wx.BusyCursor()
        msg = "Establishing remote connections to instruments."
        busyDlg = wx.BusyInfo(msg, parent=self)

        print('\nResetting connection. Closing communication with any connected instruments')
        self.text_DUT_report.Clear()
        self.text_DMM_report.Clear()
        self.da.DUT_choice = self.DUT_choice
        self.da.DMM_choice = self.DMM_choice
        # self.thread_this(self.da.connect, (self.get_instruments(),))
        self.da.connect(self.get_instruments(),)

        busyDlg = None
        del wait

    # ------------------------------------------------------------------------------------------------------------------
    def toggle_panel(self, evt):
        local = self.checkbox_1.GetValue()
        if not local:
            if not self.left_sub_panel.IsShown():
                self.left_sub_panel.Show()
                print(f"{self.DUT_choice} is in REMOTE and will be controlled by software")
        else:
            self.left_sub_panel.Hide()
            print(f"{self.DUT_choice} is in LOCAL and will not be controlled by software")

    def lock_controls(self, evt):
        local = self.checkbox_1.GetValue()
        choice = self.combo_selected_test.GetSelection()
        if choice in (1, 3):
            if local:
                self.checkbox_1.SetValue(0)
                self.toggle_panel(evt)
            self.checkbox_1.Disable()
            self.text_amplitude.Disable()
            self.combo_rms_or_peak.Disable()
            self.text_frequency.Disable()
        else:
            self.checkbox_1.Enable()
            self.text_amplitude.Enable()
            self.combo_rms_or_peak.Enable()
            self.text_frequency.Enable()

    def toggle_controls(self):
        if self.text_amplitude.IsEnabled():
            self.checkbox_1.Disable()
            self.text_amplitude.Disable()
            self.combo_rms_or_peak.Disable()
            self.text_frequency.Disable()
        else:
            self.checkbox_1.Enable()
            self.text_amplitude.Enable()
            self.combo_rms_or_peak.Enable()
            self.text_frequency.Enable()

    def on_mainlobe_change(self, evt):
        value = self.combo_mainlobe.GetValue()
        if value == 'Relative':
            self.text_mainlobe.SetValue('0.1')
            self.label_mainlobe.SetLabelText('(MLW/f0)')
            self.label_mainlobe.SetToolTip("Relative Main Lobe Width (MLW)\nwith respect to the fundamental")
        else:
            self.text_mainlobe.SetValue('100')
            self.label_mainlobe.SetLabelText('MLW (Hz)')
            self.label_mainlobe.SetToolTip("Main Lobe Width")

    # ------------------------------------------------------------------------------------------------------------------
    def get_values(self):
        selected_test = self.combo_selected_test.GetSelection()
        local = self.checkbox_1.GetValue()  # local if True (1)
        mainlobe_type = self.combo_mainlobe.GetValue().lower()
        mainlobe_value = float(self.text_mainlobe.GetValue())
        rms = self.combo_rms_or_peak.GetSelection()

        coupling = self.combo_coupling.GetValue()
        filter = self.combo_filter.GetValue()

        amp_string = self.text_amplitude.GetValue()
        freq_string = self.text_frequency.GetValue()

        self.user_input = {'selected_test': selected_test,
                           'local': local,
                           'amplitude': amp_string,
                           'frequency': freq_string,
                           'rms': rms,
                           'coupling': coupling,
                           'mainlobe_type': mainlobe_type,
                           'mainlobe_value': mainlobe_value,
                           'filter': filter
                           }

    # ------------------------------------------------------------------------------------------------------------------
    def thread_this(self, func, arg=()):
        self.t = threading.Thread(target=func, args=arg, daemon=True)
        self.t.start()

    # ------------------------------------------------------------------------------------------------------------------
    def on_run(self, evt):
        self.get_values()
        if not self.t.is_alive() and self.flag_complete:
            # start new thread
            self.thread_this(self.da.start, (self.user_input,))
            self.btn_start.SetLabel('STOP')

        elif self.t.is_alive() and self.user_input['selected_test'] in (1, 4):
            # stop continuous
            # https://stackoverflow.com/a/36499538
            self.t.do_run = False
            self.btn_start.SetLabel('RUN')
        else:
            print('thread already running.')

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ms)')
        self.ax1.set_ylabel('AMPLITUDE')
        self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE')
        self.ax2.set_xlabel('FREQUENCY (kHz)')
        self.ax2.set_ylabel('MAGNITUDE (dB)')
        self.ax2.grid()
        self.figure.align_ylabels([self.ax1, self.ax2])
        self.figure.tight_layout()

    def plot(self, params):
        # TEMPORAL -----------------------------------------------------------------------------------------------------
        xt = params['xt']
        yt = params['yt']

        self.temporal.set_data(xt, yt)

        xt_left = params['xt_left']
        xt_right = params['xt_right']
        yt_btm = params['yt_btm']
        yt_top = params['yt_top']
        yt_tick = params['yt_tick']

        self.ax1.set_xlim(left=xt_left, right=xt_right)
        self.ax1.set_yticks(np.arange(yt_btm, yt_top, yt_tick))

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        xf = params['xf']
        yf = params['yf']

        self.spectral.set_data(xf, yf)

        xf_left = params['xf_left']
        xf_right = params['xf_right']
        yf_btm = params['yf_btm']
        yf_top = params['yf_top']

        self.ax2.set_xlim(left=xf_left, right=xf_right)
        self.ax2.set_ylim(bottom=yf_btm, top=yf_top)

        # REDRAW PLOT --------------------------------------------------------------------------------------------------
        self.plot_redraw()

    def plot_redraw(self):
        try:
            self.ax1.relim()  # recompute the ax.dataLim
        except ValueError:
            xt_length = len(self.ax1.get_xdata())
            yt_length = len(self.ax1.get_ydata())
            print(f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?')
            raise
        self.ax1.margins(x=0)
        self.ax1.autoscale(axis='y')

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()

        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def results_update(self, results):
        amplitude = results['Amplitude']
        freq_ideal = results['freq_ideal']
        freq_sampled = results['freq_sampled']

        fs = results['Fs']
        N = results['N']
        aperture = results['Aperture']
        yrms = results['yrms']
        units = results['units']
        thdn = results['THDN']
        thd = results['THD']
        rms_noise = results['RMS NOISE']

        self.label_fs_report.SetLabelText(str(fs))
        self.label_samples_report.SetLabelText(str(N))
        self.label_aperture_report.SetLabelText(str(aperture))
        self.text_rms_report.SetValue(f"{'{:0.3e}'.format(yrms)} {units}")
        self.text_thdn_report.SetValue(f"{round(thdn * 100, 3)}% or {round(np.log10(thdn), 1)}dB")
        self.text_thd_report.SetValue(f"{round(thd * 100, 3)}% or {round(np.log10(thd), 1)}dB")

        row = [amplitude, freq_ideal, freq_sampled, yrms, thdn, thd, rms_noise, fs, N, aperture]
        self.frame.append_row(row)

    def error_dialog(self, error_message):
        print(error_message)
        dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR)
        dial.ShowModal()
Beispiel #12
0
class HistPanel(wx.Panel):
    def __init__(self, parent, dpi=None, **kwargs):
        wx.Panel.__init__(self, parent, **kwargs)
        self.figure = Figure(figsize=(1, 4), dpi=dpi)
        # self.axes = self.figure.add_subplot(111)
        self.axes = self.figure.gca()
        self.canvas = FigureCanvas(self, -1, self.figure)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.EXPAND)
        self.SetSizer(self.sizer)

        self._init_plot()
        # self.add_toolbar()
        # self.plot_histogram()

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        # self.Bind(wx.EVT_SIZE, self.OnSize)

        # Set up pubsub
        pub.subscribe(self.OnUpdateHistogram, "2dview.updated.image")

    def _init_plot(self):
        # set title
        self.figure.suptitle("Histogram", fontsize=10)
        # set label
        self.axes.set_xlabel("xlabel", fontsize=6)
        self.axes.set_ylabel("ylabel", fontsize=6)
        # set ticks
        self.axes.xaxis.set_tick_params(labelsize=7)
        self.axes.yaxis.set_tick_params(labelsize=5)

    def plot_histogram_img(self, img: str):
        print("plot_histogram_img")
        self.axes.cla()
        # t = np.arange(0.0, 3.0, 0.01)
        # s = np.sin(2 * np.pi * t)
        # self.axes.plot(t, s)

        img_data = mpimg.imread(img)

        self.axes.hist(img_data.ravel(), bins=50)
        self.canvas.draw()
        self.canvas.Refresh()

    def plot_histogram(self, data_array: np.ndarray):
        print("plot_histogram")
        self.axes.cla()
        self.axes.hist(data_array, bins=100)
        self.canvas.draw()
        self.canvas.Refresh()

    def add_toolbar(self):
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        # By adding toolbar in sizer, we are able to put it at the bottom
        # of the frame - so appearance is closer to G   TK version.
        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        # update the axes menu on the toolbar
        self.toolbar.update()

    def OnUpdateHistogram(self, msg):
        """Update Histogram When 2D View Image Updated."""
        # logger.info(msg)
        image: Image.Image = msg["image_pil"]

        data_array = np.array(image)
        # logger.info(data_array)

        # flatten to 1-d array
        self.plot_histogram(data_array.ravel())

    def OnPaint(self, e):
        print(f"OnPaint: {e}")

    def OnSize(self, e):
        print(f"OnSize: {e}")
class MyDemoPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.frame = parent
        self.left_panel = wx.Panel(self, wx.ID_ANY)
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1,
                                          1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        # Plot objects -------------------------------------------------------------------------------------------------
        self.ax1 = self.figure.add_subplot(311)
        self.ax2 = self.figure.add_subplot(312)
        self.ax3 = self.figure.add_subplot(313)

        self.temporal, = self.ax1.plot([], [], linestyle='-')
        self.temporal_sum, = self.ax2.plot([], [], linestyle='-', marker='')
        self.temporal_hilbert, = self.ax2.plot([], [],
                                               linestyle='-',
                                               marker='')
        self.spectral, = self.ax3.plot([], [], color='#C02942')
        self.spectral_envelope, = self.ax3.plot([], [], color='tab:blue')

        # Plot Annotations ---------------------------------------------------------------------------------------------
        # https://stackoverflow.com/a/38677732
        self.arrow_dim_obj = self.ax3.annotate(
            "",
            xy=(0, 0),
            xytext=(0, 0),
            textcoords=self.ax3.transData,
            arrowprops=dict(arrowstyle='<->'))
        self.bar_dim_obj = self.ax3.annotate("",
                                             xy=(0, 0),
                                             xytext=(0, 0),
                                             textcoords=self.ax3.transData,
                                             arrowprops=dict(arrowstyle='|-|'))
        bbox = dict(fc="white", ec="none")
        self.dim_text = self.ax3.text(0,
                                      0,
                                      "",
                                      ha="center",
                                      va="center",
                                      bbox=bbox)

        # BINDINGS =====================================================================================================
        self.combo_window = wx.ComboBox(self.left_panel,
                                        wx.ID_ANY,
                                        choices=[
                                            "Rectangular", "Bartlett",
                                            "Hanning", "Hamming", "Blackman"
                                        ],
                                        style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_bandwidth_shape = wx.ComboBox(
            self.left_panel,
            wx.ID_ANY,
            choices=["Flat-Top", "Gaussian"],
            style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.text_ctrl_fc = wx.TextCtrl(self.left_panel,
                                        wx.ID_ANY,
                                        style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_laser_bw = wx.TextCtrl(self.left_panel,
                                              wx.ID_ANY,
                                              style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_mainlobe = wx.TextCtrl(self.left_panel,
                                              wx.ID_ANY,
                                              style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_mainlobe.SetToolTip("Mainlobe width")
        self.text_ctrl_emitted_modes = wx.TextCtrl(self.left_panel,
                                                   wx.ID_ANY,
                                                   style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_index = wx.TextCtrl(self.left_panel,
                                           wx.ID_ANY,
                                           style=wx.TE_PROCESS_ENTER)
        self.checkbox_random_phase = wx.CheckBox(self.left_panel, wx.ID_ANY,
                                                 "Random Phase")

        self.report_runtime = wx.TextCtrl(self.left_panel,
                                          wx.ID_ANY,
                                          "",
                                          style=wx.TE_READONLY)
        self.report_laser_bw = wx.TextCtrl(self.left_panel,
                                           wx.ID_ANY,
                                           "",
                                           style=wx.TE_READONLY)
        self.report_wavelength = wx.TextCtrl(self.left_panel,
                                             wx.ID_ANY,
                                             "",
                                             style=wx.TE_READONLY)
        self.report_cavity_modes = wx.TextCtrl(self.left_panel,
                                               wx.ID_ANY,
                                               "",
                                               style=wx.TE_READONLY)
        self.report_cavity_length = wx.TextCtrl(self.left_panel,
                                                wx.ID_ANY,
                                                "",
                                                style=wx.TE_READONLY)
        self.report_df = wx.TextCtrl(self.left_panel,
                                     wx.ID_ANY,
                                     "",
                                     style=wx.TE_READONLY)
        self.report_longitudinal_modes = wx.TextCtrl(self.left_panel,
                                                     wx.ID_ANY,
                                                     "",
                                                     style=wx.TE_READONLY)
        self.report_fwhm = wx.TextCtrl(self.left_panel,
                                       wx.ID_ANY,
                                       "",
                                       style=wx.TE_READONLY)
        self.report_fwhm_width = wx.TextCtrl(self.left_panel,
                                             wx.ID_ANY,
                                             "",
                                             style=wx.TE_READONLY)

        on_update = lambda event: self.update(event)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_fc)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_laser_bw)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_mainlobe)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_emitted_modes)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_index)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_update,
                  self.combo_bandwidth_shape)
        self.Bind(wx.EVT_CHECKBOX, on_update, self.checkbox_random_phase)

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()
        self.update(wx.Event)

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(240, 240, 240))
        self.canvas.SetMinSize((700, 490))

        self.combo_window.SetSelection(0)
        self.combo_bandwidth_shape.SetSelection(0)
        self.checkbox_random_phase.SetValue(0)

        self.text_ctrl_fc.SetValue("473.613")  # (THz)
        self.text_ctrl_laser_bw.SetValue("0.1")
        self.text_ctrl_index.SetValue("1.0")
        self.text_ctrl_emitted_modes.SetValue("15")
        self.text_ctrl_mainlobe.SetValue("0.01")

        self.report_runtime.SetValue("--")
        self.report_laser_bw.SetValue("--")
        self.report_wavelength.SetValue("--")
        self.report_cavity_modes.SetValue("--")
        self.report_cavity_length.SetValue("--")
        self.report_df.SetValue("--")
        self.report_longitudinal_modes.SetValue("--")
        self.report_fwhm.SetValue("--")
        self.report_fwhm_width.SetValue("--")

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)
        grid_sizer_left_panel = wx.GridBagSizer(0, 0)

        # LEFT PANEL ---------------------------------------------------------------------------------------------------
        # TITLE --------------------------------------------------------------------------------------------------------
        row = 0
        label_1 = wx.StaticText(self.left_panel, wx.ID_ANY,
                                "LASER MODE-LOCKING")
        label_1.SetFont(
            wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT | wx.TOP, 5)

        row += 1
        static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_1.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # PARAMETERS ---------------------------------------------------------------------------------------------------
        row += 2
        lbl_settings = wx.StaticText(self.left_panel, wx.ID_ANY, "Parameters")
        lbl_settings.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_settings, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        lbl_fc = wx.StaticText(self.left_panel, wx.ID_ANY, "Fc:")
        grid_sizer_left_panel.Add(
            lbl_fc, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_fc, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        lbl_units_THz = wx.StaticText(self.left_panel, wx.ID_ANY, "(THz):")
        grid_sizer_left_panel.Add(
            lbl_units_THz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_laser_bw = wx.StaticText(self.left_panel, wx.ID_ANY, "Laser BW:")
        grid_sizer_left_panel.Add(
            lbl_laser_bw, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_laser_bw, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        lbl_units_laser_bw = wx.StaticText(self.left_panel, wx.ID_ANY,
                                           "(x Fc)")
        grid_sizer_left_panel.Add(
            lbl_units_laser_bw, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_emitted_modes = wx.StaticText(self.left_panel, wx.ID_ANY,
                                          "Emitted Modes:")
        lbl_emitted_modes.SetToolTip(
            "The number of emitted modes inside the cavity")
        grid_sizer_left_panel.Add(
            lbl_emitted_modes, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_emitted_modes, (row, 1),
                                  (1, 1), wx.BOTTOM, 5)

        row += 1
        lbl_reflective_index = wx.StaticText(self.left_panel, wx.ID_ANY,
                                             "Reflective Index:")
        grid_sizer_left_panel.Add(
            lbl_reflective_index, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_index, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)

        row += 1
        label_bandwidth_shape = wx.StaticText(self.left_panel, wx.ID_ANY,
                                              "Gain Bandwidth Shape:")
        grid_sizer_left_panel.Add(
            label_bandwidth_shape, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.combo_bandwidth_shape, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        grid_sizer_left_panel.Add(self.checkbox_random_phase, (row, 1), (1, 1),
                                  wx.LEFT | wx.TOP, 5)

        # SAMPLING PARAMETERS ------------------------------------------------------------------------------------------
        row += 1
        lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "SAMPLING")
        lbl_results.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_3.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        label_window = wx.StaticText(self.left_panel, wx.ID_ANY,
                                     "Windowing Function:")
        grid_sizer_left_panel.Add(
            label_window, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.combo_window, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "Mainlobe Width:")
        grid_sizer_left_panel.Add(
            label_Hz, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_mainlobe, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "MLW (Hz)")
        grid_sizer_left_panel.Add(label_Hz, (row, 2), (1, 1), wx.BOTTOM, 5)

        # REPORT -------------------------------------------------------------------------------------------------------
        row += 1

        row += 1
        lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "REPORT")
        lbl_results.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_3.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        lbl_runtime = wx.StaticText(self.left_panel, wx.ID_ANY,
                                    "Total Runtime:")
        grid_sizer_left_panel.Add(
            lbl_runtime, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_runtime, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_ps = wx.StaticText(self.left_panel, wx.ID_ANY, "(ps)")
        grid_sizer_left_panel.Add(label_ps, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        label_bandwidth_shape = wx.StaticText(self.left_panel, wx.ID_ANY,
                                              "Laser BW:")
        grid_sizer_left_panel.Add(
            label_bandwidth_shape, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_laser_bw, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_THz = wx.StaticText(self.left_panel, wx.ID_ANY, "(THz)")
        grid_sizer_left_panel.Add(label_THz, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        label_wavelength = wx.StaticText(self.left_panel, wx.ID_ANY,
                                         "Wavelength, λ:")
        grid_sizer_left_panel.Add(
            label_wavelength, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_wavelength, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_nm = wx.StaticText(self.left_panel, wx.ID_ANY, "(nm)")
        grid_sizer_left_panel.Add(label_nm, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        label_cavity_modes = wx.StaticText(self.left_panel, wx.ID_ANY,
                                           "Cavity Modes, m:")
        grid_sizer_left_panel.Add(
            label_cavity_modes, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_cavity_modes, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        label_cavity_length = wx.StaticText(self.left_panel, wx.ID_ANY,
                                            "Cavity Length, L:")
        grid_sizer_left_panel.Add(
            label_cavity_length, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_cavity_length, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_nm = wx.StaticText(self.left_panel, wx.ID_ANY, "(mm)")
        grid_sizer_left_panel.Add(label_nm, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        label_df = wx.StaticText(self.left_panel, wx.ID_ANY,
                                 "Frequency Separation, df:")
        grid_sizer_left_panel.Add(
            label_df, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_df, (row, 1), (1, 1), wx.BOTTOM,
                                  5)
        label_GHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(GHz)")
        grid_sizer_left_panel.Add(label_GHz, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        label_longitudinal_modes = wx.StaticText(self.left_panel, wx.ID_ANY,
                                                 "Longitudinal Modes:")
        grid_sizer_left_panel.Add(
            label_longitudinal_modes, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_longitudinal_modes, (row, 1),
                                  (1, 2), wx.BOTTOM, 5)

        row += 1
        lbl_fwhm = wx.StaticText(self.left_panel, wx.ID_ANY, "FWHM:")
        grid_sizer_left_panel.Add(
            lbl_fwhm, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_fwhm, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_blank = wx.StaticText(self.left_panel, wx.ID_ANY, "")
        grid_sizer_left_panel.Add(label_blank, (row, 2), (1, 1), wx.BOTTOM, 5)

        row += 1
        lbl_fwhm_width = wx.StaticText(self.left_panel, wx.ID_ANY,
                                       "FWHM Width:")
        grid_sizer_left_panel.Add(
            lbl_fwhm_width, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.report_fwhm_width, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        label_GHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(GHz)")
        grid_sizer_left_panel.Add(label_GHz, (row, 2), (1, 1), wx.BOTTOM, 5)

        self.left_panel.SetSizer(grid_sizer_left_panel)

        # PLOT PANEL ===================================================================================================
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)

        # add to main panel --------------------------------------------------------------------------------------------
        grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    def popup_dialog(self, message):
        print(message)
        dial = wx.MessageDialog(None, str(message), 'Error',
                                wx.OK | wx.ICON_ERROR)
        dial.ShowModal()

    def get_values(self):
        fc = to_float(self.text_ctrl_fc.GetValue())
        laser_bw = to_float(self.text_ctrl_laser_bw.GetValue())
        emitted_modes = to_integer(self.text_ctrl_emitted_modes.GetValue())
        refraction_index = to_float(self.text_ctrl_index.GetValue())
        bandwidth_shape = str(self.combo_bandwidth_shape.GetValue()).lower()
        window = str(self.combo_window.GetValue()).lower()
        MLW = to_float(self.text_ctrl_mainlobe.GetValue())
        random_phase = bool(self.checkbox_random_phase.GetValue())

        return fc, laser_bw, emitted_modes, refraction_index, bandwidth_shape, window, MLW, random_phase

    def update(self, evt):
        try:
            params = self.get_values()

            data, plot_data, plot_limits = worker.worker(params)

            self.results_update(data)
            self.plot(plot_data, plot_limits)

        except ValueError as e:
            self.popup_dialog(e)

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ps)')
        self.ax1.set_ylabel('AMPLITUDE')

        self.ax2.set_title('SUMMATION OF ALL MODES/TONES')
        self.ax2.set_xlabel('TIME (ps)')
        self.ax2.set_ylabel('AMPLITUDE')

        self.ax3.set_title('SPECTRAL DATA')
        self.ax3.set_xlabel('FREQUENCY (THz)')
        self.ax3.set_ylabel('MAGNITUDE (V)')

        self.ax3.grid()
        self.figure.align_ylabels([self.ax1, self.ax2, self.ax3])
        self.figure.tight_layout()

    def plot(self, plot_data, plot_limits):

        xt, yt_list, yt, yt_envelope, xf_rfft, yf_rfft, yf_smooth = plot_data
        xt1_left, xt1_right, xt2_left, xt2_right, xf_left, xf_right, dim_left, dim_right, dim_height, dim_label, dim_label_pos = plot_limits

        xt_scale = 1e12
        xf_scale = 1e12

        # TEMPORAL -----------------------------------------------------------------------------------------------------
        xt_delta = xt[1] - xt[0]
        yt_limit = int(xt1_right / xt_delta)
        yt_limit2 = int(xt2_right / xt_delta)

        self.ax1.clear()
        self.ax1.plot(xt[:yt_limit] * xt_scale,
                      (yt_list[:, :yt_limit]).T)  # All signals
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ps)')
        self.ax1.set_ylabel('AMPLITUDE')

        self.temporal_sum.set_data(
            xt[:yt_limit2] * xt_scale,
            yt[:yt_limit2])  # The summation of all signals
        self.temporal_hilbert.set_data(
            xt[:yt_limit2] * xt_scale,
            yt_envelope[:yt_limit2])  # The envelope of the summation

        self.ax1.set_xlim(left=xt1_left * xt_scale, right=xt1_right * xt_scale)

        self.ax2.set_xlim(left=xt2_left * xt_scale, right=xt2_right * xt_scale)

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        self.spectral.set_data(xf_rfft / xf_scale,
                               np.abs(yf_rfft))  # The spectral plot of sum
        self.spectral_envelope.set_data(xf_rfft / xf_scale,
                                        yf_smooth)  # The spectral plot of sum

        self.ax3.set_xlim(left=xf_left / xf_scale, right=xf_right / xf_scale)

        # Arrow dimension line update ----------------------------------------------------------------------------------
        # https://stackoverflow.com/a/48684902 -------------------------------------------------------------------------
        self.arrow_dim_obj.xy = (dim_left, dim_height)
        self.arrow_dim_obj.set_position((dim_right, dim_height))
        self.arrow_dim_obj.textcoords = self.ax3.transData

        # dimension text update ----------------------------------------------------------------------------------------
        self.dim_text.set_position((dim_left + dim_label_pos, dim_height))
        self.dim_text.set_text(dim_label)

        # REDRAW PLOT --------------------------------------------------------------------------------------------------
        self.plot_redraw()

    def plot_redraw(self):
        try:
            self.ax1.relim()  # recompute the ax.dataLim
            self.ax2.relim()  # recompute the ax.dataLim
            self.ax3.relim()  # recompute the ax.dataLim
        except MemoryError as e:
            raise ValueError(str(e))

        self.ax1.margins(x=0)
        self.ax1.autoscale(axis='y')
        self.ax2.autoscale(axis='y')
        self.ax3.autoscale(axis='y')

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()
        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def results_update(self, data):
        wavelength, laser_bw, df_max, cavity_modes, cavity_length, cavity_df, longitudinal_modes, fwhm_val, fwhm_width, runtime = data

        self.report_runtime.SetValue(str(round(runtime * 1e12, 3)))
        self.report_laser_bw.SetValue(str(laser_bw / 1e12))
        self.report_cavity_length.SetValue(str(laser_bw / 1e12))
        self.report_wavelength.SetValue(str(round(wavelength * 1e9, 3)))

        self.report_df.SetValue(str(round(df_max / 1e9, 3)))
        self.report_cavity_modes.SetValue(str(cavity_modes))
        self.report_cavity_length.SetValue(str(round(cavity_length * 1e3, 3)))
        self.report_longitudinal_modes.SetValue(str(longitudinal_modes))

        self.report_fwhm.SetValue(str(round(fwhm_val, 2)))
        self.report_fwhm_width.SetValue(str(round(fwhm_width * 1e12, 3)))

        print('total runtime:', round(runtime * 1e12, 3), 'ps')
        print('laser bandwidth:', laser_bw / 1e12, 'THz')
        print('full wave, half maximum:', laser_bw / 1e12, 'THz')
        print('wavelength, lambda:', round(wavelength * 1e9, 3), 'nm')
        print()
        print('max frequency separation for number of emitted modes, df:',
              round(df_max / 1e9, 3), 'GHz')
        print('cavity modes, m:', cavity_modes)
        print('cavity length, L:', round(cavity_length * 1e2, 3), 'cm',
              round(cavity_length * 1e3, 3), '(mm)')
        print('frequency separation of cavity, df:', round(cavity_df / 1e9, 3),
              'GHz')
        print('longitudinal modes supported:', longitudinal_modes)
        print()
        print('FWHM value:', round(fwhm_val, 2))
        print('FWHM width:', round(fwhm_width * 1e12, 3), 'ps')
        print()
Beispiel #14
0
class PlotWindow(wx.Panel):
    ## BUG: Re the blinking of the cursor by using animation. The problem is
    ## update_slice_plots(), which redraws the whole canvas, instead of only the
    ## slice axes. This can only be solved using animations (FuncAnimation)
    colors = ['b', 'r', 'g', 'c', 'm', 'y', 'k']

    def __init__(self, parent=None, frame=None, id_=wx.ID_ANY, **kwargs):
        super(PlotWindow, self).__init__(parent, id_, **kwargs)

        self.parent = parent
        self.frame = frame
        self.dpi = 70
        self.figure = plt.figure(figsize=(7.0, 7.0), dpi=self.dpi)
        self.figure.patch.set_color('w')
        self.canvas = FigureCanvas(self, wx.ID_ANY, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.button_reload_data = wx.ToggleButton(self,
                                                  wx.ID_ANY,
                                                  label="Reload data")
        self.button_reload_data_popup = ReloadDataContextMenu(
            self._on_reload_data_delay_changed)
        self.timer_reload_data = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self._on_reload_data)
        self.reload_data_delay = None

        self.xp_params = {"major_formatter": FormatStrFormatter("%.2f")}
        self.yp_params = copy.copy(self.xp_params)

        self._init_plots()
        self._init_plot_context_menu()

        sizer1 = wx.BoxSizer(wx.HORIZONTAL)
        sizer1.Add(self.toolbar,
                   proportion=0,
                   flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL,
                   border=0)
        sizer1.Add(self.button_reload_data,
                   proportion=0,
                   flag=wx.LEFT | wx.ALIGN_CENTER_VERTICAL,
                   border=5)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(sizer1, proportion=0, flag=wx.EXPAND)
        sizer.Add(self.canvas, proportion=1, flag=wx.EXPAND)

        self.SetSizer(sizer)

        # Cursor list [intensity plot, xplot, yplot]
        self.cursors = [[], [], []]
        self.create_cursor(1, self.intensity_plot, color='black', linewidth=2)
        self.canvas.mpl_connect('cursor_motion_event', self.cursor_handler)

        self.data = None
        self.x = None
        self.y = None

        self.image = None
        self.x_idx = -1
        self.y_idx = -1
        self.x_slice = None
        self.y_slice = None
        self._autoscale = {}
        self._autoscale[self.x_slice_plot] = (True, True)
        self._autoscale[self.y_slice_plot] = (True, True)

        self._bind_events()

    def _init_plots(self):

        # plt.xkcd(scale=0.5, length=200, randomness=50)

        gs = gridspec.GridSpec(2, 2, width_ratios=[3, 1], height_ratios=[3, 1])
        self.x_slice_plot = plt.subplot(gs[2])
        self.x_slice_plot.ticklabel_format(style='sci',
                                           scilimits=(0, 0),
                                           useOffset=False,
                                           axis='both')

        self.y_slice_plot = plt.subplot(gs[1])
        self.y_slice_plot.ticklabel_format(style='sci',
                                           scilimits=(0, 0),
                                           useOffset=False,
                                           axis='both')
        self.y_slice_plot.yaxis.tick_right()
        self.y_slice_plot.yaxis.set_label_position("right")

        self.y_slice_plot.xaxis.set_major_locator(MaxNLocator(4, prune=None))

        self.intensity_plot = plt.subplot(gs[0],
                                          sharex=self.x_slice_plot,
                                          sharey=self.y_slice_plot)
        ###
        self.intensity_plot.set_zorder(1)

        # The default configuration of matplotlib sets the _hold parameter to True,
        # which cause the data reload action to leak memory. We override the _hold
        # setting here to prevent this.

        self.x_slice_plot._hold = False
        self.y_slice_plot._hold = False
        self.intensity_plot._hold = False

    def _init_plot_context_menu(self):
        self._plot_cm = []
        for axes in [
                self.intensity_plot, self.x_slice_plot, self.y_slice_plot
        ]:

            if axes == self.intensity_plot:
                menu = PlotContextMenu(autoscale=False)
            else:
                menu = PlotContextMenu()
                menu.Bind(wx.EVT_MENU,
                          lambda event, inaxes=axes: self._on_autoscale(
                              event, inaxes),
                          id=menu.menu_id_by_title["Autoscale"])

            menu.Bind(
                wx.EVT_MENU,
                lambda event, inaxes=axes: self.recenter_cursor(event, inaxes),
                id=menu.menu_id_by_title["Recenter Cursor"])
            menu.Bind(wx.EVT_MENU,
                      lambda event, inaxes=axes: self.on_create_cursor(
                          event, inaxes),
                      id=menu.menu_id_by_title["Create Cursor"])
            menu.Bind(wx.EVT_MENU,
                      lambda event, inaxes=axes: self.on_delete_cursor(
                          event, inaxes),
                      id=menu.menu_id_by_title["Delete Cursor"])

            self._plot_cm.append(menu)

    def _bind_events(self):
        self.canvas.mpl_connect('motion_notify_event', self.motion_handler)
        self.canvas.mpl_connect('button_press_event', self.right_click_handler)

        self.button_reload_data.Bind(wx.EVT_TOGGLEBUTTON,
                                     self._on_reload_data_toggled)
        self.button_reload_data.Bind(
            wx.EVT_CONTEXT_MENU,
            lambda event: self.PopupMenu(self.button_reload_data_popup))

    def _on_reload_data_toggled(self, event):
        if self.reload_data_delay is None:
            # manual reload
            self._on_reload_data(event)
            self.button_reload_data.SetValue(False)
        else:
            # start/stop timer
            self._reload_data_timer_setup()

    def _on_reload_data_delay_changed(self, event):
        delay = self.button_reload_data_popup.ids[event.Id]
        self.reload_data_delay = delay
        self.button_reload_data.SetValue(False if (
            self.reload_data_delay is None) else True)
        self._reload_data_timer_setup()

    def _reload_data_timer_setup(self):
        if (self.reload_data_delay
                is not None) and self.button_reload_data.GetValue():
            self.timer_reload_data.Start(self.reload_data_delay)
        else:
            self.timer_reload_data.Stop()

    def _on_reload_data(self, event):
        main_frame = wx.GetTopLevelParent(self)
        main_frame.reload_data()

    def create_cursor(self, id_, axes, **lineprops):
        idx = self._get_axes_index(axes)
        for cursor in self.cursors[idx]:
            if id_ == cursor.id:
                raise ValueError("Cursor id already exists")
                #return

        if id_ == 1:
            lineprops["color"] = 'black'

        # This is a custom cursor, not a wxWidgets cursor,
        # nor a matplotlib cursor.

        cursor = Cursor(id_, axes, **lineprops)

        self.cursors[idx].append(cursor)
        self.GrandParent.cursor_prop_window.cursor_props[idx].add(
            'Cursor' + str(id_), cursor.position)
        cursor.recenter()
        self.repaint()

    def delete_cursor(self, axes):
        # Delete the most recently create cursor (ie we pop off the end of the list)
        idx = self._get_axes_index(axes)
        # Do not delete the data cursor to select the data slices
        if idx == 0:
            if len(self.cursors[0]) <= 1:
                return

        if self.cursors[idx]:
            cursor = self.cursors[idx].pop()
            self.GrandParent.cursor_prop_window.cursor_props[idx].remove(
                'Cursor' + str(cursor.id))
            cursor.remove()
            self.repaint()

    def motion_handler(self, event):
        # This is bound mouse motion events
        # It does not perform redraw.

        # If zooming and panning enabled, disable cursor events
        # to not interfere with zooming

        ### MIJ This stuff needs to be moved to the toolbar setting processing,
        ### not every little mouse motion event!

        if self.toolbar.mode != '':
            for cursors in self.cursors:
                [cursor.disconnect() for cursor in cursors]
        else:
            for cursors in self.cursors:
                [cursor.connect() for cursor in cursors]

        # Format and write the position information (or message) for the status bar
        # at the very base of the window

        if self.data is None:
            status_bar_text = 'no data'
        elif ((event.inaxes == self.intensity_plot)
              | (event.inaxes == self.x_slice_plot)
              | (event.inaxes == self.y_slice_plot)):
            status_bar_text = 'x = {0:.2e}, y = {1:.2e}'.format(
                event.xdata, event.ydata)
        else:
            return

        self.frame.status_bar.SetStatusText(status_bar_text)

    def right_click_handler(self, event):
        if event.button != 3:
            return

        axes_list = [self.intensity_plot, self.x_slice_plot, self.y_slice_plot]

        try:
            idx = axes_list.index(event.inaxes)
        except ValueError:
            return

        self.PopupMenu(self._plot_cm[idx])

    def _get_axes_index(self, axes):
        if axes == self.intensity_plot:
            return 0
        elif axes == self.x_slice_plot:
            return 1
        elif axes == self.y_slice_plot:
            return 2
        else:
            return

    def _on_autoscale(self, event, inaxes):
        self._autoscale[inaxes] = event.IsChecked()
        self.update_slice_plots(self.data[self.y_idx],
                                np.transpose(self.data)[self.x_idx])

    def recenter_cursor(self, event, inaxes):
        i = self._get_axes_index(inaxes)
        if i is None:
            return

        [cursor.recenter() for cursor in self.cursors[i]]

    def _cursors_recenter(self):
        for cursors in self.cursors:
            [cursor.recenter() for cursor in cursors]

    def on_create_cursor(self, event, inaxes):
        idx = self._get_axes_index(inaxes)
        cursors = self.cursors[idx]
        if not cursors:
            id_ = 1
        else:
            id_ = cursors[-1].id + 1

        self.create_cursor(id_, inaxes)

    def on_delete_cursor(self, event, inaxes):
        self.delete_cursor(inaxes)

    def cursor_handler(self, event):
        if event.inaxes == self.intensity_plot:
            if event.id == 1:
                self.data_cursor_handler(event)

        idx = self._get_axes_index(event.cursor.ax)

        self.GrandParent.cursor_prop_window.cursor_props[idx].update(
            'Cursor' + str(event.id), event.cursor.position)

    def data_cursor_handler(self, event):
        if self.data is None:
            return

        pos = event.xdata, event.ydata
        self.x_idx = np.argmin(np.abs(self.x - pos[0]))
        self.y_idx = np.argmin(np.abs(self.y - pos[1]))

        self.update_slice_plots(self.data[self.y_idx],
                                np.transpose(self.data)[self.x_idx])

    def update_slice_plots(self, xdata, ydata):
        self.x_slice.set_ydata(xdata)
        self.y_slice.set_xdata(ydata)

        if self._autoscale[self.x_slice_plot]:
            self.x_slice_plot.set_ylim((min(xdata), max(xdata)))
        if self._autoscale[self.y_slice_plot]:
            self.y_slice_plot.set_xlim((min(ydata), max(ydata)))

        #MIJ this overdraws the cursors, slowly because we keep redrawing the data over the top
        # and we should only redraw the slices anyway, not the 2D intensity. This has been disabled
        # to remove significant lag. The slices get updated on button release

        #self.repaint()
        #self.canvas.draw()

    def draw(self, x, y, data, recenter=True):

        ###DEBUG
        #print 'draw (1)'
        #objgraph.show_growth(limit=20)
        #print
        ###END

        # The parameter data is retained for the data_cursor_handler

        self.data = data
        self.x = x
        self.y = y

        # Reset the home, back and forward button on the
        # Navigation toolbar
        self.toolbar.update()

        if data.shape[0] < abs(self.y_idx):
            self.y_idx = -1
        xslice = data[self.y_idx]

        if data.shape[1] < abs(self.x_idx):
            self.x_idx = -1
        yslice = np.transpose(data)[self.x_idx]

        self.x_slice, = self.x_slice_plot.plot(
            x, xslice)  #horizontal slice below main image
        self.y_slice, = self.y_slice_plot.plot(
            yslice, y)  #vertical slice to right of main image

        deltas = []
        for coord in [x, y]:
            if len(coord) == 1:
                deltas.append(1)
            else:
                #deltas.append(coord[1] - coord[0])
                deltas.append(np.min(np.diff(coord)))

        px = np.append(x, x[-1] + deltas[0]) - deltas[0] / 2.
        py = np.append(y, y[-1] + deltas[1]) - deltas[1] / 2.

        self.intensity_plot.pcolormesh(
            px,
            py,
            data,
        )

        #self.intensity_plot.imshow(data, interpolation='none',
        #aspect='auto', origin='lower',
        #extent=[x[0] - deltas[0]/2.0,
        #x[-1] + deltas[0]/2.0,
        #y[0] - deltas[1]/2.0,
        #y[-1] + deltas[1]/2.0])

        self.intensity_plot.set_xlim(x[0], x[-1])
        self.intensity_plot.set_ylim(y[0], y[-1])

        if recenter:
            self._cursors_recenter()

        self.figure.tight_layout()
        self.repaint()

        ###DEBUG
        #print 'draw (8)'
        #objgraph.show_growth(limit=20)
        #print
        ###END

    def repaint(self):
        #        self.figure.tight_layout()
        self.canvas.draw()
Beispiel #15
0
class DataLogger_Base(GUI_datalogger.Frame_datalogger):

    # --- Class parameters -----------------------------------------------------
    plot_update_period_ms = 200  # Actualizar grafico a 5Hz

    # --- Initialization and main events ---------------------------------------
    def __init__(self, parent):
        # Base class
        GUI_datalogger.Frame_datalogger.__init__(self, parent)

        # Set icon
        icon = wx.Icon()
        icon.CopyFromBitmap(wx.Bitmap("icon.png", wx.BITMAP_TYPE_ANY))
        self.SetIcon(icon)

        # Datalogger reference
        self.device = None
        self.lastData = None
        self.lastDataStr = None

        # Sensor listing
        self.check_sensors = []
        for i in range(N_SENSORS):
            # checkboxes
            cb = wx.CheckBox(self.m_panel_checkboxes, wx.ID_ANY,
                             NAMES[i][0] + ' - ' + NAMES[i][1],
                             wx.DefaultPosition, wx.DefaultSize, 0)
            cb.SetValue(True)
            self.bSizerCB.Add(cb, 1, wx.ALL, 5)
            self.check_sensors.append(cb)

        # Logger timer
        self.timerLog = wx.Timer(self, id=wx.ID_ANY)
        self.Bind(wx.EVT_TIMER, self.Log, id=self.timerLog.GetId())

        # Data list initialization
        n_temp = 15  # Example points
        self.data = [[20 * (i + random.gauss(0, 1)) for i in range(n_temp)]
                     for x in range(N_SENSORS)]  # Temperature data
        self.timedata = [
            datetime.now() + date.timedelta(hours=i) for i in range(n_temp)
        ]  # Time data
        self.npdata = np.array(self.data)
        self.nptimedata = np.array(self.timedata)
        self.runningLog = False
        self.m_sliderXmax.Max = n_temp
        self.m_sliderXmax.SetValue(n_temp)

        # Plot panel
        self.create_main_panel()

        # Plot timer
        #self.redraw_timer = wx.Timer(self)
        #self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer)
        #self.redraw_timer.Start(self.plot_update_period_ms)

        # Initialize plot
        self.draw_plot()

        # Relayout workaround
        self.Maximize(True)
        self.Layout()
        self.Maximize(False)

    def OnClose(self, event):
        # Stop timers
        #self.redraw_timer.Stop()
        self.timerLog.Stop()

        # Close device connections
        if self.device is not None:
            self.device.close()

        # Continue closing
        event.Skip()

    # Status message
    def flash_status_message(self, msg, flash_ms=2000, n=0, clear=True):
        print msg
        self.m_statusBar.SetStatusText(msg, n)
        if clear:
            self.timeroff = wx.Timer(self)
            self.Bind(wx.EVT_TIMER, self.on_flash_status_off, self.timeroff)
            self.timeroff.Start(flash_ms, oneShot=True)

    def on_flash_status_off(self, event):
        self.m_statusBar.SetStatusText('')

    # --- Plot functions -------------------------------------------------------
    def add_toolbar(self):
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        self.vbox.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        self.toolbar.update()  # update the axes menu on the toolbar

    def create_main_panel(self):
        #self.panel = wx.Panel(self)  # Already created in GUI layout

        self.init_plot()
        self.canvas = FigCanvas(self.panel, -1, self.fig)

        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
        self.panel.SetSizer(self.vbox)
        self.add_toolbar()

    def init_plot(self):
        self.dpi = 100
        self.fig = Figure((3.0, 3.0), dpi=self.dpi)

        self.axes = self.fig.add_subplot(111)
        if hasattr(self.axes, 'set_facecolor'):
            self.axes.set_facecolor('black')
        else:
            self.axes.set_axis_bgcolor('black')  # Deprecated
        self.axes.set_title('Datalogger Data', size=12)

        pylab.setp(self.axes.get_xticklabels(), fontsize=8)
        pylab.setp(self.axes.get_yticklabels(), fontsize=8)

        # Plot the data as a line series, and save the reference to the plotted line series
        self.plot_data = []
        for i in range(N_SENSORS):
            self.plot_data.append(
                self.axes.plot(self.timedata,
                               self.data[i],
                               linewidth=1,
                               color=COLORS[i],
                               label=NAMES[i][1])[0])

        # Time plot:	https://stackoverflow.com/questions/1574088/plotting-time-in-python-with-matplotlib
        # Time format:	https://stackoverflow.com/questions/28866530/how-to-remove-microseconds-from-matplotlib-plot
        self.fig.autofmt_xdate()
        self.axes.xaxis.set_major_formatter(
            matplotlib.dates.DateFormatter('%H:%M:%S'))
        self.axes.legend(loc=2, fontsize=8)  #loc='upper center', shadow=True)

    def draw_plot(self):
        if len(self.data[0]) <= 1 or not self.m_checkBoxUpdatePlot.GetValue():
            return

        # Set X bounds
        if self.m_checkBoxAutoXAxis.IsChecked():
            self.m_sliderXmin.Max = len(self.timedata) - 2  #1
            #print self.m_sliderXmax.GetValue(), self.m_sliderXmax.Max
            xmaxAtMax = self.m_sliderXmax.GetValue() >= self.m_sliderXmax.Max
            self.m_sliderXmax.Max = len(self.timedata)  #- 1
            if xmaxAtMax:
                self.m_sliderXmax.SetValue(self.m_sliderXmax.Max)

            try:
                self.axes.set_xbound(
                    lower=self.timedata[self.m_sliderXmin.GetValue()],
                    upper=self.timedata[self.m_sliderXmax.GetValue() - 1])
            except:
                print '-'

        # Generar data
        self.nptimedata = np.array(self.timedata[self.m_sliderXmin.GetValue(
        ):self.m_sliderXmax.GetValue()])
        self.npdata = np.array([
            self.data[i]
            [self.m_sliderXmin.GetValue():self.m_sliderXmax.GetValue()]
            for i in range(N_SENSORS)
        ])

        # Set Y bounds
        if self.m_checkBoxAutoYAxis.IsChecked():
            idx_min = 0
            idx_max = len(self.npdata[0])
            # Define range if restricted
            # if self.m_checkBoxAutoXAxis.IsChecked():
            # 	idx_min = self.m_sliderXmin.GetValue()
            # 	idx_max = self.m_sliderXmax.GetValue()
            # 	if idx_min > idx_max:
            # 		tmp     = idx_min
            # 		idx_min = idx_max
            # 		idx_max = tmp
            # 	if idx_max == idx_min:
            # 		idx_max += 1

            # Search for minimal value among plots
            x = 1000
            for i in range(len(self.npdata)):  #range(N_SENSORS):
                if self.check_sensors[i].GetValue():
                    x = min(x, min(self.npdata[i][idx_min:idx_max]))
            ymin = round(x, 0) - 1

            # Search for maximal value
            x = -1000
            for i in range(len(self.npdata)):  #range(N_SENSORS):
                if self.check_sensors[i].GetValue():
                    x = max(x, max(self.npdata[i][idx_min:idx_max]))
            ymax = round(x, 0) + 1

            self.axes.set_ybound(lower=ymin, upper=ymax)

        # Anecdote: axes.grid assumes b=True if any other flag is given even if b is set to False.
        # So just passing the flag into the first statement won't work.
        if self.cb_grid.IsChecked():
            self.axes.grid(True, color='gray')
        else:
            self.axes.grid(False)

        # Update plot
        for i in range(N_SENSORS):
            self.plot_data[i].set_xdata(
                self.nptimedata)  #np.array(self.timedata))
            self.plot_data[i].set_ydata(self.npdata[i])
            self.plot_data[i].set_visible(self.check_sensors[i].GetValue())

        self.canvas.draw()

    def Redraw_Plot(self, event):
        self.draw_plot()

    #def on_redraw_timer(self, event):
    #	self.draw_plot()

    # --- Log Functions --------------------------------------------------------
    # Verificar input usuario
    def OnTextPeriod(self, event):
        s = event.GetEventObject().Value
        if len(s) == 0:
            return
        try:
            int(s)
        except:
            event.GetEventObject().Value = '1000'

    # Actualizar periodo de log
    def UpdateLogPeriod(self, event):
        if not self.runningLog:
            self.flash_status_message('Logger is not running!')
            return

        if len(self.m_textPeriod.Value) == 0:
            self.flash_status_message('Set timer period first !')
            return

        # Re starts with new period
        self.timerLog.Start(int(self.m_textPeriod.Value))
        self.flash_status_message('Period updated to %s ms' %
                                  self.m_textPeriod.Value)

    # Comenzar a medir y graficar
    def StartLog(self, event):
        if self.runningLog:
            self.flash_status_message('Logger already started!')
            return

        if len(self.m_textPeriod.Value) == 0:
            self.flash_status_message('Set timer period first !')
            return

        try:
            #if self.m_checkBoxDebug.GetValue():
            #	self.datagen = DataGen()
            #else:
            self.device = datalogger.Datalogger_device(
                debug=self.m_checkBoxDebug.GetValue())

            self.data = [[] for x in range(N_SENSORS)]  # Reset plot
            self.timedata = []
            #self.starttime = time.time()

            self.m_sliderXmin.SetValue(0)
            self.m_sliderXmax.SetValue(self.m_sliderXmax.Max)

            self.timerLog.Start(int(self.m_textPeriod.Value))
            self.runningLog = True

            self.mail_warning_sent = False
            self.mail_error_sent = False

            self.actual_hour = time.localtime().tm_hour
            self.actual_minute = time.localtime().tm_min

            self.flash_status_message('Logger started', n=0, clear=False)

        except Exception as e:
            self.flash_status_message('Error:\n' + str(e), clear=False)

        self.m_checkBoxDebug.Disable()

    def StopLog(self, event):
        if not self.runningLog:
            self.flash_status_message('Logger is not running!')
            return

        self.timerLog.Stop()

        #if not self.m_checkBoxDebug.GetValue():
        try:
            self.device.close()  # Close connection
            self.device = None
        except Exception as e:
            self.flash_status_message('Error:\n' + str(e), clear=False)

        self.runningLog = False
        self.flash_status_message('Logger stopped', n=0, clear=False)

        self.m_checkBoxDebug.Enable()

    def RestartLog(self):
        self.StopLog(None)

        print 'Restarting,'
        for i in range(3):
            time.sleep(1)
            print '.',
        print ''

        self.StartLog(None)

    def Log(self, event):
        data = self.device.logData()

        # Plot data
        for i in range(N_SENSORS):
            self.data[i].append(data[i])

        # Time data
        now = datetime.now()
        self.timedata.append(now)

        # Print new data
        datastr = now.strftime('%H:%M:%S') + ': ['
        for d in data:
            datastr += '%.3f' % d + ', '
        datastr = datastr[:-2] + ']'
        self.flash_status_message(datastr, n=1, clear=False)

        # Keep last data
        self.lastData = data
        self.lastDataStr = datastr

        # PLOT !
        self.draw_plot()

        # Eror monitoring
        if not self.m_checkBoxDebug.GetValue():

            if not self.mail_warning_sent and WARNING_CONDITION:
                self.send_mail('Warning: warning message')
                self.mail_warning_sent = True

            if not self.mail_error_sent and DANGER_CONDITION:
                self.send_mail('Error: error message')
                self.mail_error_sent = True
                self.device.stop_problematic_process()

            # Mandar mail de status-ok cada hora
            if self.actual_hour != time.localtime().tm_hour:  # Paso una hora
                self.actual_hour = time.localtime().tm_hour  # Update hora
                self.send_mail('Status OK')

            # Chequear cada minuto estado de conexion
            if self.actual_minute != time.localtime().tm_min:  # Paso un minuto
                self.actual_minute = time.localtime().tm_min
                self.PrintStatus(None)

                if self.device.connection_error():
                    print 'Connection problem, restarting connection and log. . .'
                    wx.CallAfter(self.RestartLog)

    # --- Send Mail ------------------------------------------------------------

    def OnSendMail(self, event):
        if self.lastData is None:
            self.flash_status_message('There is no data!')
            return
        self.send_mail('Test')

    # Python mail:  		https://stackoverflow.com/questions/43666912/send-email-using-python-using-gmail
    # Multiple recipients:  https://stackoverflow.com/questions/8856117/how-to-send-email-to-multiple-recipients-using-python-smtplib
    def send_mail(self, msg):
        if not SEND_MAIL or not self.m_checkBox_enable_alerts.GetValue():
            print 'mail not sent:', msg
            return

        try:
            print '\tSending mail . . .'

            gmailUser = '******'
            gmailPassword = '******'
            recipients = [
                '*****@*****.**', '*****@*****.**', '*****@*****.**',
                '*****@*****.**', '*****@*****.**'
            ]
            subject = 'Datalogger Self Generated - ' + msg
            message = 'Last data: ' + self.lastDataStr

            msg = MIMEMultipart()
            msg['From'] = gmailUser
            msg['To'] = ", ".join(recipients)  #recipient
            msg['Subject'] = subject
            msg.attach(MIMEText(message))

            print '\tFrom:', gmailUser
            print '\tTo:', recipients
            print '\tSubject:', subject
            print '\tMessage:', message

            mailServer = smtplib.SMTP('smtp.gmail.com', 587)
            mailServer.ehlo()
            mailServer.starttls()
            mailServer.ehlo()
            mailServer.login(gmailUser, gmailPassword)
            mailServer.sendmail(gmailUser, recipients, msg.as_string())
            mailServer.close()

            self.flash_status_message('Alarm mail sent !', n=0, clear=False)

        except Exception as e:
            print e
            self.flash_status_message('Problem sending mail !',
                                      n=0,
                                      clear=False)

    # --- Hardware Control -----------------------------------------------------

    def PrintStatus(self, event):
        if self.device is None:
            self.flash_status_message('Device not connected')
            return

        print 'Status:'
        print self.device.status()

    def hardware_action_1(self, event):
        pass

    def hardware_action_2(self, event):
        pass

    def hardware_action_3(self, event):
        pass

    def set_hardware_param_1(self, event):
        param = self.m_textCtrl_hardware_param_1.Value

    def set_hardware_param_2(self, event):
        param = self.m_textCtrl_hardware_param_2.Value
Beispiel #16
0
class PlotFrame(wx.Frame):
    """
    Class used for creating frames other than the main one

    Matplotlib
    Object-Oriented API vs Pyplot
        Matplotlib has two interfaces.
            [1] The first is an object-oriented (OO) interface. In this case, we utilize an nstance of axes.Axes in
                order to render visualizations on an instance of figure.Figure.

            [2] The second is based on MATLAB and uses a state-based interface. This is encapsulated in the pyplot
                module.
    """
    def __init__(self,
                 title='Matplotlib Output',
                 id=-1,
                 dpi=100,
                 parent=None,
                 **kwds):
        kwds["style"] = kwds.get("style",
                                 0) | wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER
        wx.Frame.__init__(self, parent=parent, title=title, **kwds)

        self.SetSize((843, 554))
        self.panel_2 = wx.Panel(self, wx.ID_ANY)

        # self.figure = mpl.figure.Figure(dpi=dpi, figsize=(2, 2))  # look into Figure((5, 4), 75)
        self.figure = plt.figure(figsize=(2,
                                          2))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.panel_2, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.choices = []
        self.button_9 = wx.Button(self.panel_2, wx.ID_ANY, "Edit Data")
        self.radio_box_1 = wx.RadioBox(self.panel_2,
                                       wx.ID_ANY,
                                       "",
                                       choices=["2D", "3D"],
                                       majorDimension=1,
                                       style=wx.RA_SPECIFY_ROWS)

        self.choice_dropdown = [wx.Choice(), CheckListCtrl, wx.Choice()]
        self.choice_dropdown[0] = wx.Choice(self.panel_2,
                                            wx.ID_ANY,
                                            choices=self.choices)
        self.choice_dropdown[1] = CheckListCtrl(
            self.panel_2, prefHeight=None,
            readOnly=True)  # style=wx.TE_READONLY
        self.choice_dropdown[2] = wx.Choice(self.panel_2,
                                            wx.ID_ANY,
                                            choices=self.choices)

        self.text_ctrl_17 = wx.TextCtrl(self.panel_2,
                                        wx.ID_ANY,
                                        "",
                                        style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_axis_labels = [wx.TextCtrl()] * 4
        for idx in range(4):
            self.text_ctrl_axis_labels[idx] = wx.TextCtrl(
                self.panel_2, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)

        # # Menu Bare
        # self.frame_1_menubar = wx.MenuBar()
        # self.SetMenuBar(self.frame_1_menubar)
        # # Menu Bar end

        self.x = np.array(0.)
        self.y = np.array(0.)
        self.z = np.array(0.)
        self.ax = None

        # Event Triggers -----------------------------------------------------------------------------------------------
        onPlotMode_event = lambda event: self.onPlotMode(event)
        self.Bind(wx.EVT_RADIOBOX, onPlotMode_event, self.radio_box_1)

        update_xAxisData_event = lambda event: self.update_xAxisData(event)
        self.Bind(wx.EVT_CHOICE, update_xAxisData_event,
                  self.choice_dropdown[0])

        update_yAxisData_event = lambda event: self.update_yAxisData(event)
        self.Bind(wx.EVT_CHECKBOX, update_yAxisData_event,
                  self.choice_dropdown[1])

        update_zAxisData_event = lambda event: self.update_zAxisData(event)
        self.Bind(wx.EVT_CHOICE, update_zAxisData_event,
                  self.choice_dropdown[2])

        # Update plot title
        onUpdateTitle_event = lambda event: self.onUpdateTitle(event)
        self.Bind(wx.EVT_TEXT_ENTER, onUpdateTitle_event,
                  self.text_ctrl_axis_labels[0])

        # Update plot title
        onUpdateXLabel_event = lambda event: self.onUpdateXLabel(event)
        self.Bind(wx.EVT_TEXT_ENTER, onUpdateXLabel_event,
                  self.text_ctrl_axis_labels[1])

        # Update plot title
        onUpdateYLabel_event = lambda event: self.onUpdateYLabel(event)
        self.Bind(wx.EVT_TEXT_ENTER, onUpdateYLabel_event,
                  self.text_ctrl_axis_labels[2])

        # Update plot title
        onUpdateZLabel_event = lambda event: self.onUpdateZLabel(event)
        self.Bind(wx.EVT_TEXT_ENTER, onUpdateZLabel_event,
                  self.text_ctrl_axis_labels[3])

        # Update plot title
        onParseYexpression_event = lambda event: self.onParseY_expression(event
                                                                          )
        self.Bind(wx.EVT_TEXT_ENTER, onParseYexpression_event,
                  self.text_ctrl_17)

        self.__set_properties()
        self.__do_layout()
        self.Show()
        # end wxGlade

    def __set_properties(self):
        # begin wxGlade: MyFrame1.__set_properties
        self.SetTitle("Plot Data")
        self.SetFocus()

        for idx in range(3):
            self.choice_dropdown[idx].SetMinSize((80, 23))

        for idx in range(4):
            self.text_ctrl_axis_labels[idx].SetMinSize((200, 23))

        self.radio_box_1.SetSelection(0)
        self.choice_dropdown[0].SetSelection(0)
        self.choice_dropdown[1].ClearSelections()
        self.choice_dropdown[2].SetSelection(0)

        self.choice_dropdown[2].Enable(False)
        self.text_ctrl_axis_labels[3].Enable(False)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame1.__do_layout
        sizer_3 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_2 = wx.GridBagSizer(0, 0)

        grid_sizer_2.Add(self.canvas, (2, 6), (13, 1), wx.ALL | wx.EXPAND, 10)
        grid_sizer_2.Add(self.toolbar, (15, 6), (1, 1), wx.ALL | wx.EXPAND, 10)
        # update the axes menu on the toolbar
        # self.toolbar.update()

        label_16 = wx.StaticText(self.panel_2, wx.ID_ANY, "Configure Plot")
        label_16.SetFont(
            wx.Font(20, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_ITALIC,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_2.Add(label_16, (0, 0), (1, 4),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        bitmap_1 = wx.StaticBitmap(
            self.panel_2, wx.ID_ANY,
            wx.Bitmap("images/Fluke Logo.png", wx.BITMAP_TYPE_ANY))
        grid_sizer_2.Add(bitmap_1, (0, 6), (1, 1),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL,
                         10)
        static_line_2 = wx.StaticLine(self.panel_2, wx.ID_ANY)
        static_line_2.SetMinSize((800, 2))
        grid_sizer_2.Add(static_line_2, (1, 0), (1, 7),
                         wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, 10)
        label_17 = wx.StaticText(self.panel_2, wx.ID_ANY, "Data")
        label_17.SetFont(
            wx.Font(12, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_ITALIC,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_2.Add(label_17, (2, 0), (1, 2), wx.LEFT | wx.RIGHT, 10)
        grid_sizer_2.Add(self.button_9, (3, 1), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        grid_sizer_2.Add(self.radio_box_1, (3, 3), (1, 1), wx.ALL, 10)
        label_19 = wx.StaticText(self.panel_2, wx.ID_ANY, "X:")
        grid_sizer_2.Add(label_19, (4, 0), (1, 1),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_2.Add(self.choice_dropdown[0], (4, 1), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        label_20 = wx.StaticText(self.panel_2, wx.ID_ANY, "Y:")
        grid_sizer_2.Add(label_20, (5, 0), (1, 1), wx.ALL, 10)
        grid_sizer_2.Add(self.choice_dropdown[1], (5, 1), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        grid_sizer_2.Add(self.text_ctrl_17, (5, 3), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        label_21 = wx.StaticText(self.panel_2, wx.ID_ANY, "Z:")
        grid_sizer_2.Add(label_21, (6, 0), (1, 1), wx.ALL, 10)
        grid_sizer_2.Add(self.choice_dropdown[2], (6, 1), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        static_line_3 = wx.StaticLine(self.panel_2, wx.ID_ANY)
        static_line_3.SetMinSize((250, 2))
        grid_sizer_2.Add(
            static_line_3, (7, 0), (1, 6),
            wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT | wx.TOP, 10)
        label_18 = wx.StaticText(self.panel_2, wx.ID_ANY, "Properties")
        label_18.SetFont(
            wx.Font(12, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_ITALIC,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_2.Add(label_18, (8, 0), (1, 3), wx.LEFT | wx.RIGHT, 10)
        label_22 = wx.StaticText(self.panel_2, wx.ID_ANY, "Title:")
        grid_sizer_2.Add(label_22, (9, 0), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_2.Add(self.text_ctrl_axis_labels[0], (9, 2), (1, 3),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        label_23 = wx.StaticText(self.panel_2, wx.ID_ANY, "X Label:")
        grid_sizer_2.Add(label_23, (10, 0), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_2.Add(self.text_ctrl_axis_labels[1], (10, 2), (1, 3),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        label_24 = wx.StaticText(self.panel_2, wx.ID_ANY, "Y Label:")
        grid_sizer_2.Add(label_24, (11, 0), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_2.Add(self.text_ctrl_axis_labels[2], (11, 2), (1, 3),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        label_25 = wx.StaticText(self.panel_2, wx.ID_ANY, "Z Label:")
        grid_sizer_2.Add(label_25, (12, 0), (1, 2),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_2.Add(self.text_ctrl_axis_labels[3], (12, 2), (1, 3),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        self.panel_2.SetSizer(grid_sizer_2)
        sizer_3.Add(self.panel_2, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_3)
        self.Layout()
        self.Centre()
        # end wxGlade

    def get_data(self, list_data):
        if isinstance(list_data, (list, np.ndarray)):
            self.data = np.asarray(list_data).astype(np.float)
            self.number_of_columns = self.data.shape[1]
            self.choices = [''] * self.number_of_columns
        else:
            self.data = np.array([[1, 2, 3], [2, 1, 4]])
            self.number_of_columns = self.data.shape[1]
        alphaidx = list(
            itertools.islice(increment_column_index(), self.number_of_columns))
        for m in range(self.number_of_columns):
            self.choices[m] = f'Col:{alphaidx[m]}'
        for idx in range(3):
            self.choice_dropdown[idx].SetItems(self.choices)

        self.choice_dropdown[0].SetSelection(0)
        self.choice_dropdown[1].SetSelection(1)

        self.x = 0.
        self.y = [0.]
        if self.number_of_columns >= 2:
            self.x = self.data[:, 0]
            self.y[0] = self.data[:, 1]
        else:
            self.x, self.y[0] = [1, 2, 3], [2, 1, 4]
        self.Draw_2DPlot()

    def Draw_2DPlot(self):

        # TODO - https://stackoverflow.com/questions/594266/equation-parsing-in-python

        self.ax = self.figure.add_subplot(111)

        for i, y in enumerate(self.y):
            # plot returns a list of artists of which you want the first element when changing x or y data
            self.plot = self.ax.plot(self.x, y)
        self.ax.set_title('2D Plot Title', fontsize=15, fontweight="bold")
        self.ax.set_ylabel(self.choices[0], fontsize=8)
        self.ax.set_xlabel(self.choices[1], fontsize=8)

        self.UpdateAxisLabels()
        self.figure.tight_layout()
        self.toolbar.update()  # Not sure why this is needed - ADS

    def Draw_3DPlot(self):
        """
        Use surf to plot modelised data, and you use trisurf to plot experimental data. (because your
        experimental data are not necessarily distributed on a nice regular grids). In general you use surf to plot
        modelised data, and you use trisurfto plot experimental data. (because your experimental data are not
        necessarily distributed on a nice regular grids)

        surf takes as input rectilinear grids and plots surfaces as rectilinear facets, while trisurf takes as input
        triangular representations and plots surfaces as triangular facets.

        first line determines if axes object is a 3D projection and if so, skip re-declaration:
            >   if not hasattr(self.ax, 'get_zlim'): do_something()
        https://stackoverflow.com/a/39334099

        Useful info on animated (or updating) 3D surfaces: https://stackoverflow.com/a/45713451
        There is a lot going on under the surface when calling plot_surface. You would need to replicate all of it when
        trying to set new data to the Poly3DCollection
        """
        self.ax = self.figure.add_subplot(111, projection='3d')

        triang = mtri.Triangulation(self.x, self.y[0])
        self.plot = self.ax.plot_trisurf(triang, self.z,
                                         cmap=cm.CMRmap)  # cmap=cm.viridis
        self.ax.zaxis.set_major_locator(LinearLocator(10))
        self.ax.zaxis.set_major_formatter(FormatStrFormatter('%.2f'))
        self.ax.set_zlim(round(min(self.z), 2), round(max(self.z), 2))
        self.ax.view_init(elev=15, azim=220)
        self.ax.view_init(elev=15, azim=220)

        # self.figure.colorbar(self.plot, orientation='horizontal', pad=-0.05, shrink=0.5, aspect=20)  # TODO

        self.UpdateAxisLabels()
        self.figure.tight_layout()
        self.toolbar.update()  # Not sure why this is needed - ADS

    def update_xAxisData(self, event):
        """
        There are two options for updating a plot in matplotlib:
            [1] Call self.ax.clear() and graph2.clear() before replotting the data. This is the slowest, but most
                simplest and most robust option.
                    >   The sequence for each new frame should be clear, plot, draw

            [2] Instead of replotting every new update, update the data of the plot objects, which is a much faster
                approach. However, two things must be noted:
                    >   The shape of the data cannot change
                    >   If the range of the data is changing, x and y axis limits must manually be reset. Use:
                            self.ax.relim()
                            self.ax.autoscale_view()
        In addition, ensure self.canvas.draw() is called after plotting each update to the frame
        Source: https://stackoverflow.com/a/4098938

        :param event: event waits for change in choice dropdown
        """
        column = self.choice_dropdown[0].GetSelection()
        self.x = self.data[:, column]
        if self.radio_box_1.GetStringSelection() == '2D':
            self.plot[0].set_xdata(self.x)

            self.UpdateAxisLabels()
            self.ax.relim()
            self.ax.autoscale_view()
        else:
            self.figure.delaxes(self.ax)
            self.Draw_3DPlot()

        self.canvas.draw()
        self.canvas.flush_events()

    def update_yAxisData(self, event):
        selections = self.choice_dropdown[1].GetSelection()
        self.y = [0.] * len(selections)
        for i, column in enumerate(selections):
            self.y[i] = self.data[:, column]
        if self.radio_box_1.GetStringSelection() == '2D':
            self.ax.clear()
            for i, y in enumerate(self.y):
                self.plot = self.ax.plot(self.x, y)

            self.UpdateAxisLabels()
            self.ax.relim()
            self.ax.autoscale_view()
        else:
            self.figure.delaxes(self.ax)
            self.Draw_3DPlot()

        self.canvas.draw()
        self.canvas.flush_events()

    def update_zAxisData(self, event):
        """
        Useful info on animated (or updating) 3D surfaces: https://stackoverflow.com/a/45713451
        There is a lot going on under the surface when calling plot_surface. You would need to replicate all of it when
        trying to set new data to the Poly3DCollection.
        """
        column = self.choice_dropdown[2].GetSelection()
        self.z = self.data[:, column]
        if self.radio_box_1.GetStringSelection() == '3D':
            self.figure.delaxes(self.ax)
            self.Draw_3DPlot()
        else:
            self.plot.set_3d_properties(self.z)

        self.canvas.draw()
        self.canvas.flush_events()

    def UpdateAxisLabels(self):
        """
        [Text Hints]:
            There are two approaches to including text hints inside text control objects.
                [1] Use the method SetHint found in the wx.TextEntry class. This class is not derive from wx.Window and
                    so is not a control itself, however, it is used as a base class by other controls,
                    notably wx.TextCtrl and Wx.ComboBox and gathers the methods common to both of them.
                [2] Write "hint" as actual content inside the textCtrl control object (with grey color). When the user
                    clicks into the control (control gets focus), check if the control still contains the placeholder
                    text.
                    If true, you clear the text. If the focus leaves the control, check if it is empty. And if True,
                    put the placeholder text back in.

            Source: https://forums.wxwidgets.org/viewtopic.php?t=39368
        """
        label = [''] * 4
        hint = [''] * 4
        selectedMode = self.radio_box_1.GetStringSelection()

        for idx in range(4):
            label_text = self.text_ctrl_axis_labels[idx].GetValue()
            if idx == 0:
                if label_text == '':
                    self.text_ctrl_axis_labels[idx].SetHint(
                        f'{selectedMode} Plot Title')
                    label[0] = f'{selectedMode} Plot Title'
                else:
                    label[0] = self.text_ctrl_axis_labels[0].GetValue()
            else:
                if label_text == '':
                    label[idx] = self.choice_dropdown[idx -
                                                      1].GetStringSelection()
                else:
                    label[idx] = self.text_ctrl_axis_labels[idx].GetValue()
                self.text_ctrl_axis_labels[idx].SetHint(
                    self.choice_dropdown[idx - 1].GetStringSelection())

        if selectedMode == '2D':
            self.ax.set_title(label[0], fontsize=15, fontweight="bold")
            self.ax.set_xlabel(label[1], fontsize=8)
            self.ax.set_ylabel(label[2], fontsize=8)

        elif selectedMode == '3D':
            self.ax.set_title(label[0], fontsize=15, fontweight="bold")
            self.ax.set_xlabel(label[1], fontsize=8)
            self.ax.set_ylabel(label[2], fontsize=8)
            self.ax.set_zlabel(label[3], fontsize=8)

    def onUpdateTitle(self, event):
        """
        TODO - This is a terrible fix for something that should be so simple. Updating title otherwise incrementally
               changes title position offset
               > Requires removal of the previous plot and then to be redrawn. Silly!
        """

        selectedMode = self.radio_box_1.GetStringSelection()
        if selectedMode == '2D':
            self.ax.set_title(self.text_ctrl_axis_labels[0].GetValue(),
                              fontsize=15,
                              fontweight="bold")
        elif selectedMode == '3D':
            self.figure.delaxes(self.ax)
            self.Draw_3DPlot()

        # make the canvas draw its contents again with the new data
        self.canvas.draw()

    def onUpdateXLabel(self, event):
        self.ax.set_xlabel(self.text_ctrl_axis_labels[1].GetValue())
        # make the canvas draw its contents again with the new data
        self.canvas.draw()

    def onUpdateYLabel(self, event):
        self.ax.set_ylabel(self.text_ctrl_axis_labels[2].GetValue())
        # make the canvas draw its contents again with the new data
        self.canvas.draw()

    def onUpdateZLabel(self, event):
        self.ax.set_zlabel(self.text_ctrl_axis_labels[3].GetValue())
        # make the canvas draw its contents again with the new data
        self.canvas.draw()

    def onPlotMode(self, event):
        selectedMode = self.radio_box_1.GetStringSelection()
        if selectedMode == '3D':
            if self.number_of_columns >= 3:
                self.choice_dropdown[1].Spotlight_on(
                )  # forces only one box be checked. All others are red
                self.choice_dropdown[1].ClearSelections()
                self.choice_dropdown[1].SetSelection(1)
                self.choice_dropdown[2].Enable(True)
                self.text_ctrl_axis_labels[3].Enable(True)

                self.z = self.data[:, 2]
                self.choice_dropdown[2].SetSelection(2)
                self.figure.delaxes(self.ax)
                self.Draw_3DPlot()
                self.canvas.draw()
            else:
                print(
                    'Insufficient columns of data for 3D plot!\nReverting mode back to 2D'
                )
                self.radio_box_1.SetSelection(0)
        else:
            self.choice_dropdown[1].Spotlight_off()
            self.choice_dropdown[1].ClearSelections()
            self.choice_dropdown[1].SetSelection(1)
            self.choice_dropdown[2].Enable(False)
            self.text_ctrl_axis_labels[3].Enable(False)

            self.figure.delaxes(self.ax)
            self.Draw_2DPlot()
            self.canvas.draw()

    def onParseY_expression(self, event):
        """
        Equation parsing in Python ---> https://stackoverflow.com/a/5936822
            It will still happily execute all code, not just formulas. Including bad things like os.system calls.
            eval(parser.expr("os.system('echo evil syscall')").compile()) evil syscall

        temporary variables (x, y, newY) are necessary to avoid the reference explosion: self.y[idx] = self.x

        With new_list = my_list, you don't actually have two lists. The assignment just copies the reference to the
        list, not the actual list, so both new_list and my_list refer to the same list after the assignment.

        How to clone or copy a list? ---> https://stackoverflow.com/a/2612815

        :param event:
        :return:
        """
        input_string = self.text_ctrl_17.GetValue()

        nsp = eqn_parser.NumericStringParser()
        nsp.expr(input_string)
        newY = [0.] * len(self.y)
        for idx in range(len(self.y)):
            newY[idx] = nsp.eval(variables={'x': self.x, 'y': self.y[idx]})

        # code = parser.expr(input_string).compile()
        # newY = [0.] * len(self.y)
        # for idx in range(len(self.y)):
        # x = self.x
        # y = self.y[idx]
        # newY[idx] = eval(code)

        if self.radio_box_1.GetStringSelection() == '2D':
            self.ax.clear()
            for i, y in enumerate(newY):
                self.plot = self.ax.plot(self.x, y)
            self.UpdateAxisLabels(
            )  # TODO - silly this has to be broken up. See note in method onUpdateTitle
        else:
            # self.update_3dAxisData()
            self.figure.delaxes(self.ax)
            self.ax = self.figure.add_subplot(111, projection='3d')

            triang = mtri.Triangulation(self.x, newY[0])
            self.plot = self.ax.plot_trisurf(triang, self.z,
                                             cmap=cm.CMRmap)  # cmap=cm.viridis
            self.ax.zaxis.set_major_locator(LinearLocator(10))
            self.ax.zaxis.set_major_formatter(FormatStrFormatter('%.2f'))
            self.ax.set_zlim(round(min(self.z), 2), round(max(self.z), 2))
            self.ax.view_init(elev=15, azim=220)
            self.ax.view_init(elev=15, azim=220)

            # self.figure.colorbar(self.plot, orientation='horizontal', pad=-0.05, shrink=0.5, aspect=20)  # TODO

            self.UpdateAxisLabels()
            self.figure.tight_layout()
            self.toolbar.update()  # Not sure why this is needed - ADS

        # self.UpdateAxisLabels()
        self.ax.relim()
        self.ax.autoscale_view()
        self.canvas.draw()
        self.canvas.flush_events()
Beispiel #17
0
class HistoryTab(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.parent = parent
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1,
                                          1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.ax1 = self.figure.add_subplot(211)
        self.ax2 = self.figure.add_subplot(212)

        self.temporal, = self.ax1.plot([], [], linestyle='-')
        self.spectral, = self.ax2.plot([], [], color='#C02942')

        # Open history dialog ------------------------------------------------------------------------------------------
        self.btn_openHistory = wx.Button(self, wx.ID_ANY, "Open History")
        self.Bind(wx.EVT_BUTTON, self.OnOpen, self.btn_openHistory)

        self.text_ctrl_fs = wx.TextCtrl(self, wx.ID_ANY, "")
        self.text_ctrl_samples = wx.TextCtrl(self, wx.ID_ANY, "")
        self.text_ctrl_rms = wx.TextCtrl(self, wx.ID_ANY, "")
        self.text_ctrl_thdn = wx.TextCtrl(self, wx.ID_ANY, "")
        self.text_ctrl_thd = wx.TextCtrl(self, wx.ID_ANY, "")

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(240, 240, 240))
        self.canvas.SetMinSize((700, 490))

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)
        grid_sizer_report = wx.GridBagSizer(0, 0)

        # LEFT PANEL ---------------------------------------------------------------------------------------------------
        lbl_fs = wx.StaticText(self, wx.ID_ANY, "Fs:")
        lbl_samples = wx.StaticText(self, wx.ID_ANY, "Samples:")
        lbl_rms = wx.StaticText(self, wx.ID_ANY, "RMS:")
        lbl_thdn = wx.StaticText(self, wx.ID_ANY, "THD+N:")
        lbl_THD = wx.StaticText(self, wx.ID_ANY, "THD:")

        static_line_1 = wx.StaticLine(self, wx.ID_ANY)
        static_line_1.SetMinSize((180, 2))
        static_line_2 = wx.StaticLine(self, wx.ID_ANY)
        static_line_2.SetMinSize((180, 2))

        grid_sizer_report.Add(self.btn_openHistory, (0, 0), (1, 2),
                              wx.LEFT | wx.TOP, 5)
        grid_sizer_report.Add(static_line_2, (1, 0), (1, 2), wx.ALL, 5)
        grid_sizer_report.Add(lbl_fs, (2, 0), (1, 1), wx.LEFT | wx.RIGHT, 5)
        grid_sizer_report.Add(self.text_ctrl_fs, (2, 1), (1, 1), wx.BOTTOM, 5)
        grid_sizer_report.Add(lbl_samples, (3, 0), (1, 1), wx.LEFT | wx.RIGHT,
                              5)
        grid_sizer_report.Add(self.text_ctrl_samples, (3, 1), (1, 1),
                              wx.BOTTOM, 5)
        grid_sizer_report.Add(static_line_1, (4, 0), (1, 2), wx.ALL, 5)
        grid_sizer_report.Add(lbl_rms, (5, 0), (1, 1), wx.LEFT | wx.RIGHT, 5)
        grid_sizer_report.Add(self.text_ctrl_rms, (5, 1), (1, 1), wx.BOTTOM, 5)
        grid_sizer_report.Add(lbl_thdn, (6, 0), (1, 1), wx.LEFT | wx.RIGHT, 5)
        grid_sizer_report.Add(self.text_ctrl_thdn, (6, 1), (1, 1), wx.BOTTOM,
                              5)
        grid_sizer_report.Add(lbl_THD, (7, 0), (1, 1), wx.LEFT | wx.RIGHT, 5)
        grid_sizer_report.Add(self.text_ctrl_thd, (7, 1), (1, 1), 0, 0)
        grid_sizer_1.Add(grid_sizer_report, 1, wx.EXPAND, 0)

        # PLOT PANEL ---------------------------------------------------------------------------------------------------
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)

        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    def error_dialog(self, error_message):
        print(error_message)
        dial = wx.MessageDialog(None, str(error_message), 'Error',
                                wx.OK | wx.ICON_ERROR)
        dial.ShowModal()

    def OnOpen(self, event):
        Path("results/history").mkdir(parents=True, exist_ok=True)
        with wx.FileDialog(self,
                           "Open previous measurement:",
                           wildcard="CSV files (*.csv)|*.csv",
                           defaultDir="results/history",
                           style=wx.FD_OPEN
                           | wx.FD_FILE_MUST_EXIST) as fileDialog:

            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind

            # Proceed loading the file chosen by the user
            pathname = fileDialog.GetPath()
            try:
                with open(pathname, 'r') as file:
                    self.open_history(file)
            except (IOError, ValueError) as e:
                wx.LogError("Cannot open file '%s'." % pathname)
                self.error_dialog(e)

    def open_history(self, file):
        df = pd.read_csv(file)

        try:
            xt = df['xt'].to_numpy()
            yt = df['yt'].to_numpy()
            xf = df['xf'].to_numpy()

            # https://stackoverflow.com/a/18919965/3382269
            # https://stackoverflow.com/a/51725795/3382269
            df['yf'] = df['yf'].str.replace('i',
                                            'j').apply(lambda x: np.complex(x))
            yf = df['yf'].to_numpy()
        except KeyError:
            raise ValueError(
                'Incorrect file attempted to be opened. '
                '\nCheck data headers. xt, yt, xf, yf should be present')

        self.process_raw_input(xt, yt, xf, yf)

    def process_raw_input(self, xt, yt, xf, yf):
        yrms = np.sqrt(np.mean(np.abs(yt)**2))
        N = len(xt)
        Fs = round(1 / (xt[1] - xt[0]), 2)

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        if (N % 2) == 0:
            # for even values of N: length is (N / 2) + 1
            fft_length = int(N / 2) + 1
        else:
            # for odd values of N: length is (N + 1) / 2
            fft_length = int((N + 2) / 2)

        xf_rfft, yf_rfft = xf[:fft_length], yf[:fft_length]

        thdn, *_ = THDN_F(yf_rfft, Fs, N)
        thd = THD(yf_rfft, Fs)

        self.plot(xt, yt, fft_length, xf_rfft, yf_rfft)
        self.results_update(Fs, N, yrms, thdn, thd)

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ms)')
        self.ax1.set_ylabel('AMPLITUDE')
        self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE')
        self.ax2.set_xlabel('FREQUENCY (kHz)')
        self.ax2.set_ylabel('MAGNITUDE (dB)')
        self.ax2.grid()
        self.figure.align_ylabels([self.ax1, self.ax2])
        self.figure.tight_layout()

    def plot(self, xt, yt, fft_length, xf, yf):
        # TEMPORAL -----------------------------------------------------------------------------------------------------
        self.temporal.set_data(xt, yt)

        try:
            self.ax1.relim()  # recompute the ax.dataLim
        except ValueError:
            print(
                f'Are the lengths of xt: {len(xt)} and yt: {len(yt)} mismatched?'
            )
            raise
        self.ax1.margins(x=0)
        self.ax1.autoscale()

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        yf_peak = max(abs(yf))
        self.spectral.set_data(xf / 1000, 20 * np.log10(np.abs(yf / yf_peak)))
        try:
            self.ax2.relim()  # recompute the ax.dataLim
        except ValueError:
            print(
                f'Are the lengths of xt: {len(xf)} and yt: {len(yf)} mismatched?'
            )
            raise
        self.ax2.autoscale()

        xf_left = 0
        xf_right = xf[fft_length - 1]
        self.ax2.set_xlim(left=xf_left / 1000, right=xf_right / 1000)

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()

        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def results_update(self, Fs, N, yrms, thdn, thd):
        self.text_ctrl_fs.SetLabelText(str(Fs))
        self.text_ctrl_samples.SetLabelText(str(N))
        self.text_ctrl_rms.SetValue(f"{'{:0.3e}'.format(yrms)}")
        self.text_ctrl_thdn.SetValue(
            f"{round(thdn * 100, 3)}% or {round(np.log10(thdn), 1)}dB")
        self.text_ctrl_thd.SetValue(
            f"{round(thd * 100, 3)}% or {round(np.log10(thd), 1)}dB")
Beispiel #18
0
class TestFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: TestFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((1234, 669))
        self.panel_frame = wx.Panel(self, wx.ID_ANY)

        self.thread = threading.Thread(target=self.run, args=())
        self.thread.daemon = True
        self.row = 0
        self.prevLine = ''
        self.line = ''
        self.table = {}
        self.overlay = {}
        self.ax = None
        self.x, self.y = [0.], [[0.]]
        self.flag_complete = False

        self.panel_main = wx.Panel(self.panel_frame, wx.ID_ANY)
        self.notebook = wx.Notebook(self.panel_main, wx.ID_ANY)
        self.notebook_program = wx.Panel(self.notebook, wx.ID_ANY)
        self.panel_3 = wx.Panel(self.notebook_program, wx.ID_ANY)
        self.text_ctrl_log = wx.TextCtrl(self.panel_3,
                                         wx.ID_ANY,
                                         "",
                                         style=wx.TE_MULTILINE
                                         | wx.TE_READONLY)
        self.text_ctrl_log_entry = wx.TextCtrl(self.panel_3,
                                               wx.ID_ANY,
                                               "",
                                               style=wx.TE_PROCESS_ENTER)
        sys.stdout = self.text_ctrl_log
        self.button_1 = wx.Button(self.panel_3, wx.ID_ANY, "Enter")
        self.notebook_1 = wx.Notebook(self.notebook_program, wx.ID_ANY)
        self.notebook_1_pane_2 = wx.Panel(self.notebook_1, wx.ID_ANY)

        self.figure = plt.figure(figsize=(2,
                                          2))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.notebook_1_pane_2, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        self.notebook_1_Settings = wx.Panel(self.notebook_1, wx.ID_ANY)
        # Use PropertyGridManger instead, if pages are desired
        self.property_grid_1 = wx.propgrid.PropertyGrid(
            self.notebook_1_Settings, wx.ID_ANY)

        self.notebook_Spreadsheet = wx.Panel(self.notebook, wx.ID_ANY)
        self.grid_1 = MyGrid(self.notebook_Spreadsheet)
        self.btn_run = wx.Button(self.panel_main, wx.ID_ANY, "Run Test")
        # TODO - Pause: https://stackoverflow.com/a/34313474/3382269
        self.btn_pause = wx.Button(self.panel_main, wx.ID_ANY, "Pause")
        self.btn_stop = wx.Button(self.panel_main, wx.ID_ANY, "Stop")
        self.btn_save = wx.Button(self.panel_main, wx.ID_ANY, "Save")

        # Run Measurement (start subprocess)
        on_run_event = lambda event: self.on_run(event)
        self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_run)

        self.Bind(wxpg.EVT_PG_CHANGED, self.OnGridChangeEvent)

        self.__set_properties()
        self.__do_layout()

    def __set_properties(self):
        # begin wxGlade: TestFrame.__set_properties
        self.SetTitle("Test Application")
        self.text_ctrl_log.SetMinSize((580, 410))
        self.text_ctrl_log_entry.SetMinSize((483, 23))
        self.canvas.SetMinSize((585, 406))
        self.grid_1.CreateGrid(31, 16)
        self.grid_1.SetDefaultColSize(150)
        self.notebook.SetMinSize((1200, 500))
        self._create_plot_properties()

    def __do_layout(self):
        # begin wxGlade: TestFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_1 = wx.GridBagSizer(0, 0)
        sizer_4 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_2 = wx.GridSizer(1, 2, 0, 0)
        sizer_5 = wx.BoxSizer(wx.VERTICAL)
        sizer_3 = wx.BoxSizer(wx.VERTICAL)
        grid_sizer_4 = wx.GridBagSizer(0, 0)
        grid_sizer_3 = wx.GridBagSizer(0, 0)
        label_1 = wx.StaticText(self.panel_main, wx.ID_ANY, "Test Application")
        label_1.SetFont(
            wx.Font(20, wx.FONTFAMILY_DECORATIVE, wx.FONTSTYLE_ITALIC,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_1.Add(label_1, (0, 0), (1, 3), wx.ALIGN_CENTER_VERTICAL, 0)
        # bitmap_1 = wx.StaticBitmap(self.panel_main, wx.ID_ANY, wx.Bitmap("Fluke Logo.png", wx.BITMAP_TYPE_ANY))
        # grid_sizer_1.Add(bitmap_1, (0, 3), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL, 0)
        static_line_1 = wx.StaticLine(self.panel_main, wx.ID_ANY)
        static_line_1.SetMinSize((1200, 2))
        grid_sizer_1.Add(static_line_1, (1, 0), (1, 4),
                         wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.TOP, 5)
        grid_sizer_3.Add(self.text_ctrl_log, (0, 0), (1, 2),
                         wx.LEFT | wx.RIGHT | wx.TOP, 10)
        grid_sizer_3.Add(self.text_ctrl_log_entry, (1, 0), (1, 1),
                         wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
        grid_sizer_3.Add(self.button_1, (1, 1), (1, 1),
                         wx.ALIGN_CENTER_VERTICAL, 0)
        self.panel_3.SetSizer(grid_sizer_3)
        grid_sizer_2.Add(self.panel_3, 1, wx.EXPAND, 0)

        grid_sizer_4.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_4.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)

        sizer_3.Add(grid_sizer_4, 1, wx.EXPAND, 0)
        self.notebook_1_pane_2.SetSizer(sizer_3)
        sizer_5.Add(self.property_grid_1, 1, wx.EXPAND, 0)
        self.notebook_1_Settings.SetSizer(sizer_5)
        self.notebook_1.AddPage(self.notebook_1_pane_2, "Plot")
        self.notebook_1.AddPage(self.notebook_1_Settings, "Settings")
        grid_sizer_2.Add(self.notebook_1, 1, wx.EXPAND, 0)

        self.notebook_program.SetSizer(grid_sizer_2)
        sizer_4.Add(self.grid_1, 1, wx.EXPAND, 0)
        self.notebook_Spreadsheet.SetSizer(sizer_4)
        self.notebook.AddPage(self.notebook_program, "Program")
        self.notebook.AddPage(self.notebook_Spreadsheet, "Spreadsheet")
        grid_sizer_1.Add(self.notebook, (2, 0), (1, 4), wx.EXPAND, 0)
        static_line_2 = wx.StaticLine(self.panel_main, wx.ID_ANY)
        static_line_2.SetMinSize((1200, 2))
        grid_sizer_1.Add(static_line_2, (3, 0), (1, 4),
                         wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.TOP, 5)
        grid_sizer_1.Add(self.btn_run, (4, 0), (1, 1),
                         wx.ALIGN_RIGHT | wx.RIGHT, 20)
        grid_sizer_1.Add(self.btn_pause, (4, 1), (1, 1),
                         wx.ALIGN_RIGHT | wx.RIGHT, 20)
        grid_sizer_1.Add(self.btn_stop, (4, 2), (1, 1),
                         wx.ALIGN_RIGHT | wx.RIGHT, 10)
        grid_sizer_1.Add(self.btn_save, (4, 3), (1, 1), wx.ALIGN_RIGHT, 0)
        self.panel_main.SetSizer(grid_sizer_1)
        sizer_2.Add(self.panel_main, 1, wx.ALL | wx.EXPAND, 5)
        self.panel_frame.SetSizer(sizer_2)
        sizer_1.Add(self.panel_frame, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)
        self.Layout()

    def _create_plot_properties(self):
        pg = self.property_grid_1
        # pg.AddPage("Page 1 - Plot Settings")  # if pages are needed, use PropertyGridManger instead
        dir_path = os.path.dirname(os.path.realpath(__file__))
        pg.Append(wxpg.PropertyCategory("1 - Basic Properties"))
        pg.Append(wxpg.StringProperty(label="Title", value="Title"))
        pg.Append(wxpg.StringProperty(label="X Label", value="X Label"))
        pg.Append(wxpg.StringProperty(label="Y Label", value="Y Label"))
        pg.Append(wxpg.BoolProperty(label="Grid", value=True))
        pg.SetPropertyAttribute(id="Grid", attrName="UseCheckbox", value=True)

        pg.Append(wxpg.PropertyCategory("2 - Data"))
        # https://discuss.wxpython.org/t/wxpython-pheonix-4-0-2-question-regarding-multichoiceproperty-and-setting-the-selection-of-the-choices/30044
        # https://discuss.wxpython.org/t/how-to-set-propertygrid-values/30152/4
        pg.Append(
            wxpg.EnumProperty(
                label="Scale",
                labels=['Linear', 'SemilogX', 'SemilogY', 'LogLog']))
        pg.Append(
            wxpg.EnumProperty(label="X Data",
                              name="X Data",
                              labels=['NaN'],
                              values=[0]))
        pg.Append(
            wxpg.MultiChoiceProperty(label="Y Data",
                                     name='Y Data',
                                     choices=['NaN'],
                                     value=['NaN']))
        pg.Append(
            wxpg.EnumProperty(label="Data Labels",
                              name="Data Labels",
                              labels=['NaN'],
                              values=[0]))

        pg.Append(wxpg.PropertyCategory("3 - Optional Static Plot Overlay"))
        pg.Append(wxpg.FileProperty(label="Overlay Plot",
                                    value=rf"{dir_path}"))
        pg.Append(wxpg.EnumProperty(label="X Axis Variable", labels=['']))
        pg.Append(wxpg.EnumProperty(label="Y Axis Variable", labels=['']))

        pg.Append(wxpg.PropertyCategory("4 - Advanced Properties"))
        pg.Append(wxpg.ArrayStringProperty(label="xLim", value=['0', '100']))
        pg.Append(wxpg.ArrayStringProperty(label="yLim", value=['0', '100']))
        pg.Append(wxpg.DateProperty(label="Date", value=wx.DateTime.Now()))
        pg.Append(wxpg.ColourProperty(label="Line Colour", value='#1f77b4'))
        pg.Append(wxpg.FontProperty(label="Font", value=self.GetFont()))
        pg.Grid.FitColumns()

    def on_run(self, event):
        self.thread.start()

    def run(self):
        print('run!')
        self.flag_complete = False
        T = Test(self)
        T.run()
        self.flag_complete = True

    def OnGridChangeEvent(self, evt):
        pg = self.property_grid_1
        prop = evt.GetProperty()
        if prop.GetName() == 'Overlay Plot':
            self.get_static_overlay_data()
            choices = list(self.overlay.keys())
            pg.GetProperty('X Axis Variable').SetChoices(
                wxpg.PGChoices(['Select option'] + choices))
            pg.GetProperty('Y Axis Variable').SetChoices(
                wxpg.PGChoices(['Select option'] + choices))

        elif prop.GetName(
        ) == 'X Axis Variable' or 'Y Axis Variable' and self.flag_complete:
            self.update_yAxisData()

    def get_static_overlay_data(self):
        pg = self.property_grid_1
        file = pg.GetPropertyValue('Overlay Plot')
        # Read CSV file
        kwargs = {
            'newline': '',
            'encoding': "utf-8-sig"
        }  # https://stackoverflow.com/a/49543191/3382269
        mode = 'r'
        if sys.version_info < (3, 0):
            kwargs.pop('newline', None)
            mode = 'rb'
        with open(f'{file}', mode, **kwargs) as csvfile:
            spamreader = csv.reader(csvfile, delimiter=',', quotechar='"')
            for row in spamreader:
                if not self.overlay:
                    self.overlay = {key: [] for key in row}
                else:
                    for idx, key in enumerate(self.overlay.keys()):
                        self.overlay[key].append(row[idx])

    def write_header(self, header):
        if not self.table:
            self.table = {key: [] for key in header}
        else:
            self.table = {
                header[col]: self.table[key]
                for col, key in enumerate(self.table.keys())
            }

        self.grid_1.write_list_to_row(self.row, self.table.keys())
        self.row += 1

    def write_to_log(self, row_data):
        self.grid_1.write_list_to_row(self.row, row_data)
        self.row += 1

        if not self.table:
            self.table = {
                f'col {idx}': item
                for idx, item in enumerate(row_data)
            }
        else:
            for idx, key in enumerate(self.table.keys()):
                self.table[key].append(row_data[idx])

    def plot_data(self):
        if not self.ax:
            self.draw_2dplot()
        else:
            pg = self.property_grid_1
            # self.x = self.table[pg.GetProperty('X Data').GetValueAsString()]
            self.x = self.table[pg.GetPropertyValueAsString('X Data')]
            self.y = [self.table[col] for col in pg.GetPropertyValue('Y Data')]
            self.update_yAxisData()

    def _plot_helper(self):
        pg = self.property_grid_1
        self.plot = [self.ax.plot] * len(self.y)
        for idx, y in enumerate(self.y):
            # plot returns a list of artists of which you want the first element when changing x or y data
            scale = pg.GetPropertyValueAsString('Scale')
            if scale == 'Linear':
                self.plot[idx], = self.ax.plot(self.x, y)
                self.draw_overlay(self.ax.plot)
            if scale == 'SemilogX':
                self.plot[idx], = self.ax.semilogx(self.x, y)
                self.draw_overlay(self.ax.semilogx)
            if scale == 'SemilogY':
                self.plot[idx], = self.ax.semilogy(self.x, y)
                self.draw_overlay(self.ax.semilogy)
            if scale == 'LogLog':
                self.plot[idx], = self.ax.loglog(self.x, y)
                self.draw_overlay(self.ax.loglog)

    def draw_overlay(self, plot_type):
        pg = self.property_grid_1
        x_var = pg.GetPropertyValueAsString('X Axis Variable')
        y_var = pg.GetPropertyValueAsString('Y Axis Variable')
        if x_var and y_var != ('' or 'Select option'):
            plot_type(self.overlay[x_var], self.overlay[y_var])

    def draw_2dplot(self):
        pg = self.property_grid_1
        if self.table:
            choices = list(self.table.keys())
            pg.GetProperty('X Data').SetChoices(wxpg.PGChoices(choices))
            pg.GetProperty('Y Data').SetChoices(wxpg.PGChoices(choices))
            pg.GetProperty('Data Labels').SetChoices(
                wxpg.PGChoices([""] + choices))
            pg.SetPropertyValue('X Data', choices[0])
            pg.SetPropertyValue('Y Data', [choices[1]])

            self.x, self.y = self.table[choices[0]], [self.table[choices[1]]]
        else:
            self.x, self.y = [0.], [[0.]]

        self.ax = self.figure.add_subplot(111)
        self._plot_helper()

        self.update_axis_labels()
        self.figure.tight_layout()
        self.toolbar.update()  # Not sure why this is needed - ADS

    def update_yAxisData(self):
        self.ax.clear()
        self._plot_helper()

        self.update_data_labels()
        self.update_axis_labels()
        self.ax.relim()
        self.ax.autoscale_view()

        self.canvas.draw()
        self.canvas.flush_events()

    def update_data_labels(self):
        pg = self.property_grid_1
        label_var = pg.GetPropertyValueAsString('Data Labels')
        if label_var:
            for idx, text in enumerate(self.table[label_var]):
                plt.annotate(f'{label_var}={round(text, 3)}',
                             (self.x[idx], self.y[0][idx]),
                             xytext=(0, 10),
                             textcoords='offset pixels',
                             horizontalalignment='center')

    def update_axis_labels(self):
        pg = self.property_grid_1
        self.ax.set_title(pg.GetPropertyValue('Title'),
                          fontsize=15,
                          fontweight="bold")
        self.ax.set_xlabel(pg.GetPropertyValue('X Label'), fontsize=8)
        self.ax.set_ylabel(pg.GetPropertyValue('Y Label'), fontsize=8)
        self.ax.grid(pg.GetPropertyValue('Grid'))
        self.plot[0].set_color(
            tuple(x / 255 for x in pg.GetPropertyValue('Line Colour')))
Beispiel #19
0
class PlotPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        self.createPlot()
        self.Fit()

    def createPlot(self):
        self.fgm_data       = []
        self.year           = date[0:4]
        self.month          = date[5:7]
        self.day            = date[8:10]
        self.hour_start     = t_start[0:2]
        self.minute_start   = t_start[3:5]
        self.second_start   = t_start[6:8]
        self.hour_stop      = t_stop[0:2]
        self.minute_stop    = t_stop[3:5]
        self.second_stop    = t_stop[6:8]
        self.date_time      = datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour_start), int(self.minute_start), int(self.second_start))
        self.doy            = self.date_time.timetuple().tm_yday

        self.plt = plot
        self.fig = self.plt.figure(1, figsize=(5, 4))       # Plot window size
        self.canvas = FigureCanvas(self, -1, self.fig)
        self.toolbar = NavigationToolbar(self.canvas)       # matplotlib toolbar
        self.toolbar.Realize()

        # Now put all into a sizer
        # sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        # This way of adding to sizer allows resizing
        # sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        # Best to allow the toolbar to resize!
        # sizer.Add(self.toolbar, 0, wx.GROW)
        self.sizer.Add(self.toolbar, 0, wx.GROW)
        self.SetSizer(self.sizer)
        self.Fit()
        # self.plt.show()
        # self.resize_event()

    #--------------------------------------------------
    #  readData()
    #--------------------------------------------------
    def readData(self, fileName):
        # filename = station_code + '-' + year+month+day + '-runmag.log'
        # f=open(data_dir+filename)
        self.f = open(fileName)
        self.header_index = 0
        self.csv_f = csv.reader(self.f, delimiter=",")
        # self.fgm_data = []
        for row in self.csv_f:
            self.fgm_data.append(row)
        self.date_time_array = np.array([self.year+'-'+self.month+'-'+self.day + ' ' + self.fgm_data[i][0][19:27] for i in range(self.header_index, len(self.fgm_data))])
        self.pattern = '%Y-%m-%d %H:%M:%S'
        self.epoch_temp = np.array([datetime.datetime.strptime(x, self.pattern) for x in self.date_time_array])
        self.Bx_temp = np.array([float(self.fgm_data[i][3][4:11]) for i in range(self.header_index, len(self.fgm_data))])
        self.By_temp = np.array([float(self.fgm_data[i][4][4:11]) for i in range(self.header_index, len(self.fgm_data))])
        self.Bz_temp = np.array([float(self.fgm_data[i][5][4:11]) for i in range(self.header_index, len(self.fgm_data))])
        #Array slicing/indexing
        self.start = datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour_start), int(self.minute_start), int(self.second_start))
        self.stop = datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour_stop), int(self.minute_stop), int(self.second_stop))
        self.start_index = bisect.bisect_left(self.epoch_temp, self.start)
        self.stop_index = bisect.bisect_left(self.epoch_temp, self.stop)
        self.Epoch = self.epoch_temp[self.start_index : self.stop_index]
        self.Bx = 1000*self.Bx_temp[self.start_index : self.stop_index]  #*1000: uT to nT
        self.By = 1000*self.By_temp[self.start_index : self.stop_index]
        self.Bz = 1000*self.Bz_temp[self.start_index : self.stop_index]
               
        # Specify date_str/time range for x-axis range
        self.ep_start = datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour_start), int(self.minute_start), int(self.second_start))
        self.ep_stop = datetime.datetime(int(self.year), int(self.month), int(self.day), int(self.hour_stop), int(self.minute_stop), int(self.second_stop))
        
        #This is for Pi magnetometer (PNI)
        self.Bx[np.where(self.Bx[:] >= 999999.0)] = np.nan
        self.By[np.where(self.By[:] >= 999999.0)] = np.nan
        self.Bz[np.where(self.Bz[:] >= 999999.0)] = np.nan
        
        #Moving average
        self.N = 10 #WindowWidth in sec
        self.Bx = np.array(moving_average(self.Bx, self.N))
        self.By = np.array(moving_average(self.By, self.N))
        self.Bz = np.array(moving_average(self.Bz, self.N))
        self.Epoch = np.array(self.Epoch[0:len(self.Bx)])  #to match the new array size with the moving averaged array. 
        self.Bt = np.sqrt(self.Bx**2 + self.By**2 + self.Bz**2)

        # Subplot 1
        self.ax1 = self.fig.add_subplot(411)
        self.box = self.ax1.get_position()
        self.plt.subplots_adjust(left=self.box.x0, right=self.box.x1-0.08, top=self.box.y1, bottom=0.1, hspace=0.1)
        
        self.ax1.plot(self.Epoch, self.Bx, label='Bx', linewidth=0.5)
        self.title = station_code + ' HamSCI Mag '  + date + ' ' + t_start + ' - ' + t_stop
        self.ax1.set_title(self.title)
        
        self.ax1.set_xlim([self.ep_start, self.ep_stop])    #without this, the time range will not show up properly because there are missing data.
        self.ax1.set_ylabel('Bx (nT)')
        self.ax1.get_xaxis().set_ticklabels([])
        self.ax1.tick_params(axis='x', direction='out', top='on')
        self.ax1.tick_params(axis='y', direction='out', right='on')
        self.ax1.minorticks_on()
        self.ax1.tick_params(axis='x', which ='minor', direction='out', top='on')
        self.ax1.tick_params(axis='y', which ='minor', direction='out', right='on')
        self.ax1.set_ylim(47600, 47800)
        self.ax1.yaxis.set_major_formatter(FormatStrFormatter('%.1f'))
        
        # Subplot 2
        self.ax2 = self.fig.add_subplot(412)
        self.ax2.plot(self.Epoch, self.By, label='By', linewidth=0.5)

        self.ax2.set_xlim([self.ep_start, self.ep_stop])     #without this, the time range will not show up properly because there are missing data.       
        self.ax2.set_ylabel('By (nT)')
        self.ax2.get_xaxis().set_ticklabels([])
        self.ax2.tick_params(axis='x', direction='out', top='on')
        self.ax2.tick_params(axis='y', direction='out', right='on')
        self.ax2.minorticks_on()
        self.ax2.tick_params(axis='x', which ='minor', direction='out', top='on')
        self.ax2.tick_params(axis='y', which ='minor', direction='out', right='on')
        self.ax2.set_ylim(-100, 100)
        self.ax2.yaxis.set_major_formatter(FormatStrFormatter('%.1f'))
        
        # Subplot 3
        self.ax3 = self.fig.add_subplot(413)
        self.ax3.plot(self.Epoch, self.Bz, label='Bz', linewidth=0.5)

        self.ax3.set_xlim([self.ep_start, self.ep_stop])     #without this, the time range will not show up properly because there are missing data.
        self.ax3.set_ylabel('Bz (nT)')
        self.ax3.get_xaxis().set_ticklabels([])
        self.ax3.tick_params(axis='x', direction='out', top='on')
        self.ax3.tick_params(axis='y', direction='out', right='on')
        self.ax3.minorticks_on()
        self.ax3.tick_params(axis='x', which ='minor', direction='out', top='on')
        self.ax3.tick_params(axis='y', which ='minor', direction='out', right='on')
        self.ax3.set_ylim(-15400, -15200)
        self.ax3.yaxis.set_major_formatter(FormatStrFormatter('%.1f'))
        
        # Subplot 4
        self.ax4 = self.fig.add_subplot(414)
        self.ax4.plot(self.Epoch, self.Bt, label='Bt', linewidth=0.5)
        self.ax4.set_xlim([self.ep_start, self.ep_stop])     #without this, the time range will not show up properly because there are missing data.
        self.ax4.set_ylabel('Bt (nT)')
        self.ax4.get_xaxis().set_ticklabels([])
        self.ax4.tick_params(axis='x', direction='out', top='on')
        self.ax4.tick_params(axis='y', direction='out', right='on')
        self.ax4.minorticks_on()
        self.ax4.tick_params(axis='x', which ='minor', direction='out', top='on')
        self.ax4.tick_params(axis='y', which ='minor', direction='out', right='on')
        self.ax4.set_ylim(50000, 50200)
        self.ax4.yaxis.set_major_formatter(FormatStrFormatter('%.1f'))
       
        # X-axis
        self.ax4.set_xlabel('UT (hh:mm)')
        self.date_fmt = '%H:%M'  #'%d-%m-%y %H:%M:%S'  #Choose your xtick format string
        self.date_formatter = mdate.DateFormatter(self.date_fmt)
        self.ax4.xaxis.set_major_formatter(self.date_formatter)

        self.toolbar.update()       # Not sure why this is needed - ADS

        
    #--------------------------------------------------
    #  resetPlot()
    #--------------------------------------------------
    def resetPlot(self):
        self.plt.close()
        self.plt = None
        self.createPlot()
        self.Fit()

    #--------------------------------------------------
    #  OnSavePlot()
    #--------------------------------------------------
    def OnSavePlot(self, event):
        t_start_str = t_start[0:2] + t_start[3:5]
        t_stop_str = t_stop[0:2] + t_stop[3:5]
        filename_plot = station_code + '_' + year+month+day + '_' + t_start_str + '_' + t_stop_str + '_MovingAve.jpg'
        plt.savefig(plot_dir + filename_plot, format='jpg', bbox_inches='tight', dpi=600)
        plt.close()

    #--------------------------------------------------
    #  GetToolBar()
    #--------------------------------------------------
    def GetToolBar(self):
        # You will need to override GetToolBar if you are using an
        # unmanaged toolbar in your frame
        return self.toolbar

    #--------------------------------------------------
    #  onEraseBackground()
    #--------------------------------------------------
    def onEraseBackground(self, evt):
        # this is supposed to prevent redraw flicker on some X servers...
        pass
Beispiel #20
0
class Interface(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,
                          None,
                          wx.ID_ANY,
                          "Hot Data Viewer",
                          size=(700, 600))

        self.plotConfigFrame = PlotConfigFrame(self)

        self._makeWidget()
        self._makeSizer()
        self._makeBinding()

    def _makeWidget(self):

        self.figure = Figure(facecolor=(0.9, 0.9, 0.9, 1))
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.axes = self.figure.add_axes((0.1, 0.1, 0.8, 0.8))

        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        self.toolbar.update()

        self.OpenFileButton = wx.Button(self, -1, "Open File", size=(100, 20))
        self.PlotConfigButton = wx.Button(self,
                                          -1,
                                          "Plot Config",
                                          size=(100, 20))

    def _makeSizer(self):

        topSizer = wx.BoxSizer(wx.HORIZONTAL)

        middleSizer = wx.BoxSizer(wx.HORIZONTAL)
        middleSizer = wx.BoxSizer(wx.HORIZONTAL)
        middleSizer.Add(self.canvas, 1, wx.EXPAND | wx.ALL, 0)

        bottomSizer = wx.BoxSizer(wx.HORIZONTAL)

        bottomSizer.Add(self.OpenFileButton, 0, wx.ALL | wx.EXPAND, 0)
        bottomSizer.Add(self.PlotConfigButton, 0, wx.EXPAND | wx.ALL, 0)
        bottomSizer.AddStretchSpacer(1)
        bottomSizer.Add(self.toolbar, 0, wx.ALL | wx.EXPAND, 0)

        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(topSizer, 0, wx.EXPAND | wx.ALL, 5)
        mainSizer.Add(middleSizer, 1, wx.EXPAND | wx.ALL, 5)
        mainSizer.Add(bottomSizer, 0, wx.EXPAND | wx.ALL, 5)

        self.SetSizer(mainSizer)

    def _makeBinding(self):

        self.Bind(wx.EVT_CLOSE, self.onClose)

        self.OpenFileButton.Bind(wx.EVT_BUTTON, self.onOpenFile)
        self.PlotConfigButton.Bind(wx.EVT_BUTTON, self.onPlotConfig)

    def onClose(self, event):
        self.Destroy()
        for dataFollower in self.dataFollowerList:
            dataFollower.stop()

        print("Hot Data Viewer Closed!")

    def onOpenFile(self, event):

        with wx.FileDialog(self,
                           "Open data file",
                           wildcard="Data files (*.txt)|*.txt",
                           style=wx.FD_OPEN
                           | wx.FD_FILE_MUST_EXIST) as fileDialog:

            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return

            pathName = fileDialog.GetPath()
            try:
                self.openDataFile(pathName)
                self.lastDataFileDir, dump = os.path.split(pathName)
            except IOError:
                wx.LogError("Cannot open file.")

    def onPlotConfig(self, event):

        self.plotConfigFrame.Show()
Beispiel #21
0
class PhaseModulatorPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.parent = parent

        self.left_panel = wx.Panel(self, wx.ID_ANY)
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # PANELS =======================================================================================================
        # LEFT Panel ---------------------------------------------------------------------------------------------------
        self.text_sample_rate = wx.TextCtrl(self.left_panel, wx.ID_ANY, "80000")
        self.text_mainlobe_error = wx.TextCtrl(self.left_panel, wx.ID_ANY, "0.06")

        self.text_carrier_amplitude = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1")
        self.text_carrier_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1e3")

        self.combo_waveform = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                          choices=["Sine", "Triangle", 'Sawtooth', "Square", "Shift Keying"],
                                          style=wx.CB_DROPDOWN | wx.CB_READONLY)
        self.combo_modulation = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                            choices=["Amplitude", "Frequency", "Phase"],
                                            style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.text_modulation_index = wx.TextCtrl(self.left_panel, wx.ID_ANY, "1")
        self.text_message_frequency = wx.TextCtrl(self.left_panel, wx.ID_ANY, "100")
        self.text_message_phase = wx.TextCtrl(self.left_panel, wx.ID_ANY, "0")
        self.text_report_rms = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.text_report_bw = wx.TextCtrl(self.left_panel, wx.ID_ANY, "", style=wx.TE_READONLY)

        self.btn_start = wx.Button(self.left_panel, wx.ID_ANY, "RUN")
        self.combo_mode = wx.ComboBox(self.left_panel, wx.ID_ANY,
                                      choices=["Single", "Continuous"],
                                      style=wx.CB_DROPDOWN)

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1, 1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        # instance variables -------------------------------------------------------------------------------------------
        self.flag_complete = True  # Flag indicates any active threads (False) or thread completed (True)
        self.t = threading.Thread()
        self.pm = pm(self)
        self.user_input = {}

        # Plot objects -------------------------------------------------------------------------------------------------
        self.ax1 = self.figure.add_subplot(211)
        self.ax2 = self.figure.add_subplot(212)

        self.temporal, = self.ax1.plot([], [], linestyle='-')
        self.temporal_2, = self.ax1.plot([], [], linestyle='--')
        self.spectral, = self.ax2.plot([], [], color='#C02942')

        # Plot Annotations ---------------------------------------------------------------------------------------------
        # https://stackoverflow.com/a/38677732
        self.arrow_dim_obj = self.ax2.annotate("", xy=(0, 0), xytext=(0, 0),
                                               textcoords=self.ax2.transData, arrowprops=dict(arrowstyle='<->'))
        self.bar_dim_obj = self.ax2.annotate("", xy=(0, 0), xytext=(0, 0),
                                             textcoords=self.ax2.transData, arrowprops=dict(arrowstyle='|-|'))
        bbox = dict(fc="white", ec="none")
        self.dim_text = self.ax2.text(0, 0, "", ha="center", va="center", bbox=bbox)

        # BINDINGS =====================================================================================================
        # Run Measurement (start subprocess) ---------------------------------------------------------------------------
        on_run_event = lambda event: self.on_run(event)
        self.Bind(wx.EVT_BUTTON, on_run_event, self.btn_start)

        on_combo_modulation_select = lambda event: self.combo_modulation_select(event)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_modulation_select, self.combo_modulation)
        self.Bind(wx.EVT_COMBOBOX_CLOSEUP, on_combo_modulation_select, self.combo_waveform)

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.canvas.SetMinSize((700, 490))

        self.left_panel.SetBackgroundColour(wx.Colour(255, 255, 255))
        self.plot_panel.SetBackgroundColour(wx.Colour(255, 255, 255))

        self.left_panel.SetMinSize((310, 502))
        # self.left_sub_panel.SetBackgroundColour(wx.Colour(255, 0, 255))
        self.plot_panel.SetMinSize((700, 502))
        self.combo_modulation.SetSelection(0)
        self.combo_waveform.SetSelection(0)
        self.canvas.SetMinSize((700, 490))

        self.combo_mode.SetSelection(0)
        self.combo_mode.SetMinSize((110, 23))

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_left_panel = wx.GridBagSizer(0, 0)
        grid_sizer_left_sub_btn_row = wx.GridBagSizer(0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)

        # LEFT PANEL ===================================================================================================
        # TITLE --------------------------------------------------------------------------------------------------------
        label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "MODULATION SCHEMES")
        label_1.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_1, (0, 0), (1, 2), 0, 0)

        # static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        # static_line_1.SetMinSize((300, 2))
        # grid_sizer_left_panel.Add(static_line_1, (1, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # SIMULATION SETUP ---------------------------------------------------------------------------------------------
        label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Simulation Settings")
        label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_source, (2, 0), (1, 2), wx.TOP, 10)

        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (3, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        label_sample_rate = wx.StaticText(self.left_panel, wx.ID_ANY, "Sample Rate")
        grid_sizer_left_panel.Add(label_sample_rate, (4, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_sample_rate, (4, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)")
        grid_sizer_left_panel.Add(label_Hz, (4, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        label_error = wx.StaticText(self.left_panel, wx.ID_ANY, "Error")
        grid_sizer_left_panel.Add(label_error, (5, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_mainlobe_error, (5, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)
        label_percent = wx.StaticText(self.left_panel, wx.ID_ANY, "(%)")
        grid_sizer_left_panel.Add(label_percent, (5, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        # CARRIER SETUP ------------------------------------------------------------------------------------------------
        label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Carrier Signal")
        label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_source, (6, 0), (1, 1), wx.TOP, 10)

        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (7, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        label_carrier_amplitude = wx.StaticText(self.left_panel, wx.ID_ANY, "Amplitude:")
        grid_sizer_left_panel.Add(label_carrier_amplitude, (8, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_carrier_amplitude, (8, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        label_carrier_frequency = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency:")
        grid_sizer_left_panel.Add(label_carrier_frequency, (9, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_carrier_frequency, (9, 1), (1, 1), wx.LEFT, 5)
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)")
        grid_sizer_left_panel.Add(label_Hz, (9, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        # MODULATION SETUP ---------------------------------------------------------------------------------------------
        label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Message Signal")
        label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_source, (10, 0), (1, 2), wx.TOP, 10)

        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (11, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        label_modulation = wx.StaticText(self.left_panel, wx.ID_ANY, "Modulation:")
        grid_sizer_left_panel.Add(label_modulation, (12, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.combo_modulation, (12, 1), (1, 1), wx.EXPAND | wx.BOTTOM | wx.LEFT, 5)

        label_waveform = wx.StaticText(self.left_panel, wx.ID_ANY, "Waveform:")
        grid_sizer_left_panel.Add(label_waveform, (13, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.combo_waveform, (13, 1), (1, 1), wx.EXPAND | wx.BOTTOM | wx.LEFT, 5)

        label_modulation_index = wx.StaticText(self.left_panel, wx.ID_ANY, "Modulation Index:")
        grid_sizer_left_panel.Add(label_modulation_index, (14, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_modulation_index, (14, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        label_message_frequency = wx.StaticText(self.left_panel, wx.ID_ANY, "Frequency:")
        grid_sizer_left_panel.Add(label_message_frequency, (15, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_message_frequency, (15, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)
        label_Hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz)")
        grid_sizer_left_panel.Add(label_Hz, (15, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT, 5)

        label_message_phase = wx.StaticText(self.left_panel, wx.ID_ANY, "Phase:")
        grid_sizer_left_panel.Add(label_message_phase, (16, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_message_phase, (16, 1), (1, 1), wx.LEFT, 5)
        label_deg = wx.StaticText(self.left_panel, wx.ID_ANY, "(deg)")
        grid_sizer_left_panel.Add(label_deg, (16, 2), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)

        # METRICS (RESULTS) --------------------------------------------------------------------------------------------
        label_source = wx.StaticText(self.left_panel, wx.ID_ANY, "Metrics")
        label_source.SetFont(wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_source, (17, 0), (1, 1), wx.TOP, 10)

        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (18, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        label_report_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:")
        grid_sizer_left_panel.Add(label_report_rms, (19, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_report_rms, (19, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        label_report_bw = wx.StaticText(self.left_panel, wx.ID_ANY, "BW:")
        grid_sizer_left_panel.Add(label_report_bw, (20, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, 5)
        grid_sizer_left_panel.Add(self.text_report_bw, (20, 1), (1, 1), wx.BOTTOM | wx.LEFT, 5)

        # BUTTONS ------------------------------------------------------------------------------------------------------
        static_line_4 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_4.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_4, (21, 0), (1, 3), wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        grid_sizer_left_sub_btn_row.Add(self.btn_start, (0, 0), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        grid_sizer_left_sub_btn_row.Add(self.combo_mode, (0, 1), (1, 1), wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
        grid_sizer_left_panel.Add(grid_sizer_left_sub_btn_row, (22, 0), (1, 3), wx.ALIGN_TOP | wx.BOTTOM, 13)

        self.left_panel.SetSizer(grid_sizer_left_panel)

        # PLOT PANEL ===================================================================================================
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)

        # add to main panel --------------------------------------------------------------------------------------------
        grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    # ------------------------------------------------------------------------------------------------------------------
    def toggle_controls(self):
        if self.text_carrier_amplitude.Enabled:
            self.combo_modulation.Disable()
            self.text_sample_rate.Disable()
            self.text_mainlobe_error.Disable()
            self.text_carrier_amplitude.Disable()
            self.text_carrier_frequency.Disable()
            self.text_modulation_index.Disable()
            self.text_message_frequency.Disable()
            self.text_message_phase.Disable()

        else:
            self.combo_modulation.Enable()
            self.text_sample_rate.Enable()
            self.text_mainlobe_error.Enable()
            self.text_carrier_amplitude.Enable()
            self.text_carrier_frequency.Enable()
            self.text_modulation_index.Enable()
            self.text_message_frequency.Enable()
            self.text_message_phase.Enable()

    def combo_modulation_select(self, evt):
        if self.combo_modulation.GetSelection() == 0:
            # amplitude modulation
            self.text_modulation_index.SetValue('1')

        elif self.combo_modulation.GetSelection() == 1:
            # frequency modulation
            self.text_modulation_index.SetValue('5')

        elif self.combo_modulation.GetSelection() == 2:
            # phase modulation
            self.text_modulation_index.SetValue('5')

        else:
            raise ValueError("Invalid modulation selected!")

        # PSK
        if self.combo_waveform.GetSelection() in (3, 4):
            if self.combo_modulation.GetSelection() == 2:
                self.text_message_phase.SetValue('180')
            else:
                self.text_message_phase.SetValue('0')
        else:
            self.text_message_phase.SetValue('0')

    # ------------------------------------------------------------------------------------------------------------------
    def get_values(self):
        mode = self.combo_mode.GetSelection()

        sample_rate = to_float(self.text_sample_rate.GetValue(), property="sample rate")
        main_lobe_error = to_float(self.text_mainlobe_error.GetValue(), property="main lobe error")
        modulation_type = self.combo_modulation.GetSelection()
        waveform_lookup = {0: 'sine', 1: 'triangle', 2: 'sawtooth', 3: 'square', 4: 'keying'}
        waveform_type = waveform_lookup[self.combo_waveform.GetSelection()]
        carrier_amplitude = to_float(self.text_carrier_amplitude.GetValue(), property="carrier amplitude")
        carrier_frequency = to_float(self.text_carrier_frequency.GetValue(), property="carrier frequency")
        modulation_index = to_float(self.text_modulation_index.GetValue(), property="modulation index")
        message_frequency = to_float(self.text_message_frequency.GetValue(), property="message frequency")
        message_phase = to_float(self.text_message_phase.GetValue(), property="message phase")

        self.user_input = {'mode': mode,
                           'sample_rate': sample_rate,
                           'main_lobe_error': main_lobe_error,
                           'waveform_type': waveform_type,
                           'modulation_type': modulation_type,
                           'carrier_amplitude': carrier_amplitude,
                           'carrier_frequency': carrier_frequency,
                           'modulation_index': modulation_index,
                           'message_frequency': message_frequency,
                           'message_phase': message_phase,
                           }

        self.pm.user_values_good = True

    # ------------------------------------------------------------------------------------------------------------------
    def thread_this(self, func, arg=()):
        self.t = threading.Thread(target=func, args=arg, daemon=True)
        self.t.start()

    # ------------------------------------------------------------------------------------------------------------------
    def on_run(self, evt):
        self.get_values()
        if not self.t.is_alive() and self.flag_complete:
            # start new thread
            self.thread_this(self.pm.start, (self.user_input,))
            self.btn_start.SetLabel('STOP')

        elif self.t.is_alive() and self.user_input['mode'] == 1:
            # stop continuous
            # https://stackoverflow.com/a/36499538
            self.t.do_run = False
            self.btn_start.SetLabel('RUN')
        else:
            print('thread already running.')

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ms)')
        self.ax1.set_ylabel('AMPLITUDE')

        self.ax2.set_title('SPECTRAL DATA')
        self.ax2.set_xlabel('FREQUENCY (kHz)')
        self.ax2.set_ylabel('AMPLITUDE (V)')
        self.ax2.grid()
        self.figure.align_ylabels([self.ax1, self.ax2])
        self.figure.tight_layout()

    def plot(self, params):
        # TEMPORAL -----------------------------------------------------------------------------------------------------
        xt = params['xt']
        yt = params['yt']
        mt = params['mt']

        self.temporal.set_data(xt, yt)
        self.temporal_2.set_data(xt, mt)

        xt_left = params['xt_left']
        xt_right = params['xt_right']
        yt_btm = params['yt_btm']
        yt_top = params['yt_top']
        yt_tick = params['yt_tick']

        self.ax1.set_xlim(left=xt_left, right=xt_right)
        # self.ax1.set_yticks(np.arange(yt_btm, yt_top, yt_tick))

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        xf = params['xf']
        yf = params['yf']

        self.spectral.set_data(xf, yf)

        xf_left = params['xf_left']
        xf_right = params['xf_right']
        yf_btm = params['yf_btm']
        yf_top = params['yf_top']
        yf_ticks = params['yf_ticks']

        self.ax2.set_xlim(left=xf_left, right=xf_right)
        self.ax2.set_ylim(bottom=yf_btm, top=yf_top)
        self.ax2.set_yticks(yf_ticks)

        # Annotations --------------------------------------------------------------------------------------------------
        dim_height = params['dim_height']
        dim_left = params['dim_left']
        dim_right = params['dim_right']
        bw = params['bw']

        # Arrow dimension line update ----------------------------------------------------------------------------------
        # https://stackoverflow.com/a/48684902 -------------------------------------------------------------------------
        self.arrow_dim_obj.xy = (dim_left, dim_height)
        self.arrow_dim_obj.set_position((dim_right, dim_height))
        self.arrow_dim_obj.textcoords = self.ax2.transData

        # Bar dimension line update ------------------------------------------------------------------------------------
        self.bar_dim_obj.xy = (dim_left, dim_height)
        self.bar_dim_obj.set_position((dim_right, dim_height))
        self.bar_dim_obj.textcoords = self.ax2.transData

        # dimension text update ----------------------------------------------------------------------------------------
        self.dim_text.set_position((dim_left + (bw / 2), dim_height))
        self.dim_text.set_text(params['bw_text'])

        # REDRAW PLOT --------------------------------------------------------------------------------------------------
        self.plot_redraw()

    def plot_redraw(self):
        try:
            self.ax1.relim()  # recompute the ax.dataLim
        except ValueError:
            xt_length = len(self.ax1.get_xdata())
            yt_length = len(self.ax1.get_ydata())
            print(f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?')
            raise
        self.ax1.margins(x=0)
        self.ax1.autoscale(axis='y')

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()

        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def results_update(self, results):
        yrms = results['yrms']
        bw = results['bw']

        self.text_report_rms.SetValue(f"{'{:0.3e}'.format(yrms)}")
        self.text_report_bw.SetValue(f"{'{:0.3e}'.format(bw)}")

    def error_dialog(self, error_message):
        print(error_message)
        dial = wx.MessageDialog(None, str(error_message), 'Error', wx.OK | wx.ICON_ERROR)
        dial.ShowModal()
Beispiel #22
0
class MyDemoPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY)

        self.frame = parent
        self.left_panel = wx.Panel(self, wx.ID_ANY)
        self.plot_panel = wx.Panel(self, wx.ID_ANY, style=wx.SIMPLE_BORDER)

        # PLOT Panel ---------------------------------------------------------------------------------------------------
        self.figure = plt.figure(figsize=(1,
                                          1))  # look into Figure((5, 4), 75)
        self.canvas = FigureCanvas(self.plot_panel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()

        self.ax1 = self.figure.add_subplot(211)
        self.ax2 = self.figure.add_subplot(212)

        self.temporal, = self.ax1.plot([], [],
                                       linestyle='-',
                                       linewidth=5,
                                       alpha=0.3)
        self.temporal_sampled, = self.ax1.plot([], [],
                                               linestyle='-',
                                               marker='x')
        self.spectral, = self.ax2.plot([], [], color='#C02942')
        self.x, self.y = [], []

        self.text_ctrl_fs = wx.TextCtrl(self.left_panel,
                                        wx.ID_ANY,
                                        style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_ldf = wx.TextCtrl(self.left_panel,
                                         wx.ID_ANY,
                                         style=wx.TE_PROCESS_ENTER)
        self.text_ctrl_ldf.SetToolTip("Lowest Recoverable Frequency")

        self.text_ctrl_samples = wx.TextCtrl(self.left_panel,
                                             wx.ID_ANY,
                                             "",
                                             style=wx.TE_READONLY)
        self.text_ctrl_rms_true = wx.TextCtrl(self.left_panel,
                                              wx.ID_ANY,
                                              "",
                                              style=wx.TE_READONLY)
        self.text_ctrl_rms_sampled = wx.TextCtrl(self.left_panel,
                                                 wx.ID_ANY,
                                                 "",
                                                 style=wx.TE_READONLY)
        self.text_ctrl_rms_delta = wx.TextCtrl(self.left_panel,
                                               wx.ID_ANY,
                                               "",
                                               style=wx.TE_READONLY)

        self.text_ctrl_cycles = wx.TextCtrl(self.left_panel,
                                            wx.ID_ANY,
                                            "",
                                            style=wx.TE_READONLY)
        self.text_ctrl_samples_per_cycle = wx.TextCtrl(self.left_panel,
                                                       wx.ID_ANY,
                                                       "",
                                                       style=wx.TE_READONLY)
        self.text_ctrl_aliased_freq01 = wx.TextCtrl(self.left_panel,
                                                    wx.ID_ANY,
                                                    "",
                                                    style=wx.TE_READONLY)
        self.text_ctrl_aliased_freq02 = wx.TextCtrl(self.left_panel,
                                                    wx.ID_ANY,
                                                    "",
                                                    style=wx.TE_READONLY)
        self.text_ctrl_aliased_freq03 = wx.TextCtrl(self.left_panel,
                                                    wx.ID_ANY,
                                                    "",
                                                    style=wx.TE_READONLY)

        on_update = lambda event: self.update(event)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_fs)
        self.Bind(wx.EVT_TEXT_ENTER, on_update, self.text_ctrl_ldf)

        self.__set_properties()
        self.__do_layout()
        self.__do_plot_layout()
        self.update(wx.Event)

    def __set_properties(self):
        self.SetBackgroundColour(wx.Colour(240, 240, 240))
        self.canvas.SetMinSize((700, 490))

        self.text_ctrl_fs.SetValue('12')
        self.text_ctrl_ldf.SetValue('100')

        # width = 200
        # self.text_ctrl_fs.SetMaxSize((width, 23))
        # self.text_ctrl_ldf.SetMaxSize((width, 23))
        #
        # self.text_ctrl_samples.SetMaxSize((width, 23))
        # self.text_ctrl_rms_true.SetMaxSize((width, 23))
        # self.text_ctrl_rms_sampled.SetMaxSize((width, 23))
        # self.text_ctrl_rms_delta.SetMaxSize((width, 23))
        #
        # self.text_ctrl_cycles.SetMaxSize((width, 23))
        # self.text_ctrl_samples_per_cycle.SetMaxSize((width, 23))
        # self.text_ctrl_aliased_freq01.SetMaxSize((width, 23))
        # self.text_ctrl_aliased_freq02.SetMaxSize((width, 23))
        # self.text_ctrl_aliased_freq03.SetMaxSize((width, 23))

    def __do_layout(self):
        sizer_2 = wx.GridSizer(1, 1, 0, 0)
        grid_sizer_1 = wx.FlexGridSizer(1, 2, 0, 0)
        grid_sizer_plot = wx.GridBagSizer(0, 0)
        grid_sizer_left_panel = wx.GridBagSizer(0, 0)

        # LEFT PANEL ---------------------------------------------------------------------------------------------------
        # TITLE --------------------------------------------------------------------------------------------------------
        row = 0
        label_1 = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS && ALIASING")
        label_1.SetFont(
            wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(label_1, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT | wx.TOP, 5)

        row += 1
        static_line_1 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_1.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_1, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        # SIGNAL -------------------------------------------------------------------------------------------------------
        row += 1
        lbl_signal = wx.StaticText(self.left_panel, wx.ID_ANY,
                                   "Signal Characteristics")
        lbl_signal.SetFont(
            wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_signal, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_signal_rms = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS:")
        grid_sizer_left_panel.Add(lbl_signal_rms, (row, 0), (1, 1),
                                  wx.LEFT | wx.RIGHT, 5)
        lbl_signal_rms_val = wx.StaticText(self.left_panel, wx.ID_ANY, "1")
        grid_sizer_left_panel.Add(lbl_signal_rms_val, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        lbl_signal_freq = wx.StaticText(self.left_panel, wx.ID_ANY,
                                        "Frequency (f0):")
        grid_sizer_left_panel.Add(lbl_signal_freq, (row, 0), (1, 1),
                                  wx.LEFT | wx.RIGHT, 5)
        lbl_signal_freq_val = wx.StaticText(self.left_panel, wx.ID_ANY,
                                            "1000 Hz")
        grid_sizer_left_panel.Add(lbl_signal_freq_val, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        # SETTINGS -----------------------------------------------------------------------------------------------------
        row += 2
        lbl_settings = wx.StaticText(self.left_panel, wx.ID_ANY, "Settings")
        lbl_settings.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_settings, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        static_line_2 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_2.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_2, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        lbl_fs = wx.StaticText(self.left_panel, wx.ID_ANY, "Fs:")
        grid_sizer_left_panel.Add(
            lbl_fs, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_fs, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        lbl_units_kHz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):")
        grid_sizer_left_panel.Add(
            lbl_units_kHz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_ldf = wx.StaticText(self.left_panel, wx.ID_ANY, "LDF:")
        lbl_ldf.SetToolTip("Lowest Recoverable Frequency")
        grid_sizer_left_panel.Add(
            lbl_ldf, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_ldf, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(Hz):")
        grid_sizer_left_panel.Add(
            lbl_units_hz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        # RESULTS ------------------------------------------------------------------------------------------------------
        row += 1
        lbl_results = wx.StaticText(self.left_panel, wx.ID_ANY, "Results")
        lbl_results.SetFont(
            wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(lbl_results, (row, 0), (1, 3),
                                  wx.LEFT | wx.RIGHT, 5)

        row += 1
        static_line_3 = wx.StaticLine(self.left_panel, wx.ID_ANY)
        static_line_3.SetMinSize((300, 2))
        grid_sizer_left_panel.Add(static_line_3, (row, 0), (1, 3),
                                  wx.BOTTOM | wx.RIGHT | wx.TOP, 5)

        row += 1
        lbl_samples = wx.StaticText(self.left_panel, wx.ID_ANY, "Samples (N):")
        grid_sizer_left_panel.Add(
            lbl_samples, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_samples, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        lbl_cycles = wx.StaticText(self.left_panel, wx.ID_ANY, "cycles:")
        grid_sizer_left_panel.Add(
            lbl_cycles, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_cycles, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        lbl_samples_per_cycle = wx.StaticText(self.left_panel, wx.ID_ANY,
                                              "N/cycles:")
        grid_sizer_left_panel.Add(
            lbl_samples_per_cycle, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_samples_per_cycle, (row, 1),
                                  (1, 2), wx.BOTTOM, 5)

        row += 2
        lbl_rms_true = wx.StaticText(self.left_panel, wx.ID_ANY, "RMS (True):")
        grid_sizer_left_panel.Add(
            lbl_rms_true, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_rms_true, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        lbl_rms_sampled = wx.StaticText(self.left_panel, wx.ID_ANY,
                                        "RMS (Sampled):")
        grid_sizer_left_panel.Add(
            lbl_rms_sampled, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_rms_sampled, (row, 1), (1, 2),
                                  wx.BOTTOM, 5)

        row += 1
        lbl_delta = wx.StaticText(self.left_panel, wx.ID_ANY, "Delta RMS:")
        grid_sizer_left_panel.Add(
            lbl_delta, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_rms_delta, (row, 1), (1, 1),
                                  wx.BOTTOM, 5)
        lbl_units_ppm = wx.StaticText(self.left_panel, wx.ID_ANY, "(ppm):")
        grid_sizer_left_panel.Add(
            lbl_units_ppm, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 2
        lbl_aliased = wx.StaticText(self.left_panel, wx.ID_ANY,
                                    "Aliased Frequency:")
        lbl_aliased.SetFont(
            wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
                    wx.FONTWEIGHT_BOLD, 0, ""))
        grid_sizer_left_panel.Add(
            lbl_aliased, (row, 0), (1, 3),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_h1 = wx.StaticText(self.left_panel, wx.ID_ANY, "1st Harmonic:")
        grid_sizer_left_panel.Add(
            lbl_h1, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq01, (row, 1),
                                  (1, 1), wx.BOTTOM, 5)
        lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):")
        grid_sizer_left_panel.Add(
            lbl_units_hz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_h2 = wx.StaticText(self.left_panel, wx.ID_ANY, "3rd Harmonic:")
        grid_sizer_left_panel.Add(
            lbl_h2, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq02, (row, 1),
                                  (1, 1), wx.BOTTOM, 5)
        lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):")
        grid_sizer_left_panel.Add(
            lbl_units_hz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        row += 1
        lbl_h3 = wx.StaticText(self.left_panel, wx.ID_ANY, "5th Harmonic:")
        grid_sizer_left_panel.Add(
            lbl_h3, (row, 0), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)
        grid_sizer_left_panel.Add(self.text_ctrl_aliased_freq03, (row, 1),
                                  (1, 1), wx.BOTTOM, 5)
        lbl_units_hz = wx.StaticText(self.left_panel, wx.ID_ANY, "(kHz):")
        grid_sizer_left_panel.Add(
            lbl_units_hz, (row, 2), (1, 1),
            wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5)

        self.left_panel.SetSizer(grid_sizer_left_panel)

        # PLOT PANEL ===================================================================================================
        grid_sizer_plot.Add(self.canvas, (0, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.Add(self.toolbar, (1, 0), (1, 1), wx.ALL | wx.EXPAND)
        grid_sizer_plot.AddGrowableRow(0)
        grid_sizer_plot.AddGrowableCol(0)
        self.plot_panel.SetSizer(grid_sizer_plot)

        # add to main panel --------------------------------------------------------------------------------------------
        grid_sizer_1.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 5)
        grid_sizer_1.Add(self.plot_panel, 1, wx.EXPAND, 5)
        grid_sizer_1.AddGrowableRow(0)
        grid_sizer_1.AddGrowableCol(1)

        sizer_2.Add(grid_sizer_1, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_2)
        self.Layout()

    def popup_dialog(self, message):
        print(message)
        dial = wx.MessageDialog(None, str(message), 'Error',
                                wx.OK | wx.ICON_ERROR)
        dial.ShowModal()

    def update(self, evt):
        try:
            f0 = 1000
            signal_rms = 1

            Fs = to_float(self.text_ctrl_fs.GetValue()) * 1e3
            LDF = to_float(self.text_ctrl_ldf.GetValue())

            # TEMPORAL -------------------------------------------------------------------------------------------------
            N = getWindowLength(f0,
                                200e3,
                                windfunc='blackman',
                                error=LDF,
                                mainlobe_type='absolute')
            self.x, self.y = ADC(signal_rms, f0, 200e3, N, CONTAINS_HARMONICS,
                                 CONTAINS_NOISE)

            N = getWindowLength(f0,
                                Fs,
                                windfunc='blackman',
                                error=LDF,
                                mainlobe_type='absolute')
            xdt, ydt = ADC(signal_rms, f0, Fs, N, CONTAINS_HARMONICS,
                           CONTAINS_NOISE)

            rms_true = round(
                true_signal(signal_rms, N, CONTAINS_HARMONICS, CONTAINS_NOISE),
                8)
            rms_sampled = round(rms_flat(ydt), 8)
            rms_delta = round(1e6 * (rms_true - rms_sampled), 2)

            cycles = N * f0 / Fs
            samples_per_cycles = N / cycles

            # ALIASING -------------------------------------------------------------------------------------------------
            aliased_freq = [1.0, 3.0, 5.0]

            for idx, harmonic in enumerate(aliased_freq):
                # https://ez.analog.com/partnerzone/fidus-partnerzone/m/file-uploads/212
                Fn = Fs / 2
                nyquist_zone = np.floor(f0 * harmonic / Fn) + 1

                if nyquist_zone % 2 == 0:
                    aliased_freq[idx] = (Fn - (f0 * harmonic % Fn)) / 1000
                else:
                    aliased_freq[idx] = (f0 * harmonic % Fn) / 1000

            # WINDOW ---------------------------------------------------------------------------------------------------
            w = np.blackman(N)

            # Calculate amplitude correction factor after windowing ----------------------------------------------------
            # https://stackoverflow.com/q/47904399/3382269
            amplitude_correction_factor = 1 / np.mean(w)

            # Calculate the length of the FFT --------------------------------------------------------------------------
            if (N % 2) == 0:
                # for even values of N: FFT length is (N / 2) + 1
                fft_length = int(N / 2) + 1
            else:
                # for odd values of N: FFT length is (N + 1) / 2
                fft_length = int((N + 2) / 2)

            # SPECTRAL -------------------------------------------------------------------------------------------------
            xf_fft = np.round(np.fft.fftfreq(N, d=1. / Fs), 6)
            yf_fft = (np.fft.fft(ydt * w) /
                      fft_length) * amplitude_correction_factor

            yf_rfft = yf_fft[:fft_length]
            xf_rfft = np.round(np.fft.rfftfreq(N, d=1. / Fs), 6)  # one-sided

            if aliased_freq[0] == 0:
                fh1 = f0
            else:
                fh1 = 1000 * aliased_freq[0]

            self.plot(fh1, xdt, ydt, fft_length, xf_rfft, yf_rfft)
            self.results_update(N, rms_true, rms_sampled, rms_delta,
                                np.floor(cycles), samples_per_cycles,
                                aliased_freq)
        except ValueError as e:
            self.popup_dialog(e)

    # ------------------------------------------------------------------------------------------------------------------
    def __do_plot_layout(self):
        self.ax1.set_title('SAMPLED TIMED SERIES DATA')
        self.ax1.set_xlabel('TIME (ms)')
        self.ax1.set_ylabel('AMPLITUDE')
        self.ax2.set_title('DIGITIZED WAVEFORM SPECTRAL RESPONSE')
        self.ax2.set_xlabel('FREQUENCY (kHz)')
        self.ax2.set_ylabel('MAGNITUDE (dB)')
        self.ax2.grid()
        self.figure.align_ylabels([self.ax1, self.ax2])
        self.figure.tight_layout()

    def plot(self, f0, xt, yt, fft_length, xf, yf):
        # TEMPORAL -----------------------------------------------------------------------------------------------------
        self.temporal.set_data(self.x * 1000, self.y)
        self.temporal_sampled.set_data(xt * 1000, yt)

        xt_left = 0
        xt_right = 4 / f0  # 4 periods are displayed by default
        self.ax1.set_xlim(left=xt_left * 1000, right=xt_right * 1000)

        # SPECTRAL -----------------------------------------------------------------------------------------------------
        if NORMALIZE_FFT:
            yf_peak = max(abs(yf))
            self.spectral.set_data(xf / 1000,
                                   20 * np.log10(np.abs(yf / yf_peak)))
            self.ax2.set_ylabel('MAGNITUDE (dB)')
        else:
            self.spectral.set_data(xf / 1000, np.abs(yf) / np.sqrt(2))
            self.ax2.set_ylabel('Amplitude (Vrms)')
        try:
            self.ax2.relim()  # recompute the ax.dataLim
        except ValueError:
            print(
                f'Are the lengths of xt: {len(xf)} and yt: {len(yf)} mismatched?'
            )
            raise
        self.ax2.autoscale()

        xf_left = 0
        xf_right = xf[fft_length - 1]
        self.ax2.set_xlim(left=xf_left / 1000, right=xf_right / 1000)

        # REDRAW PLOT --------------------------------------------------------------------------------------------------
        self.plot_redraw()

    def plot_redraw(self):
        try:
            self.ax1.relim()  # recompute the ax.dataLim
        except ValueError:
            xt_length = len(self.ax1.get_xdata())
            yt_length = len(self.ax1.get_ydata())
            print(
                f'Are the lengths of xt: {xt_length} and yt: {yt_length} mismatched?'
            )
            raise
        self.ax1.margins(x=0)
        self.ax1.autoscale(axis='y')

        # UPDATE PLOT FEATURES -----------------------------------------------------------------------------------------
        self.figure.tight_layout()

        self.toolbar.update()  # Not sure why this is needed - ADS
        self.canvas.draw()
        self.canvas.flush_events()

    def results_update(self, N, rms_true, rms_sampled, rms_delta, cycles,
                       samples_per_cycle, aliased_freq):
        self.text_ctrl_samples.SetLabelText(str(N))
        self.text_ctrl_rms_true.SetLabelText(str(rms_true))
        self.text_ctrl_rms_sampled.SetLabelText(str(rms_sampled))
        self.text_ctrl_rms_delta.SetLabelText(str(rms_delta))

        self.text_ctrl_cycles.SetLabelText(str(cycles))
        self.text_ctrl_samples_per_cycle.SetLabelText(str(samples_per_cycle))
        self.text_ctrl_aliased_freq01.SetLabelText(str(aliased_freq[0]))
        self.text_ctrl_aliased_freq02.SetLabelText(str(aliased_freq[1]))
        self.text_ctrl_aliased_freq03.SetLabelText(str(aliased_freq[2]))