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 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)')
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)')
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
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
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()
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()
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()
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()
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()
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
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()
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")
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')))
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
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()
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()
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]))