def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) printButton = tk.Button(self, text="Plot1", command=lambda: self.printMessage(canvas, a)) printButton.grid(row=0, column=0, sticky='w') f = Figure() f.subplots_adjust(left=0.03, bottom=0.07, right=0.98, top=0.97, wspace=0, hspace=0) a = f.add_subplot(111) axLeft = f.add_axes([0.2, 0.5, 0.6, 0.03], facecolor='b') axRight = f.add_axes([0.2, 0.1, 0.6, 0.03], facecolor='r') # axLeft = plt.axes([0.35, 0.15, 0.65, 0.03], facecolor=axcolor) # axRight = plt.axes([0.25, 0.05, 0.65, 0.03], facecolor='r') sLeft = Slider(axLeft, 'Time', 0, 30, valinit=0) sRight = Slider(axRight, 'Time', 0, 30, valinit=0, color='b') axLeft.clear() axRight.clear() canvas = FigureCanvasTkAgg(f, self) canvas.get_tk_widget().grid(row=1) canvas._tkcanvas.config(bg='blue', width=50, height=50) canvas.show() canvas.blit()
class StripChartWdg(tkinter.Frame): """A widget to changing values in real time as a strip chart Usage Hints: - For each variable quantity to display: - Call addLine once to specify the quantity - Call addPoint for each new data point you wish to display - For each constant line (e.g. limit) to display call addConstantLine - To make sure a plot includes one or two y values (e.g. 0 or a range of values) call showY - To manually scale a Y axis call setYLimits (by default all y axes are autoscaled). - All supplied times are POSIX timestamps (e.g. as supplied by time.time()). You may choose the kind of time displayed on the time axis (e.g. UTC or local time) using cnvTimeFunc and the format of that time using dateFormat. Known Issues: matplotlib's defaults present a number of challenges for making a nice strip chart display. Some issues and manual solutions are discussed in the main file's document string. Potentially Useful Attributes: - canvas: the matplotlib FigureCanvas - figure: the matplotlib Figure - subplotArr: list of subplots, from top to bottom; each is a matplotlib Subplot object, which is basically an Axes object but specialized to live in a rectangular grid - xaxis: the x axis shared by all subplots """ def __init__(self, master, timeRange = 3600, numSubplots = 1, width = 8, height = 2, showGrid = True, dateFormat = "%H:%M:%S", updateInterval = None, cnvTimeFunc = None, ): """Construct a StripChartWdg with the specified time range Inputs: - master: Tk parent widget - timeRange: range of time displayed (seconds) - width: width of graph in inches - height: height of graph in inches - numSubplots: the number of subplots - showGrid: if True a grid is shown - dateFormat: format for major axis labels, using time.strftime format - updateInterval: now often the time axis is updated (seconds); if None a value is calculated - cnvTimeFunc: a function that takes a POSIX timestamp (e.g. time.time()) and returns matplotlib days; typically an instance of TimeConverter; defaults to TimeConverter(useUTC=False) """ tkinter.Frame.__init__(self, master) self._timeRange = timeRange self._isVisible = self.winfo_ismapped() self._isFirst = True if updateInterval is None: updateInterval = max(0.1, min(5.0, timeRange / 2000.0)) self.updateInterval = float(updateInterval) # print "updateInterval=", self.updateInterval if cnvTimeFunc is None: cnvTimeFunc = TimeConverter(useUTC=False) self._cnvTimeFunc = cnvTimeFunc # how many time axis updates occur before purging old data self._maxPurgeCounter = max(1, int(0.5 + (5.0 / self.updateInterval))) self._purgeCounter = 0 self.figure = matplotlib.figure.Figure(figsize=(width, height), frameon=True) self.canvas = FigureCanvasTkAgg(self.figure, self) self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news") self.canvas.mpl_connect('draw_event', self._handleDrawEvent) self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) bottomSubplot = self.figure.add_subplot(numSubplots, 1, numSubplots) self.subplotArr = [self.figure.add_subplot(numSubplots, 1, n+1, sharex=bottomSubplot) \ for n in range(numSubplots-1)] + [bottomSubplot] if showGrid: for subplot in self.subplotArr: subplot.grid(True) self.xaxis = bottomSubplot.xaxis bottomSubplot.xaxis_date() self.xaxis.set_major_formatter(matplotlib.dates.DateFormatter(dateFormat)) # dictionary of constant line name: (matplotlib Line2D, matplotlib Subplot) self._constLineDict = dict() for subplot in self.subplotArr: subplot._scwLines = [] # a list of contained _Line objects; # different than the standard lines property in that: # - lines contains Line2D objects # - lines contains constant lines as well as data lines subplot._scwBackground = None # background for animation subplot.label_outer() # disable axis labels on all but the bottom subplot subplot.set_ylim(auto=True) # set auto scaling for the y axis self.bind("<Map>", self._handleMap) self.bind("<Unmap>", self._handleUnmap) self._timeAxisTimer = Timer() self._updateTimeAxis() def addConstantLine(self, y, subplotInd=0, **kargs): """Add a new constant to plot Inputs: - y: value of constant line - subplotInd: index of subplot - All other keyword arguments are sent to the matplotlib Line2D constructor to control the appearance of the data. See addLine for more information. """ subplot = self.subplotArr[subplotInd] line2d = subplot.axhline(y, **kargs) yMin, yMax = subplot.get_ylim() if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y <= yMax): subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) return line2d def addLine(self, subplotInd=0, **kargs): """Add a new quantity to plot Inputs: - subplotInd: index of subplot - All other keyword arguments are sent to the matplotlib Line2D constructor to control the appearance of the data. Useful arguments include: - label: name of line (displayed in a Legend) - color: color of line - linestyle: style of line (defaults to a solid line); "" for no line, "- -" for dashed, etc. - marker: marker shape, e.g. "+" Please do not attempt to control other sorts of line properties, such as its data. Arguments to avoid include: animated, data, xdata, ydata, zdata, figure. """ subplot = self.subplotArr[subplotInd] return _Line( subplot = subplot, cnvTimeFunc = self._cnvTimeFunc, wdg = self, **kargs) def clear(self): """Clear data in all non-constant lines """ for subplot in self.subplotArr: for line in subplot._scwLines: line.clear() def getDoAutoscale(self, subplotInd=0): return self.subplotArr[subplotInd].get_autoscaley_on() def removeLine(self, line): """Remove an existing line added by addLine or addConstantLine Raise an exception if the line is not found """ if isinstance(line, _Line): # a _Line object needs to be removed from _scwLines as well as the subplot line2d = line.line2d subplot = line.subplot subplot._scwLines.remove(line) else: # a constant line is just a matplotlib Line2D instance line2d = line subplot = line.axes subplot.lines.remove(line2d) if subplot.get_autoscaley_on(): subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) self.canvas.draw() def setDoAutoscale(self, doAutoscale, subplotInd=0): """Turn autoscaling on or off for the specified subplot You can also turn off autoscaling by calling setYLimits. """ doAutoscale = bool(doAutoscale) subplot = self.subplotArr[subplotInd] subplot.set_ylim(auto=doAutoscale) if doAutoscale: subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) def setYLimits(self, minY, maxY, subplotInd=0): """Set y limits for the specified subplot and disable autoscaling. Note: if you want to autoscale with a minimum range, use showY. """ self.subplotArr[subplotInd].set_ylim(minY, maxY, auto=False) def showY(self, y0, y1=None, subplotInd=0): """Specify one or two values to always show in the y range. Inputs: - subplotInd: index of subplot - y0: first y value to show - y1: second y value to show; None to omit Warning: setYLimits overrides this method (but the values are remembered in case you turn autoscaling back on). """ subplot = self.subplotArr[subplotInd] yMin, yMax = subplot.get_ylim() if y1 is not None: yList = [y0, y1] else: yList = [y0] doRescale = False for y in yList: subplot.axhline(y, linestyle=" ") if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y <= yMax): doRescale = True if doRescale: subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) def _handleDrawEvent(self, event=None): """Handle draw event """ # print "handleDrawEvent" for subplot in self.subplotArr: subplot._scwBackground = self.canvas.copy_from_bbox(subplot.bbox) for line in subplot._scwLines: subplot.draw_artist(line.line2d) self.canvas.blit(subplot.bbox) def _handleMap(self, evt): """Handle map event (widget made visible) """ self._isVisible = True self._handleDrawEvent() self._updateTimeAxis() def _handleUnmap(self, evt): """Handle unmap event (widget made not visible) """ self._isVisible = False def _updateTimeAxis(self): """Update the time axis; calls itself """ tMax = time.time() + self.updateInterval tMin = tMax - self._timeRange minMplDays = self._cnvTimeFunc(tMin) maxMplDays = self._cnvTimeFunc(tMax) self._purgeCounter = (self._purgeCounter + 1) % self._maxPurgeCounter doPurge = self._purgeCounter == 0 if doPurge: for subplot in self.subplotArr: for line in subplot._scwLines: line._purgeOldData(minMplDays) if self._isVisible or self._isFirst: for subplot in self.subplotArr: subplot.set_xlim(minMplDays, maxMplDays) if doPurge: if subplot.get_autoscaley_on(): # since data is being purged the y limits may have changed subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) self._isFirst = False self.canvas.draw() self._timeAxisTimer.start(self.updateInterval, self._updateTimeAxis)
ax.grid(which='both', linestyle='--') ax.set_xlabel("Time (s)") line, = ax.plot(evo.time, evo.data) ax.set_ylim([0, 100]) ax.get_xaxis().set_animated(True) #ax.get_yaxis().set_animated(True) line.set_animated(True) canvas.draw() background = canvas.copy_from_bbox(fig.bbox) # now redraw and blit ax.draw_artist(ax.get_xaxis()) ax.draw_artist(line) canvas.blit(ax.clipbox) while True: evo.update() line.set_xdata(evo.time) line.set_ydata(evo.getData()) ax.set_xlim([evo.time[0], evo.time[-1]]) #ax.set_ylim([max(evo.data),min(evo.data)]) # restore the background, draw animation,blit canvas.restore_region(background) ax.draw_artist(ax.get_xaxis()) #ax.draw_artist(ax.get_yaxis()) ax.draw_artist(line) canvas.blit(ax.clipbox)
class StripChartWdg(tkinter.Frame): """A widget to changing values in real time as a strip chart Usage Hints: - For each variable quantity to display: - Call addLine once to specify the quantity - Call addPoint for each new data point you wish to display - For each constant line (e.g. limit) to display call addConstantLine - To make sure a plot includes one or two y values (e.g. 0 or a range of values) call showY - To manually scale a Y axis call setYLimits (by default all y axes are autoscaled). - All supplied times are POSIX timestamps (e.g. as supplied by time.time()). You may choose the kind of time displayed on the time axis (e.g. UTC or local time) using cnvTimeFunc and the format of that time using dateFormat. Known Issues: matplotlib's defaults present a number of challenges for making a nice strip chart display. Some issues and manual solutions are discussed in the main file's document string. Potentially Useful Attributes: - canvas: the matplotlib FigureCanvas - figure: the matplotlib Figure - subplotArr: list of subplots, from top to bottom; each is a matplotlib Subplot object, which is basically an Axes object but specialized to live in a rectangular grid - xaxis: the x axis shared by all subplots """ def __init__( self, master, timeRange=3600, numSubplots=1, width=8, height=2, showGrid=True, dateFormat="%H:%M:%S", updateInterval=None, cnvTimeFunc=None, ): """Construct a StripChartWdg with the specified time range Inputs: - master: Tk parent widget - timeRange: range of time displayed (seconds) - width: width of graph in inches - height: height of graph in inches - numSubplots: the number of subplots - showGrid: if True a grid is shown - dateFormat: format for major axis labels, using time.strftime format - updateInterval: now often the time axis is updated (seconds); if None a value is calculated - cnvTimeFunc: a function that takes a POSIX timestamp (e.g. time.time()) and returns matplotlib days; typically an instance of TimeConverter; defaults to TimeConverter(useUTC=False) """ tkinter.Frame.__init__(self, master) self._timeRange = timeRange self._isVisible = self.winfo_ismapped() self._isFirst = True if updateInterval is None: updateInterval = max(0.1, min(5.0, timeRange / 2000.0)) self.updateInterval = float(updateInterval) # print "updateInterval=", self.updateInterval if cnvTimeFunc is None: cnvTimeFunc = TimeConverter(useUTC=False) self._cnvTimeFunc = cnvTimeFunc # how many time axis updates occur before purging old data self._maxPurgeCounter = max(1, int(0.5 + (5.0 / self.updateInterval))) self._purgeCounter = 0 self.figure = matplotlib.figure.Figure(figsize=(width, height), frameon=True) self.canvas = FigureCanvasTkAgg(self.figure, self) self.canvas.get_tk_widget().grid(row=0, column=0, sticky="news") self.canvas.mpl_connect('draw_event', self._handleDrawEvent) self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) bottomSubplot = self.figure.add_subplot(numSubplots, 1, numSubplots) self.subplotArr = [self.figure.add_subplot(numSubplots, 1, n+1, sharex=bottomSubplot) \ for n in range(numSubplots-1)] + [bottomSubplot] if showGrid: for subplot in self.subplotArr: subplot.grid(True) self.xaxis = bottomSubplot.xaxis bottomSubplot.xaxis_date() self.xaxis.set_major_formatter( matplotlib.dates.DateFormatter(dateFormat)) # dictionary of constant line name: (matplotlib Line2D, matplotlib Subplot) self._constLineDict = dict() for subplot in self.subplotArr: subplot._scwLines = [] # a list of contained _Line objects; # different than the standard lines property in that: # - lines contains Line2D objects # - lines contains constant lines as well as data lines subplot._scwBackground = None # background for animation subplot.label_outer( ) # disable axis labels on all but the bottom subplot subplot.set_ylim(auto=True) # set auto scaling for the y axis self.bind("<Map>", self._handleMap) self.bind("<Unmap>", self._handleUnmap) self._timeAxisTimer = Timer() self._updateTimeAxis() def addConstantLine(self, y, subplotInd=0, **kargs): """Add a new constant to plot Inputs: - y: value of constant line - subplotInd: index of subplot - All other keyword arguments are sent to the matplotlib Line2D constructor to control the appearance of the data. See addLine for more information. """ subplot = self.subplotArr[subplotInd] line2d = subplot.axhline(y, **kargs) yMin, yMax = subplot.get_ylim() if subplot.get_autoscaley_on() and numpy.isfinite(y) and not (yMin <= y <= yMax): subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) return line2d def addLine(self, subplotInd=0, **kargs): """Add a new quantity to plot Inputs: - subplotInd: index of subplot - All other keyword arguments are sent to the matplotlib Line2D constructor to control the appearance of the data. Useful arguments include: - label: name of line (displayed in a Legend) - color: color of line - linestyle: style of line (defaults to a solid line); "" for no line, "- -" for dashed, etc. - marker: marker shape, e.g. "+" Please do not attempt to control other sorts of line properties, such as its data. Arguments to avoid include: animated, data, xdata, ydata, zdata, figure. """ subplot = self.subplotArr[subplotInd] return _Line(subplot=subplot, cnvTimeFunc=self._cnvTimeFunc, wdg=self, **kargs) def clear(self): """Clear data in all non-constant lines """ for subplot in self.subplotArr: for line in subplot._scwLines: line.clear() def getDoAutoscale(self, subplotInd=0): return self.subplotArr[subplotInd].get_autoscaley_on() def removeLine(self, line): """Remove an existing line added by addLine or addConstantLine Raise an exception if the line is not found """ if isinstance(line, _Line): # a _Line object needs to be removed from _scwLines as well as the subplot line2d = line.line2d subplot = line.subplot subplot._scwLines.remove(line) else: # a constant line is just a matplotlib Line2D instance line2d = line subplot = line.axes subplot.lines.remove(line2d) if subplot.get_autoscaley_on(): subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) self.canvas.draw() def setDoAutoscale(self, doAutoscale, subplotInd=0): """Turn autoscaling on or off for the specified subplot You can also turn off autoscaling by calling setYLimits. """ doAutoscale = bool(doAutoscale) subplot = self.subplotArr[subplotInd] subplot.set_ylim(auto=doAutoscale) if doAutoscale: subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) def setYLimits(self, minY, maxY, subplotInd=0): """Set y limits for the specified subplot and disable autoscaling. Note: if you want to autoscale with a minimum range, use showY. """ self.subplotArr[subplotInd].set_ylim(minY, maxY, auto=False) def showY(self, y0, y1=None, subplotInd=0): """Specify one or two values to always show in the y range. Inputs: - subplotInd: index of subplot - y0: first y value to show - y1: second y value to show; None to omit Warning: setYLimits overrides this method (but the values are remembered in case you turn autoscaling back on). """ subplot = self.subplotArr[subplotInd] yMin, yMax = subplot.get_ylim() if y1 is not None: yList = [y0, y1] else: yList = [y0] doRescale = False for y in yList: subplot.axhline(y, linestyle=" ") if subplot.get_autoscaley_on() and numpy.isfinite(y) and not ( yMin <= y <= yMax): doRescale = True if doRescale: subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) def _handleDrawEvent(self, event=None): """Handle draw event """ # print "handleDrawEvent" for subplot in self.subplotArr: subplot._scwBackground = self.canvas.copy_from_bbox(subplot.bbox) for line in subplot._scwLines: subplot.draw_artist(line.line2d) self.canvas.blit(subplot.bbox) def _handleMap(self, evt): """Handle map event (widget made visible) """ self._isVisible = True self._handleDrawEvent() self._updateTimeAxis() def _handleUnmap(self, evt): """Handle unmap event (widget made not visible) """ self._isVisible = False def _updateTimeAxis(self): """Update the time axis; calls itself """ tMax = time.time() + self.updateInterval tMin = tMax - self._timeRange minMplDays = self._cnvTimeFunc(tMin) maxMplDays = self._cnvTimeFunc(tMax) self._purgeCounter = (self._purgeCounter + 1) % self._maxPurgeCounter doPurge = self._purgeCounter == 0 if doPurge: for subplot in self.subplotArr: for line in subplot._scwLines: line._purgeOldData(minMplDays) if self._isVisible or self._isFirst: for subplot in self.subplotArr: subplot.set_xlim(minMplDays, maxMplDays) if doPurge: if subplot.get_autoscaley_on(): # since data is being purged the y limits may have changed subplot.relim() subplot.autoscale_view(scalex=False, scaley=True) self._isFirst = False self.canvas.draw() self._timeAxisTimer.start(self.updateInterval, self._updateTimeAxis)
class App(Tk): def __init__(self): Tk.__init__(self) self.title("Baja GUI") self.grid() cw = 1 # cell width ch = 2 cr = self.winfo_screenwidth() / 32 print(self.winfo_screenheight()) print(self.winfo_screenwidth()) # Root level frames self.controlFrame = Frame(self, bg='grey', borderwidth=0, width=self.winfo_screenwidth(), height=(self.winfo_screenheight()/4)) self.displayFrame = Frame(self, bg='grey', borderwidth=0) # Serial Control Frame self.serialFrame = Frame(self.controlFrame, borderwidth=0) self.COMPort = StringVar() self.COMPortTextBox = Entry(self.serialFrame, textvariable = self.COMPort) self.COMPort.set('/dev/ttyUSB0') self.COMBaud = StringVar() self.COMBaudTextBox = Entry(self.serialFrame, textvariable = self.COMBaud) self.COMBaud.set('9600') self.COMButton = Button(self.serialFrame, text = "Start Serial", command = self.StartSerial) self.SerialSendButton = Button(self.serialFrame, text = "Send to Board", command = self.SendToSerial) self.SerialSendToDriverButton = Button(self.serialFrame, text = "Send to Driver", command = self.SendToDriver) self.SerialSendTextBox = Entry(self.serialFrame) # Extra Buttons self.SerialStopButton = Button(self.serialFrame, text = "Stop Serial", command = self.StopSerial) self.comeBackToPitNowButton = Button(self.controlFrame, text = "Pit Now", command = self.PitNow) self.comeBackToPitNextLapButton = Button(self.controlFrame, text = "Pit Next Lap", command = self.PitNextLap) # Informative Things self.dataBarFrame = Frame(self.controlFrame) self.numActiveThreads = StringVar() self.numThreadsLabel = Label(self.dataBarFrame, textvariable = self.numActiveThreads) self.numActiveThreads.set('Threads\n' + str(threading.enumerate().__len__())) self.numThreadsLabel.grid(column = 0, row = 0) self.counter = StringVar() self.counterLabel = Label(self.dataBarFrame, textvariable = self.counter) self.counter.set('Counter\n' + 'N/A') self.counterLabel.grid(column = 1, row = 0) self.gpsDate = StringVar() self.gpsDateLabel = Label(self.dataBarFrame, textvariable = self.gpsDate) self.gpsDate.set('Date\n' + '010100') self.gpsDateLabel.grid(column = 2, row = 0) self.gpsTime = StringVar() self.gpsTimeLabel = Label(self.dataBarFrame, textvariable = self.gpsTime) self.gpsTime.set('Time\n' + '00000000') self.gpsTimeLabel.grid(column = 3, row = 0) self.numSats = StringVar() self.numSatsLabel = Label(self.dataBarFrame, textvariable = self.numSats) self.numSats.set('Sats\n' + 'NSA') self.numSatsLabel.grid(column = 4, row = 0) self.currentLatitude = StringVar() self.latitudeLabel = Label(self.dataBarFrame, textvariable = self.currentLatitude) self.currentLatitude.set("Latitude\n" + 'Unknown') self.latitudeLabel.grid(column = 5, row = 0) self.currentLongitude = StringVar() self.longitudeLabel = Label(self.dataBarFrame, textvariable = self.currentLongitude) self.currentLongitude.set("Longitude\n" + 'Unknown') self.longitudeLabel.grid(column = 6, row = 0) self.currentSpeed = StringVar() self.speedLabel = Label(self.dataBarFrame, textvariable=self.currentSpeed) self.currentSpeed.set("Speed\n" + "86") self.speedLabel.grid(column = 7, row = 0) self.currentDashTemp = StringVar() self.dashTempLabel = Label(self.dataBarFrame, textvariable=self.currentDashTemp) self.currentDashTemp.set("Dash Temp\n" + "N/A") self.dashTempLabel.grid(column = 8, row = 0) self.batteryVoltage = StringVar() self.batteryVoltageLabel = Label(self.dataBarFrame, textvariable = self.batteryVoltage) self.batteryVoltage.set("BATT\n" + "N/A") self.batteryVoltageLabel.grid(column = 9, row = 0) self.xbeeSignalStrength = StringVar() self.xbeeSignalStrengthLabel = Label(self.dataBarFrame, textvariable = self.xbeeSignalStrength) self.xbeeSignalStrength.set("XBee\n" + "N/A") self.xbeeSignalStrengthLabel.grid(column = 10, row = 0) self.currentFrontBrakeAvg = StringVar() self.frontBrakeAvgLabel = Label(self.dataBarFrame, textvariable = self.currentFrontBrakeAvg) self.currentFrontBrakeAvg.set("Front Brake Avg\n" + "N/A") self.frontBrakeAvgLabel.grid(column = 11, row = 0) self.currentFrontBrakeMax = StringVar() self.frontBrakeMaxLabel = Label(self.dataBarFrame, textvariable = self.currentFrontBrakeMax) self.currentFrontBrakeMax.set("Front Brake Max\n" + "N/A") self.frontBrakeMaxLabel.grid(column = 12, row = 0) self.currentRearBrakeAvg = StringVar() self.rearBrakeAvgLabel = Label(self.dataBarFrame, textvariable = self.currentRearBrakeAvg) self.currentRearBrakeAvg.set("Rear Brake Avg\n" + "N/A") self.rearBrakeAvgLabel.grid(column = 13, row = 0) self.currentRearBrakeMax = StringVar() self.rearBrakeMaxLabel = Label(self.dataBarFrame, textvariable = self.currentRearBrakeMax) self.currentRearBrakeMax.set("Rear Brake Max\n" + "N/A") self.rearBrakeMaxLabel.grid(column = 14, row = 0) self.currentSteeringAvg = StringVar() self.steeringAvgLabel = Label(self.dataBarFrame, textvariable = self.currentSteeringAvg) self.currentSteeringAvg.set("Steering\n" + "Yes") self.steeringAvgLabel.grid(column = 15, row = 0) self.buttonPressed = StringVar() self.buttonPressedLabel = Label(self.dataBarFrame, textvariable = self.buttonPressed, borderwidth = 1) self.buttonPressed.set("Button\n" + "No") self.buttonPressedLabel.grid(column = 16, row = 0) self.currentPrimaryRPM = StringVar() self.currentSecondaryRPM = StringVar() # Accelerometer self.accX = [0] self.maxX = [0] self.accY = [0] self.maxY = [0] self.accZ = [0] self.maxZ = [0] # self.rearTemp = [0] self.listbox = Listbox(self.controlFrame) # Set to full screen self.attributes('-fullscreen', True) # Bind <Return> Keypress to text boxes self.SerialSendTextBox.bind("<Return>", self.SendOnReturn) self.COMBaudTextBox.bind("<Return>", self.StartSerialOnReturn) # Pack Serial Controls into Serial Frame self.SerialSendTextBox.grid(column = 0, row=0, sticky=EW) self.SerialSendButton.grid(column = 2, row=0) self.SerialSendToDriverButton.grid(column = 3, row = 0) self.COMPortTextBox.grid(column = 4, row=0, sticky = E) self.COMBaudTextBox.grid(column=6, row=0, sticky = E) self.COMButton.grid(column = 8, row=0, sticky = NE) self.SerialStopButton.grid(row = 0, column = 9, sticky=NE) # Pack Control Widgets into Control Frame self.serialFrame.grid(row = 0, column=0, columnspan=12, sticky=EW) self.comeBackToPitNowButton.grid(row=0, column=10, sticky=NSEW) self.comeBackToPitNextLapButton.grid(row=1, column=10, sticky=NSEW) self.dataBarFrame.grid(row = 1, column = 0, columnspan = 10, sticky = EW) self.listbox.grid(row = 2, column = 0, columnspan=10, sticky = EW) # Pack Control Frame into root self.controlFrame.grid(row = 0, sticky=EW) # Top Level Display Frames self.leftDisplayFrame = Frame(self.displayFrame, bg='grey', borderwidth=0, width=(self.winfo_screenwidth()/2)) self.rightDisplayFrame = Frame(self.displayFrame, bg='grey', borderwidth=0, width=(self.winfo_screenwidth()/2)) # Inside Left Display Frames self.topLeftDisplayFrame = Frame(self.leftDisplayFrame, bg='grey', borderwidth=0, height=(self.winfo_screenheight()/4)) self.middleLeftDisplayFrame = Frame(self.leftDisplayFrame, bg='grey', borderwidth=0, height=(self.winfo_screenheight()/4)) self.bottomLeftDisplayFrame = Frame(self.leftDisplayFrame, bg='grey', borderwidth=0, height=(self.winfo_screenheight()/4)) # Inside Top Left Display Frames # Inside Middle Left Display Frames self.brakeFrame = Frame(self.middleLeftDisplayFrame, borderwidth=0) # Inside Bottom Left Display Frames self.driveTrainTempFrame = Frame(self.bottomLeftDisplayFrame, borderwidth=0) self.driveTrainRPMFrame = Frame(self.bottomLeftDisplayFrame, borderwidth=0) # Inside Rigt Display Frames self.shieldTempFrame = Frame(self.rightDisplayFrame, borderwidth=0, bg='grey', height=(self.winfo_screenheight()/8)) self.shieldHealthFrame = Frame(self.rightDisplayFrame, borderwidth=0, bg='grey') # Construct Items for Display Frame self.gpsFigure, self.gpsAxes = plt.subplots(figsize=(2*ch,2*ch), dpi=cr*2) self.speedFigure, self.speedAxes = plt.subplots(figsize=(2*ch,2*ch), dpi=cr) self.rpmFigure, self.rpmAxes = plt.subplots(figsize=(ch,ch), dpi=cr) self.dashTempFigure, self.dashTempAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.rearTempFigure, self.rearTempAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.gearTempFigure, self.gearTempAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.cvtTempFigure, self.cvtTempAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.battFigure, self.battAxes = plt.subplots(figsize=(ch,ch), dpi=cr) self.frontBrakeFigure, self.frontBrakeAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.rearBrakeFigure, self.rearBrakeAxes = plt.subplots(figsize=(2*ch,ch), dpi=cr) self.steeringFigure, self.steeringAxes = plt.subplots(figsize=(ch,2*ch), dpi=cr) self.buttonFigure, self.buttonGraph = plt.subplots(figsize=(ch,ch), dpi=cr) self.accFigure, self.accAxes = plt.subplots(figsize=(2*ch,2*ch), dpi=cr) self.zFigure, self.zAxes = plt.subplots(figsize=(ch, 2*ch), dpi=cr) # GPS Axes Setup self.gpsFigure.patch.set_visible(False) self.gpsAxes.set_title('GPS') #self.gpsAxes.set_xlim(-110.953,-110.952) self.gpsAxes.set_xlim(-118823207 - 3000,-118823207 + 3000) self.gpsAxes.set_ylim(34749085 - 3000,34749085 + 3000) self.gpsAxes.hold(True) self.gpsCanvas = FigureCanvasTkAgg(self.gpsFigure, master=self.rightDisplayFrame) self.gpsCanvas.show() # speed Axes Setup self.speedFigure.patch.set_visible(False) self.speedAxes.set_title('Speed') self.speedAxes.set_xlim(0,100) self.speedAxes.set_ylim(0,50) self.speedAxes.hold(True) self.speedCanvas = FigureCanvasTkAgg(self.speedFigure, master=self.topLeftDisplayFrame) self.speedCanvas.show() # Speed Textbox Setup # Dash Temperature Axes Setup self.dashTempFigure.patch.set_visible(False) self.dashTempAxes.set_title('Dash Temperature') # self.ten_wide = [0,1,2,3,4,5,6,7,8,9] self.dashTempAxes.set_xlim(0,100) # self.dashTempAxes.autoscale(enable=True, axis='x') self.dashTempAxes.set_ylim(0,100) self.dashTempAxes.hold(True) self.dashTempCanvas = FigureCanvasTkAgg(self.dashTempFigure, master=self.shieldTempFrame) self.dashTempCanvas.show() # self.dashTempCanvas.draw() # Rear Temperature Axes Setup self.rearTempFigure.patch.set_visible(False) self.rearTempAxes.set_title('Rear Temperature') self.rearTempAxes.set_xlim(0,500) self.rearTempAxes.set_ylim(0,100) self.rearTempAxes.hold(True) self.rearTempCanvas = FigureCanvasTkAgg(self.rearTempFigure, master=self.shieldTempFrame) self.rearTempCanvas.show() # # Gearbox Temperature Axes Setup self.gearTempFigure.patch.set_visible(False) self.gearTempAxes.set_title('Gearbox Temperature') self.gearTempAxes.set_xlim(0,500) self.gearTempAxes.set_ylim(0,100) self.gearTempAxes.hold(True) self.gearTempCanvas = FigureCanvasTkAgg(self.gearTempFigure, master=self.driveTrainTempFrame) self.gearTempCanvas.show() # # CVT Temperature Axes Setup self.cvtTempAxes.set_title('CVT Temperature') self.cvtTempAxes.set_xlim(0,500) self.cvtTempAxes.set_ylim(0,100) self.cvtTempAxes.hold(True) self.cvtTempCanvas = FigureCanvasTkAgg(self.cvtTempFigure, master=self.driveTrainTempFrame) self.cvtTempCanvas.show() # # Acceleration Axes Setup self.accFigure.patch.set_visible(False) self.accAxes.set_title('Acceleration 2-D') self.accAxes.set_xlim(-2, 2) self.accAxes.set_ylim(-2, 2) self.accAxes.hold(True) self.accCanvas = FigureCanvasTkAgg(self.accFigure, master=self.middleLeftDisplayFrame) self.accCanvas.show() self.zFigure.patch.set_visible(False) self.zAxes.set_title('Z-Acc') self.zAxes.set_xlim(-2, 2) self.zAxes.set_ylim(-2, 2) self.zAxes.hold(True) self.zCanvas = FigureCanvasTkAgg(self.zFigure, master=self.topLeftDisplayFrame) self.zCanvas.show() self.steeringFigure.patch.set_visible(False) self.steeringAxes.set_title('Steering') self.steeringAxes.set_xlim(-2, 2) self.steeringAxes.set_ylim(-2, 2) self.steeringAxes.hold(True) self.steeringCanvas = FigureCanvasTkAgg(self.steeringFigure, master=self.topLeftDisplayFrame) self.steeringCanvas.show() # Front Brake Axes Setup self.frontBrakeFigure.patch.set_visible(False) self.frontBrakeAxes.set_title('Front Brake Pressure') self.frontBrakeAxes.set_xlim(0,500) self.frontBrakeAxes.set_ylim(0,100) self.frontBrakeAxes.hold(True) self.frontBrakeCanvas = FigureCanvasTkAgg(self.frontBrakeFigure, master=self.brakeFrame) self.frontBrakeCanvas.show() # Rear Brake Axes Setup self.rearBrakeFigure.patch.set_visible(False) self.rearBrakeAxes.set_title('Rear Brake Pressure') self.rearBrakeAxes.set_xlim(0,500) self.rearBrakeAxes.set_ylim(0,100) self.rearBrakeAxes.hold(True) self.rearBrakeCanvas = FigureCanvasTkAgg(self.rearBrakeFigure, master=self.brakeFrame) self.rearBrakeCanvas.show() # Layout # Pack Brake Pressure self.frontBrakeCanvas.get_tk_widget().grid(column = 0, row=0, sticky=N) # self.frontBrakeCanvas._tkcanvas.grid(column=0, row=0, sticky=N) self.rearBrakeCanvas.get_tk_widget().grid(column=0, row=1, sticky=S) # self.rearBrakeCanvas._tkcanvas.grid(column=0, row=1, sticky=S) # Pack Drive Train RPM # Pack Drive Train Temp self.gearTempCanvas.get_tk_widget().grid(column = 0, row=0, sticky=W) # self.gearTempCanvas._tkcanvas.grid(column=0, row=0, sticky=W) self.cvtTempCanvas.get_tk_widget().grid(column=1, row=0, sticky=E) # self.cvtTempCanvas._tkcanvas.grid(column=1, row=0, sticky=E) # Pack stuff into top left # self.speedTextBox.grid(column=1, row=0, sticky=NSEW) self.zCanvas.get_tk_widget().grid(column=0, row=0, sticky=NSEW) self.steeringCanvas.get_tk_widget().grid(column=1, row=0, sticky=NSEW) self.speedCanvas.get_tk_widget().grid(column=2, row=0 , sticky=NSEW) # self.speedCanvas._tkcanvas.grid(column=2 , row=0 , sticky=NSEW) # Pack stuff into middle left self.accCanvas.get_tk_widget().grid(column=0 , row=1 , sticky=W ) # self.accCanvas._tkcanvas.grid(column=0 , row=1 , sticky=W ) self.brakeFrame.grid(column=1 , row=1 , sticky=E) # Pack stuff into bottom left self.driveTrainTempFrame.grid(column=0 , row=5 , sticky=N ) self.driveTrainRPMFrame.grid(column=0 , row=6 , sticky=S ) # Pack Left Display self.topLeftDisplayFrame.grid(column=0 , row=2 , sticky=NSEW ) self.middleLeftDisplayFrame.grid(column=0 , row=4 , sticky=NSEW ) self.bottomLeftDisplayFrame.grid(column=0 , row=6 , sticky=NSEW ) # Pack Shield Temp self.dashTempCanvas.get_tk_widget().grid(column=0 , row=5 ) # self.dashTempCanvas._tkcanvas.grid(column=0 , row=5 , sticky=W ) self.rearTempCanvas.get_tk_widget().grid(column=1 , row=5 ) # self.rearTempCanvas._tkcanvas.grid(column=1 , row=5 , sticky=E ) # Pack Shield Health # Pack stuff inside right display self.gpsCanvas.get_tk_widget().grid(column=4 , row=2 , sticky=NSEW ) # self.gpsCanvas._tkcanvas.grid(column=4 , row=2 , sticky=NSEW ) self.shieldTempFrame.grid(column=4 , row=5 , sticky=N ) self.shieldHealthFrame.grid(column=4 , row=6 , sticky=N ) # TODO: Button Press notification goes here... # Pack Display Frame self.leftDisplayFrame.grid(column=0 , row=3 , sticky=NS ) self.rightDisplayFrame.grid(column=5 , row=3 , sticky=NS ) # Pack Display Into Root self.displayFrame.grid(column=0 , row=2 , sticky=NSEW ) # Initialize Data Variables # Dash Data # self.counter.set(0) # Commented to preserve initial text value, left here to preserve variable order/index # self.gpsDate = 0 # self.gpsTime = 0 # self.numSats = 0 self.latitude = [32236562] self.longitude = [-110952322] self.speed = [0]*102 self.dashTemp = [0]*102 # self.batteryVoltage = 0 # self.xbeeSignalStrength = 0 self.frontBrakeAvg = [0] self.frontBrakeMax = [0] self.rearBrakeAvg = [0] self.rearBrakeMax = [0] self.steeringAvg = [0] # self.buttonPressed = 0 # Rear Board Data self.primaryRPM = 0 self.secondaryRPM = 0 # Accelerometer self.accX = [0] self.maxX = [0] self.accY = [0] self.maxY = [0] self.accZ = [0] self.maxZ = [0] # self.rearTemp = [0] self.hundred_array = list(range(0,102)) self.gpsBackground = self.gpsCanvas.copy_from_bbox(self.gpsAxes.bbox) self.speedBackground = self.speedCanvas.copy_from_bbox(self.speedAxes.bbox) self.dashTempBackground = self.dashTempCanvas.copy_from_bbox(self.dashTempAxes.bbox) self.gpsPlot = self.gpsAxes.plot(self.longitude, self.latitude, '-')[0] self.speedPlot = self.speedAxes.plot(self.hundred_array,self.speed)[0] self.dashTempPlot = self.dashTempAxes.plot(self.hundred_array, self.dashTemp)[0] # Create Queues for talking to serial line asyncronously self.rx = queue.Queue() self.tx = queue.Queue() def StartSerial(self): self.listbox.insert(0, "Baud Rate: " + self.COMBaud.get()) self.listbox.insert(0, "COM Port: " + self.COMPort.get()) self.numActiveThreads.set('Threads\n' + str(threading.enumerate().__len__())) if (threading.enumerate().__len__()!=1): self.serialThread.join() #print(threading.enumerate().__len__()) self.numActiveThreads.set('Threads\n' + str(threading.enumerate().__len__())) self.serialThread = SerialThread(self.COMPort, self.COMBaud, self.rx, self.tx) self.serialThread.start() self.numActiveThreads.set('Threads\n' + str(threading.enumerate().__len__())) self.process_serial() def StopSerial(self): self.listbox.insert(0, "Serial Stopped") if (threading.enumerate().__len__()!=1): self.serialThread.join() self.numActiveThreads.set('Threads\n' + str(threading.enumerate().__len__())) def PitNow(self): self.listbox.insert(0, "Pit Now") self.tx.put("<PIT NOW>\n") def PitNextLap(self): self.listbox.insert(0, "Pit Next Lap") self.tx.put("<PIT NEXT LAP>\n") def SendOnReturn(self, event): self.SendToSerial() def StartSerialOnReturn(self, event): self.StartSerial() def SendToSerial(self): text_contents = self.SerialSendTextBox.get() self.listbox.insert(0, text_contents) self.tx.put(text_contents) self.SerialSendTextBox.delete(0,END) def SendToDriver(self): text_contents = self.SerialSendTextBox.get() self.listbox.insert(0, text_contents) self.tx.put('<') self.tx.put(text_contents) self.tx.put('>') self.SerialSendTextBox.delete(0,END) def process_serial(self): text = StringVar() if self.rx.qsize(): # self.listbox.delete(0,END) try: dataString = self.rx.get() except Queue.Empty: pass self.listbox.insert(0, dataString) data = dataString.split(bytes(', ','UTF-8')) print(data.__len__()) if(data.__len__()==18 or data.__len__()==28): try: i = 0 # Dash Data self.counter.set('Counter\n' + str(int(data[0]))) i = 1 self.gpsDate.set('Date\n' + str(int(data[1]))) i = 2 self.gpsTime.set('Time\n' + str(int(data[2]))) i = 3 self.numSats.set('Sats\n' + str(int(data[3]))) if (float(data[4])/1000000 < 100 and float(data[5])/1000000 < 200): i = 4 self.latitude.append(int(data[4])) self.currentLatitude.set('Latitude\n' + str(int(data[4]))) i = 5 self.longitude.append(int(data[5])) self.currentLongitude.set('Longitude\n' + str(int(data[5]))) i = 6 self.speed.append(float(data[6])) self.currentSpeed.set('Speed\n' + str(float(data[6]))) self.currentSpeed.set('Speed\n' + str(float(data[6]))) i = 7 self.dashTemp.append(float(data[7])) self.currentDashTemp.set('Dash Temp\n' + str(float(data[7])) + 'C') i = 8 self.batteryVoltage.set('Batt\n' + str(int(data[8]))) i = 9 self.xbeeSignalStrength.set('XBee\n' + str(int(data[9]))) i = 10 self.frontBrakeAvg.append(float(data[10])) i = 11 self.frontBrakeMax.append(int(data[11])) i = 12 self.rearBrakeAvg.append(float(data[12])) i = 13 self.rearBrakeMax.append(int(data[13])) i = 14 self.steeringAvg.append(float(data[14])) i = 15 # self.buttonPressed = int(data[15]) i = 16 # # # Rear Board Data # self.primaryRPM = 0 # self.secondaryRPM = 0 # # # # Accelerometer # self.accX = [0] # self.maxX = [0] # self.accY = [0] # self.maxY = [0] # self.accZ = [0] # self.maxZ = [0] # # # # self.rearTemp = [0] except: print('Exception Raised!' + str(i)) pass self.gpsCanvas.restore_region(self.gpsBackground) self.gpsPlot.set_data(self.longitude[1:], self.latitude[1:]) self.gpsAxes.draw_artist(self.gpsPlot) self.gpsCanvas.blit(self.gpsAxes.bbox) self.speedCanvas.restore_region(self.speedBackground) self.speedPlot.set_data(self.hundred_array[-101:], self.speed[-101:]) self.speedAxes.draw_artist(self.speedPlot) self.speedCanvas.blit(self.speedAxes.bbox) self.dashTempCanvas.restore_region(self.dashTempBackground) self.dashTempPlot.set_data(self.hundred_array[-101:], self.dashTemp[-101:]) self.dashTempAxes.draw_artist(self.dashTempPlot) self.dashTempCanvas.blit(self.dashTempAxes.bbox) self.after(100, self.process_serial) # Graceful Exit def destroy(e): sys.exit()
class Annotator: def __init__(self, parent): self.parent = parent parent.title("Sound Annotation/Analyzation Tool") self.initUI() def initUI(self): self.labeldict = {} self.show = [] self.timeValues = [] self.stamp = -1 self.stampHistory = [] self.funclist = { "rms", "hfc", "complex", "complex_phase", "flux", "superflux", "noveltycurve", "CNNOnsetDetector", "RNNOnsetDetector", "modifiedKL", "weightedPhaseDev", "PhaseDev", "rectifiedComplexDomain", "ninos" } self.featurelist = [ "Audio", "rms", "spectralCentroid", "spectralRolloff", "zcr", "spectralEntropy", "spectralFlux", "StrongDecay", "stft" ] self.hopsizes = [] self.calculated_features_dict = {} self.calculated_features = [] self.calculated_featuresParams = [] self.defaultParams = { 'rms': (512, 1024, 'hann'), 'Audio': (), 'spectralCentroid': (512, 1024, 'hann'), 'spectralRolloff': (512, 1024, 'hann'), 'spectralFlux': (512, 1024, 'hann'), 'zcr': (512, 1024), 'spectralEntropy': (512, 1024, 'hann'), 'StrongDecay': (512, 1024), 'stft': (512, 1024) } self.chunk_size = 2048 self.mag = [] self.phase = [] self.sem = asyncio.Semaphore() # Info text self.info = StringVar() self.info.set("welcome") self.info_widget = Message(self.parent, textvariable=self.info, width=300) self.info_widget.grid(row=0, column=3, sticky=W + N + E + S, rowspan=2) # define options for opening file self.file_opt = options = {} options['defaultextension'] = '.wav' options['filetypes'] = [('All files', '.*'), ('Wav files', '.wav')] options['initialdir'] = os.getcwd() + "/sounds" if (options['initialdir'][-1] != '/'): options['initialdir'] += '/' # BUTTON TO SET DIRECTORY self.directory_button = Button(self.parent, text="Directory", command=self.set_directory) self.directory_button.grid(row=1, column=0, sticky=W) #TEXTBOX TO PRINT PATH OF THE SOUND FILE self.filelocation = Entry(self.parent) self.filelocation["width"] = 25 self.filelocation.grid(row=0, column=0, sticky=W, padx=10) self.filelocation.delete(0, END) self.filename = '' # initial file self.filelocation.insert(0, self.file_opt['initialdir'] + self.filename) #BUTTON TO BROWSE SOUND FILE self.open_file = Button(self.parent, text="Browse...", command=self.browse_file) self.open_file.grid(row=0, column=0, sticky=W, padx=(220, 6)) #BUTTON TO PLOT SOUND FILE self.preview = Button(self.parent, text="Plot", command=self.plot, bg="gray30", fg="white") self.preview.grid(row=0, column=0, sticky=W, padx=(300, 6)) #BUTTON TO draw stamps self.draw = Button(self.parent, text="Draw", command=self.drawAllStamps, bg="gray30", fg="white") self.draw.grid(row=1, column=0, sticky=W, padx=(420, 6)) # Dropdown Function Select self.funcname = StringVar() self.funcname.set("Select a function") self.fselect = OptionMenu(self.parent, self.funcname, *self.funclist) self.fselect.grid(row=1, column=2, sticky=W) # Button to Apply function self.afuncbtn = Button(self.parent, text="Apply", command=self.applyfunction, bg="gray30", fg="white") self.afuncbtn.grid(row=1, column=2, sticky=W, padx=(300, 6)) # Dropdown SHOW FEATURE self.featurename = StringVar() self.featurename.set("Audio") self.featurename.trace("w", self.changeparams) self.ftselect = OptionMenu(self.parent, self.featurename, *self.featurelist) self.ftselect.grid(row=0, column=2, sticky=W) # Entry for feature params self.featureParamsEntry = Entry(self.parent) self.featureParamsEntry.grid(row=0, column=2, sticky=W, padx=(150, 6)) self.featureParamsEntry.bind("<Enter>", self.updateinfo) # Button to show feature self.afuncbtn = Button(self.parent, text="Show", command=self.showfeature, bg="gray30", fg="white") self.afuncbtn.grid(row=0, column=2, sticky=W, padx=(300, 6)) # Button to send top feature to below plot self.sendbelowbtn = Button(self.parent, text="v", command=self.sendbelow) self.sendbelowbtn.grid(row=0, column=2, sticky=W, padx=(360, 6)) #BUTTON TO PLAY/STOP , shortcut: spacebar self.play_mode = tk.BooleanVar(self.parent, False) self.new_sound = tk.BooleanVar(self.parent, False) self.playbutton = Button(self.parent, text="Play", command=self.playsound) self.playbutton.grid(row=0, column=0, sticky=W, padx=(350, 6)) self.parent.bind("<space>", self.playsound) #BUTTON TO SAVE AND LOAD NEXT SOUND FILE self.saveload = Button(self.parent, text="Save& Load Next", command=self.saveAndNext) self.saveload.grid(row=0, column=1, sticky=W) #BUTTON TO ADD LABELS self.addlabel_button = Button(self.parent, text="Add/Manage Labels", command=self.addlabel_gui) self.addlabel_button.grid(row=0, column=0, sticky=W, padx=(420, 6)) # BUTTON TO DISCARD CURRENT ANNOTATIONS self.discardbutton = Button(self.parent, text="Discard", command=self.discard) self.discardbutton.grid(row=0, column=4, sticky=W) #BUTTON TO get next self.load_next = Button(self.parent, text="Next Sound", command=self.getNext) self.load_next.grid(row=1, column=0, sticky=W, padx=(220, 6)) # BUTTON TO QUIT self.quitbutton = Button(self.parent, text="Quit", command=self.quit) self.quitbutton.grid(row=3, column=4, sticky=W) # tickbox to autoload existing annotations self.ALoad = IntVar() self.autoload_annotations = Checkbutton(master=self.parent, text="Autoload Annotations?", variable=self.ALoad, onvalue=1, offvalue=0) self.autoload_annotations.grid(row=1, column=1, sticky=W) # tickbox to convert time to samples self.isTime = IntVar() self.timeorsamples = Checkbutton(master=self.parent, text="time?", variable=self.isTime, onvalue=1, offvalue=0) self.timeorsamples.grid(row=1, column=0, sticky=W, padx=(500, 6)) # tickbox to enable discarding labels self.DLabels = IntVar() self.discardlabels_check = Checkbutton(master=self.parent, text="Discard Labels?", variable=self.DLabels, onvalue=1, offvalue=0) self.discardlabels_check.grid(row=1, column=4, sticky=W) # init figure for plotting self.currentplot = 0 fig = plt.figure(figsize=(18, 7), dpi=100) self.mainplot = fig.add_subplot(211) self.secplot = fig.add_subplot(212) self.canvas = FigureCanvasTkAgg(fig, self.parent) self.canvaswidget = self.canvas.get_tk_widget() self.canvaswidget.grid(row=2, column=0, columnspan=5, sticky=W) toolbarFrame = Frame(master=self.parent) toolbarFrame.grid(row=3, column=0, columnspan=7) self.toolbar = NavigationToolbar2Tk(self.canvas, toolbarFrame) self.toolbar.update() self.canvas._tkcanvas.grid(row=2, column=0, columnspan=7, sticky=W) self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) self.cursor = self.mainplot.axvline(color="k", animated=True) self.cursor.set_xdata(0) self.colors = [ 'r', 'g', 'c', 'm', 'y', '#FFBD33', '#924A03', '#D00000', '#D000D0', '#6800D0', '#095549', 'b', 'r', 'r' ] # modification to original handler of 'release_zoom', "release_pan" and "_update_view" from NavigationToolbar2 # latest one is for forward, backward and home buttons self.release_zoom_orig = self.toolbar.release_zoom self.toolbar.release_zoom = self.new_release_zoom self.release_pan_orig = self.toolbar.release_pan self.toolbar.release_pan = self.new_release_pan self._update_view_orig = self.toolbar._update_view self.toolbar._update_view = self.new_update_view self.canvas.mpl_connect('toolbar_event', self.handle_toolbar) cid1 = self.canvas.mpl_connect("button_press_event", self.onclick) self.parent.bind("<Escape>", self.release_stamp) self.parent.bind("x", self.discard_last) self.p = pyaudio.PyAudio() self.parent.bind("<<playbackmove>>", self.playbackMove) def callbackstream(in_data, frame_count, time_info, status): self.sem.acquire() data = self.wf.readframes(frame_count) self.parent.event_generate("<<playbackmove>>", when="now") self.sem.release() return (data, pyaudio.paContinue) self._callbackstream = callbackstream def updateinfo(self, event, *args): caller = event.widget if str(caller) == '.!entry2': self.info.set(getattr(features, self.featurename.get()).__doc__) def applyfunction(self): values = getattr(libod, self.funcname.get())(self.filelocation.get()) if len(values) == 0: self.info.set("No detections") else: self.labeldict[len(self.labeldict)] = self.funcname.get() var = IntVar() var.set(1) self.show.append(var) self.timeValues.append([i * 44100 for i in values]) self.drawAllStamps() def set_directory(self): directory = tkFileDialog.askdirectory() + '/' self.file_opt['initialdir'] = directory def release_stamp(self, event=None): self.stamp = -1 self.info.set("Cursor") def discard_last(self, event=None ): # discard last annotation for current sound. button: X self.mainplot.lines.pop() self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) self.timeValues[self.stampHistory[-1]].pop() self.stampHistory.pop() def discard(self): # discard all annotations self.timeValues = [] if (self.DLabels.get() == True or self.ALoad.get() == True): self.labeldict = {} self.show = [] else: for i in range(len(self.labeldict)): self.timeValues.append([]) self.stamp = -1 self.stampHistory = [] self.mag = [] self.phase = [] self.hopsizes = [] self.calculated_features_dict = {} self.calculated_features = [] self.calculated_featuresParams = {} self.currentplot = 0 def label(self, event): self.info.set(self.labeldict[int(event.char) - 1] + " is selected") self.stamp = int(event.char) - 1 def addlabel(self): self.labeldict[len(self.labeldict)] = self.newlabel_entry.get() var = IntVar() var.set(1) self.show.append(var) self.parent.bind("%d" % len(self.labeldict), self.label) self.timeValues.append([]) def deselect(self): for cb in self.checkbuttons: cb.deselect() def addlabel_gui(self): frame_addlabel = Toplevel(master=self.parent) frame_addlabel.geometry( "%dx%d%+d%+d" % (400, 100 + 33 * len(self.labeldict), 200, 200)) frame_addlabel.title("Add Labels") self.newlabel_entry = Entry(frame_addlabel) self.newlabel_entry.grid(row=0, column=0) labels_text = "Current Labels:" w = Message(frame_addlabel, text=labels_text, width=150) w.grid(row=1, column=0) labels_text = "Display?" w = Message(frame_addlabel, text=labels_text, width=150) w.grid(row=1, column=1) self.checkbuttons = [] for i in range(len(self.labeldict)): labels_text = '%d' % (i + 1) + '. ' + self.labeldict[i] w = Message(frame_addlabel, text=labels_text, width=150) w.grid(row=i + 2, column=0) check = Checkbutton(master=frame_addlabel, text="", variable=self.show[i], onvalue=1, offvalue=0) check.grid(row=i + 2, column=1, sticky=W) self.checkbuttons.append(check) addbutton = Button(frame_addlabel, text="Add", command=self.addlabel) addbutton.grid(row=0, column=1, sticky=W) togglebutton = Button(frame_addlabel, text="Deselect", command=self.deselect) togglebutton.grid(row=1, column=2, sticky=W) def quit(self): try: self.stream.close() self.wf.close() self.p.terminate() except: print("no audio init") finally: self.parent.destroy() def initStream(self): self.stream = self.p.open(format=8, channels=1, rate=44100, output=True, stream_callback=self._callbackstream, start=True, frames_per_buffer=self.chunk_size) def playsound(self, event=None): if self.sem.locked(): return if self.play_mode.get() == True: try: self.playbutton.config(text="Play") self.stream.close() self.stream.close() self.play_mode.set(False) except: print("stop failed") else: try: self.initStream() self.play_mode.set(True) self.playbutton.config(text="Stop") except: print("stop failed") def playbackMove(self, event=None): # move cursor by audio chunk size incr = (self.chunk_size) // self.hopsizes[self.currentplot] self.cursor.set_xdata(self.cursor.get_xdata() + incr) self.updateCursor() def new_release_zoom(self, *args, **kwargs): self.release_zoom_orig(*args, **kwargs) s = 'toolbar_event' event = Event(s, self) self.canvas.callbacks.process(s, Event('toolbar_event', self)) def new_release_pan(self, *args, **kwargs): self.release_pan_orig(*args, **kwargs) s = 'toolbar_event' event = Event(s, self) self.canvas.callbacks.process(s, Event('toolbar_event', self)) def new_update_view(self, *args, **kwargs): self._update_view_orig(*args, **kwargs) s = 'toolbar_event' event = Event(s, self) self.canvas.callbacks.process(s, Event('toolbar_event', self)) def handle_toolbar(self, event): self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) def onclick(self, event): if (self.toolbar._active == 'ZOOM' or self.toolbar._active == 'PAN'): pass elif (self.stamp > -1): self.stampHistory.append(self.stamp) self.timeValues[self.stamp].append(event.xdata * self.hopsizes[self.currentplot]) self.mainplot.draw_artist( self.mainplot.axvline(x=event.xdata, color=self.colors[self.stamp])) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) self.info.set("Cursor") self.stamp = -1 else: self.cursor.set_xdata(event.xdata) self.wf.setpos(int(event.xdata * self.hopsizes[self.currentplot])) self.updateCursor() def updateCursor(self): self.canvas.restore_region(self.background) self.mainplot.draw_artist(self.cursor) self.canvas.blit(self.mainplot.bbox) def browse_file(self): self.filename = os.path.basename( tkFileDialog.askopenfilename(**self.file_opt)) self.filelocation.delete(0, END) self.filelocation.insert(0, self.file_opt['initialdir'] + self.filename) def changeparams(self, *args): self.featureParamsEntry.delete(0, END) try: self.featureParamsEntry.insert( 0, str(self.defaultParams[self.featurename.get()])) except: self.featureParamsEntry.insert(0, "()") print("No default params for selected feature") def showfeature(self): self.mainplot.clear() featureName = self.featurename.get() featureParams = eval(self.featureParamsEntry.get()) if featureName in self.calculated_features_dict and self.calculated_featuresParams[ featureName] != featureParams: # if a feature is calculated before and params are the same; plot the saved result self.currentplot = self.calculated_features_dict[featureName] if len(self.calculated_features[self.currentplot].shape) == 2: ylim = 5000 binToFreq = np.arange(0, ylim, 43.066) # Fs/N self.mainplot.pcolormesh( np.arange( self.calculated_features[self.currentplot].shape[0]), binToFreq, self.calculated_features[ self.currentplot].T[:binToFreq.size, :]) else: self.mainplot.plot(self.calculated_features[self.currentplot]) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) hopSize = self.hopsizes[self.calculated_features_dict[featureName]] else: self.currentplot = len(self.calculated_features) self.calculated_features_dict[featureName] = self.currentplot result, hopSize = getattr(features, featureName)(self.audio, featureParams) if len(result.shape) == 2: ylim = 5000 binToFreq = np.arange(0, ylim, 43.066) # Fs/N self.mainplot.pcolormesh(np.arange(result.shape[0]), binToFreq, result.T[:binToFreq.size, :]) else: self.mainplot.plot(result) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) self.calculated_features.append(result) self.hopsizes.append(hopSize) self.calculated_featuresParams[featureName] = featureParams self.drawAllStamps() def sendbelow(self): self.secplot.clear() if len(self.calculated_features[self.currentplot].shape) == 2: ylim = 5000 binToFreq = np.arange(0, ylim, 43.066) # Fs/N self.secplot.pcolormesh( np.arange(self.calculated_features[self.currentplot].shape[0]), binToFreq, self.calculated_features[ self.currentplot].T[:binToFreq.size, :]) else: self.secplot.plot(self.calculated_features[self.currentplot]) self.canvas.draw() def plot(self): self.discard() inputFile = self.filelocation.get() self.wf = wave.open(inputFile, 'rb') self.featurename.set("Audio") self.audio = MonoLoader(filename=inputFile, sampleRate=44100)() self.showfeature() self.cursor.set_xdata(0) self.canvaswidget.focus_set() if (self.ALoad.get() == 1): self.loadAnnotations() def saveAnnotations(self): directory = self.file_opt[ 'initialdir'] + "Annotations/" + self.filename.split('.')[ 0] # deleting .wav if not os.path.exists(directory): os.makedirs(directory) for i in self.labeldict: with open(directory + '/%s.txt' % self.labeldict[i], 'w') as filehandle: json.dump(self.timeValues[i], filehandle) def loadAnnotations(self): directory = self.file_opt[ 'initialdir'] + "Annotations/" + self.filename.split('.')[0] + "/" if not os.path.exists(directory): print("No annotations") else: for x in sorted(os.listdir(directory)): with open(directory + x, 'r') as filehandle: values = json.load(filehandle) if self.isTime.get() == 1: values = [v * 44100 for v in values] self.labeldict[len(self.labeldict)] = x.split('.')[0] var = IntVar() var.set(1) self.show.append(var) self.timeValues.append(values) def getNext(self): self.discard() fileList = sorted(os.listdir(self.file_opt['initialdir'])) nextIndex = fileList.index(self.filename) + 1 if nextIndex == 0 or nextIndex == len(fileList): print("No more files") else: self.filename = fileList[nextIndex] self.filelocation.delete(0, END) self.filelocation.insert( 0, self.file_opt['initialdir'] + fileList[nextIndex]) self.plot() def drawAllStamps(self): hs = self.hopsizes[self.currentplot] try: del self.mainplot.lines[1:] except: pass for i in self.labeldict: if self.show[i].get() == 1: for j in self.timeValues[i]: self.mainplot.draw_artist( self.mainplot.axvline(x=j // hs, color=self.colors[i])) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.mainplot.bbox) def saveAndNext(self): self.saveAnnotations() self.getNext() self.plot()
class Viewer(tk.Frame): def __init__(self, parent, collection=None, with_toolbar=True): tk.Frame.__init__(self, parent) # toolbar if with_toolbar: self.create_toolbar() # canvas #canvas_frame = tk.Frame(self) #canvas_frame.pack(side=tk.LEFT,fill=tk.BOTH,expand=1) #title_frame = tk.Frame(canvas_frame) #title_frame.pack(side=tk.TOP,anchor=tk.NW) #tk.Label(title_frame,text=" Plot Title: ").pack(side=tk.LEFT) #self._title = tk.Entry(title_frame,width=30) #self._title.pack(side=tk.LEFT) #tk.Button(title_frame, text='Set', command=lambda: self.updateTitle() # ).pack(side=tk.LEFT) self.fig = plt.Figure(figsize=(8, 6)) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, master=self) self.setupMouseNavigation() self.navbar = ToolBar(self.canvas, self, self.ax) # for matplotlib features self.setupNavBarExtras(self.navbar) self.canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=1) # spectra list self.create_listbox() # toggle options self.mean = False self.median = False self.max = False self.min = False self.std = False self.spectrum_mode = False self.show_flagged = True # data self.collection = collection self.head = 0 self.flag_filepath = os.path.abspath('./flagged_spectra.txt') if collection: self.update_artists(new_lim=True) self.update_list() # pack self.pack(fill=tk.BOTH, expand=1) self.color = '#000000' def returnToSelectMode(self): if self.ax.get_navigate_mode() == 'PAN': #Turn panning off self.navbar.pan() elif self.ax.get_navigate_mode() == 'ZOOM': #Turn zooming off self.navbar.zoom() def setupNavBarExtras(self, navbar): working_dir = os.path.dirname(os.path.abspath(__file__)) img = Image.open(os.path.join(working_dir, "select.png")) #self.select_icon = tk.PhotoImage(file=os.path.join(working_dir,"select.png")) self.select_icon = tk.PhotoImage(img) self.select_button = tk.Button(navbar, width="24", height="24", image=img, command=self.returnToSelectMode).pack( side=tk.LEFT, anchor=tk.W) self.dirLbl = tk.Label(navbar, text="Viewing: None") self.dirLbl.pack(side=tk.LEFT, anchor=tk.W) def plotConfig(self): config = PlotConfigDialog(self, title=self.ax.get_title(), xlabel=self.ax.get_xlabel(), ylabel=self.ax.get_ylabel(), xlim=self.ax.get_xlim(), ylim=self.ax.get_ylim()) if (config.applied): print(config.title) print(config.xlim) self.ax.set_title(config.title) self.ax.set_xlabel(config.xlabel) self.ax.set_ylabel(config.ylabel) self.ax.set_xlim(*config.xlim) self.ax.set_ylim(*config.ylim) self.canvas.draw() def rectangleStartEvent(self, event): self._rect = None self._rect_start = event def rectangleMoveEvent(self, event): try: dx = event.xdata - self._rect_start.xdata dy = event.ydata - self._rect_start.ydata except TypeError: #we're out of canvas bounds return if self._rect is not None: self._rect.remove() self._rect = Rectangle( (self._rect_start.xdata, self._rect_start.ydata), dx, dy, color='k', ls='--', lw=1, fill=False) self.ax.add_patch(self._rect) self.ax.draw_artist(self._rect) def rectangleEndEvent(self, event): if self._rect is not None: self._rect.remove() else: #make a small, fake rectangle class FakeEvent(object): def __init__(self, x, y): self.xdata, self.ydata = x, y dy = (self.ax.get_ylim()[1] - self.ax.get_ylim()[0]) / 100. self._rect_start = FakeEvent(event.xdata - 10, event.ydata + dy) event = FakeEvent(event.xdata + 10, event.ydata - dy) if not self.collection is None: x0 = min(self._rect_start.xdata, event.xdata) x1 = max(self._rect_start.xdata, event.xdata) y0 = min(self._rect_start.ydata, event.ydata) y1 = max(self._rect_start.ydata, event.ydata) try: #if our data is sorted, we can easily isolate it x_data = self.collection.data.loc[x0:x1] except: #Pandas builtin throws an error, use another pandas builtin data = self.collection.data in_xrange = (data.index >= x0) & (data.index <= x1) x_data = data.iloc[in_xrange] ylim = sorted([self._rect_start.ydata, event.ydata]) is_in_box = ((x_data > y0) & (x_data < y1)).any() highlighted = is_in_box.index[is_in_box].tolist() key_list = list(self.collection._spectra.keys()) self.update_selected(highlighted) flags = self.collection.flags for highlight in highlighted: #O(n^2) woof if (not (highlight in flags)) or self.show_flagged: pos = key_list.index(highlight) self.listbox.selection_set(pos) def setupMouseNavigation(self): self.clicked = False self.select_mode = 'rectangle' self._bg_cache = None START_EVENTS = {'rectangle': self.rectangleStartEvent} MOVE_EVENTS = {'rectangle': self.rectangleMoveEvent} END_EVENTS = {'rectangle': self.rectangleEndEvent} def onMouseDown(event): if self.ax.get_navigate_mode() is None: self._bg_cache = self.canvas.copy_from_bbox(self.ax.bbox) self.clicked = True START_EVENTS[self.select_mode](event) def onMouseUp(event): if self.ax.get_navigate_mode() is None: self.canvas.restore_region(self._bg_cache) self.canvas.blit(self.ax.bbox) self.clicked = False END_EVENTS[self.select_mode](event) def onMouseMove(event): if self.ax.get_navigate_mode() is None: if (self.clicked): self.canvas.restore_region(self._bg_cache) MOVE_EVENTS[self.select_mode](event) self.canvas.blit(self.ax.bbox) self.canvas.mpl_connect('button_press_event', onMouseDown) self.canvas.mpl_connect('button_release_event', onMouseUp) self.canvas.mpl_connect('motion_notify_event', onMouseMove) @property def head(self): return self._head @head.setter def head(self, value): if not hasattr(self, '_head'): self._head = 0 else: self._head = value % len(self.collection) def set_head(self, value): if isinstance(value, Iterable): if len(value) > 0: value = value[0] else: value = 0 self.head = value if self.spectrum_mode: self.update() self.update_selected() @property def collection(self): return self._collection @collection.setter def collection(self, value): if isinstance(value, Spectrum): # create new collection self._collection = Collection(name=Spectrum.name, spectra=[value]) if isinstance(value, Collection): self._collection = value else: self._collection = None def move_selected_to_top(self): selected = self.listbox.curselection() keys = [self.collection.spectra[s].name for s in selected] for s in selected[::-1]: self.listbox.delete(s) self.listbox.insert(0, *keys) self.listbox.selection_set(0, len(keys)) def unselect_all(self): self.listbox.selection_clear(0, tk.END) self.update_selected() def select_all(self): self.listbox.selection_set(0, tk.END) self.update_selected() def invert_selection(self): for i in range(self.listbox.size()): if self.listbox.selection_includes(i): self.listbox.selection_clear(i) else: self.listbox.selection_set(i) self.update_selected() def change_color(self): cpicker = ColorPickerDialog(self) #rgb,color = askcolor(self.color) if cpicker.applied: self.color = cpicker.color self.color_pick.config(bg=self.color) #update our list of chosen colors selected = self.listbox.curselection() selected_keys = [self.collection.spectra[s].name for s in selected] for key in selected_keys: self.colors[key] = self.color self.update() def select_by_name(self): pattern = self.name_filter.get() for i in range(self.listbox.size()): if pattern in self.listbox.get(i): self.listbox.selection_set(i) else: self.listbox.selection_clear(i) self.update_selected() def create_listbox(self): self._sbframe = tk.Frame(self) list_label = tk.Frame(self._sbframe) list_label.pack(side=tk.TOP, anchor=tk.N, fill=tk.X) tk.Label(list_label, text="Name:").pack(side=tk.LEFT, anchor=tk.W) self.name_filter = tk.Entry(list_label, width=14) self.name_filter.pack(side=tk.LEFT, anchor=tk.W) tk.Button(list_label, text="Select", command=lambda: self.select_by_name()).pack(side=tk.LEFT, anchor=tk.W) self.sblabel = tk.Label(list_label, text="Showing: 0") self.sblabel.pack(side=tk.RIGHT) self.scrollbar = tk.Scrollbar(self._sbframe) self.listbox = tk.Listbox(self._sbframe, yscrollcommand=self.scrollbar.set, selectmode=tk.EXTENDED, width=30) self.scrollbar.config(command=self.listbox.yview) self.list_tools = tk.Frame(self._sbframe) tk.Button(self.list_tools, text="To Top", command=lambda: self.move_selected_to_top()).pack( side=tk.TOP, anchor=tk.NW, fill=tk.X) tk.Button(self.list_tools, text="Select All", command=lambda: self.select_all()).pack(side=tk.TOP, anchor=tk.NW, fill=tk.X) tk.Button(self.list_tools, text="Clear", command=lambda: self.unselect_all()).pack(side=tk.TOP, anchor=tk.NW, fill=tk.X) tk.Button(self.list_tools, text="Invert", command=lambda: self.invert_selection()).pack(side=tk.TOP, anchor=tk.NW, fill=tk.X) self.color_field = tk.Frame(self.list_tools) tk.Label(self.color_field, text="Color:").pack(side=tk.LEFT) self.color_pick = tk.Button(self.color_field, text="", command=lambda: self.change_color(), bg='#000000') self.color_pick.pack(side=tk.RIGHT, anchor=tk.NW, fill=tk.X, expand=True) self.color_field.pack(side=tk.TOP, anchor=tk.NW, fill=tk.X) self.list_tools.pack(side=tk.RIGHT, anchor=tk.NW) self.scrollbar.pack(side=tk.RIGHT, anchor=tk.E, fill=tk.Y) self.listbox.pack(side=tk.RIGHT, anchor=tk.E, fill=tk.Y) self.listbox.bind('<<ListboxSelect>>', lambda x: self.set_head(self.listbox.curselection())) self._sbframe.pack(side=tk.RIGHT, anchor=tk.E, fill=tk.Y) def create_toolbar(self): self.toolbar = tk.Frame(self) tk.Button(self.toolbar, text='Read', command=lambda: self.read_dir()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Mode', command=lambda: self.toggle_mode()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text="Plot Config", command=lambda: self.plotConfig()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Show/Hide Flagged', command=lambda: self.toggle_show_flagged()).pack( side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Flag/Unflag', command=lambda: self.toggle_flag()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Unflag all', command=lambda: self.unflag_all()).pack(side=tk.LEFT, fill=tk.X, expand=1) #tk.Button(self.toolbar, text='Save Flag', command=lambda: # self.save_flag()).pack(side=tk.LEFT,fill=tk.X,expand=1) tk.Button(self.toolbar, text='Save Flags', command=lambda: self.save_flag_as()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Stitch', command=lambda: self.stitch()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='Jump_Correct', command=lambda: self.jump_correct()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='mean', command=lambda: self.toggle_mean()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='median', command=lambda: self.toggle_median()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='max', command=lambda: self.toggle_max()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='min', command=lambda: self.toggle_min()).pack(side=tk.LEFT, fill=tk.X, expand=1) tk.Button(self.toolbar, text='std', command=lambda: self.toggle_std()).pack(side=tk.LEFT, fill=tk.X, expand=1) self.toolbar.pack(side=tk.TOP, fill=tk.X) def updateTitle(self): print("Hello world!") self.ax.set_title(self._title.get()) self.canvas.draw() def set_collection(self, collection): new_lim = True if self.collection is None else False self.collection = collection self.update_artists(new_lim=new_lim) self.update() self.update_list() def read_dir(self): try: directory = os.path.split( filedialog.askopenfilename(filetypes=( ("Supported types", "*.asd *.sed *.sig *.pico"), ("All files", "*"), )))[0] except: return if not directory: return c = Collection(name="collection", directory=directory) self.set_collection(c) self.dirLbl.config(text="Viewing: " + directory) def reset_stats(self): if self.mean_line: self.mean_line.remove() self.mean_line = None self.mean = False if self.median_line: self.median_line.remove() self.median_line = None self.median = False if self.max_line: self.max_line.remove() self.max_line = None self.max = False if self.min_line: self.min_line.remove() self.min_line = None self.min = False if self.std_line: self.std_line.remove() self.std_line = None self.std = False def toggle_mode(self): if self.spectrum_mode: self.spectrum_mode = False else: self.spectrum_mode = True self.update() def toggle_show_flagged(self): if self.show_flagged: self.show_flagged = False else: self.show_flagged = True self.update() def unflag_all(self): #new flags -> new statistics self.reset_stats() for spectrum in list(self.collection.flags): self.collection.unflag(spectrum) self.update() self.update_list() def toggle_flag(self): #new flags -> new statistics self.reset_stats() selected = self.listbox.curselection() keys = [self.listbox.get(s) for s in selected] for i, key in enumerate(keys): print(i, key) spectrum = key if spectrum in self.collection.flags: self.collection.unflag(spectrum) self.listbox.itemconfigure(selected[i], foreground='black') else: self.collection.flag(spectrum) self.listbox.itemconfigure(selected[i], foreground='red') # update figure self.update() def save_flag(self): ''' save flag to self.flag_filepath''' with open(self.flag_filepath, 'w') as f: for spectrum in self.collection.flags: print(spectrum, file=f) def save_flag_as(self): ''' modify self.flag_filepath and call save_flag()''' flag_filepath = filedialog.asksaveasfilename() if os.path.splitext(flag_filepath)[1] == '': flag_filepath = flag_filepath + '.txt' self.flag_filepath = flag_filepath self.save_flag() def update_list(self): self.listbox.delete(0, tk.END) for i, spectrum in enumerate(self.collection.spectra): self.listbox.insert(tk.END, spectrum.name) if spectrum.name in self.collection.flags: self.listbox.itemconfigure(i, foreground='red') self.update_selected() def ask_for_draw(self): #debounce canvas updates now = datetime.now() print(now - self.last_draw) if ((now - self.last_draw).total_seconds() > 0.5): self.canvas.draw() self.last_draw = now def update_artists(self, new_lim=False): if self.collection is None: return #update values being plotted -> redo statistics self.mean_line = None self.median_line = None self.max_line = None self.min_line = None self.std_line = None # save limits if new_lim == False: xlim = self.ax.get_xlim() ylim = self.ax.get_ylim() # plot self.ax.clear() # show statistics if self.spectrum_mode: idx = self.listbox.curselection() if len(idx) == 0: idx = [self.head] spectra = [self.collection.spectra[i] for i in idx] flags = [s.name in self.collection.flags for s in spectra] print("flags = ", flags) flag_style = ' ' if self.show_flagged: flag_style = 'r' artists = Collection(name='selection', spectra=spectra).plot( ax=self.ax, style=list(np.where(flags, flag_style, self.color)), picker=1) self.ax.set_title('selection') # c = str(np.where(spectrum.name in self.collection.flags, 'r', 'k')) # spectrum.plot(ax=self.ax, label=spectrum.name, c=c) else: # red curves for flagged spectra flag_style = ' ' if self.show_flagged: flag_style = 'r' flags = [ s.name in self.collection.flags for s in self.collection.spectra ] print("flags = ", flags) self.collection.plot(ax=self.ax, style=list(np.where(flags, flag_style, 'k')), picker=1) #self.ax.set_title(self.collection.name) keys = [s.name for s in self.collection.spectra] artists = self.ax.lines self.artist_dict = {key: artist for key, artist in zip(keys, artists)} self.colors = {key: 'black' for key in keys} self.ax.legend().remove() self.navbar.setHome(self.ax.get_xlim(), self.ax.get_ylim()) self.canvas.draw() self.sblabel.config(text="Showing: {}".format(len(artists))) def update_selected(self, to_add=None): """ Update, only on flaged""" if self.collection is None: return if to_add: for key in to_add: self.artist_dict[key].set_linestyle('--') else: keys = [s.name for s in self.collection.spectra] selected = self.listbox.curselection() selected_keys = [self.collection.spectra[s].name for s in selected] for key in keys: if key in selected_keys: self.artist_dict[key].set_linestyle('--') else: self.artist_dict[key].set_linestyle('-') self.canvas.draw() def update(self): """ Update the plot """ if self.collection is None: return # show statistics if self.spectrum_mode: self.ax.clear() idx = self.listbox.curselection() if len(idx) == 0: idx = [self.head] spectra = [self.collection.spectra[i] for i in idx] flags = [s.name in self.collection.flags for s in spectra] print("flags = ", flags) flag_style = ' ' if self.show_flagged: flag_style = 'r' Collection(name='selection', spectra=spectra).plot( ax=self.ax, style=list(np.where(flags, flag_style, 'k')), picker=1) self.ax.set_title('selection') # c = str(np.where(spectrum.name in self.collection.flags, 'r', 'k')) # spectrum.plot(ax=self.ax, label=spectrum.name, c=c) else: # red curves for flagged spectra keys = [s.name for s in self.collection.spectra] for key in keys: if key in self.collection.flags: if self.show_flagged: self.artist_dict[key].set_visible(True) self.artist_dict[key].set_color('red') else: self.artist_dict[key].set_visible(False) else: self.artist_dict[key].set_color(self.colors[key]) self.artist_dict[key].set_visible(True) if self.show_flagged: self.sblabel.config( text="Showing: {}".format(len(self.artist_dict))) else: self.sblabel.config(text="Showing: {}".format( len(self.artist_dict) - len(self.collection.flags))) ''' self.collection.plot(ax=self.ax, style=list(np.where(flags, flag_style, 'k')), picker=1) self.ax.set_title(self.collection.name) ''' if self.spectrum_mode: #self.ax.legend() pass else: #self.ax.legend().remove() pass self.ax.set_ylabel(self.collection.measure_type) #toggle appearance of statistics if self.mean_line != None: self.mean_line.set_visible(self.mean) if self.median_line != None: self.median_line.set_visible(self.median) if self.max_line != None: self.max_line.set_visible(self.max) if self.min_line != None: self.min_line.set_visible(self.min) if self.std_line != None: self.std_line.set_visible(self.std) self.canvas.draw() def next_spectrum(self): if not self.spectrum_mode: return self.head = (self.head + 1) % len(self.collection) self.update() def stitch(self): ''' Known Bugs ---------- Can't stitch one spectrum and plot the collection ''' self.collection.stitch() self.update_artists() def jump_correct(self): ''' Known Bugs ---------- Only performs jump correction on 1000 and 1800 wvls and 1 reference ''' self.collection.jump_correct([1000, 1800], 1) self.update_artists() def toggle_mean(self): if self.mean: self.mean = False else: self.mean = True if not self.mean_line: self.collection.mean().plot(ax=self.ax, c='b', label=self.collection.name + '_mean', lw=3) self.mean_line = self.ax.lines[-1] self.update() def toggle_median(self): if self.median: self.median = False else: self.median = True if not self.median_line: self.collection.median().plot(ax=self.ax, c='g', label=self.collection.name + '_median', lw=3) self.median_line = self.ax.lines[-1] self.update() def toggle_max(self): if self.max: self.max = False else: self.max = True if not self.max_line: self.collection.max().plot(ax=self.ax, c='y', label=self.collection.name + '_max', lw=3) self.max_line = self.ax.lines[-1] self.update() def toggle_min(self): if self.min: self.min = False else: self.min = True if not self.min_line: self.collection.min().plot(ax=self.ax, c='m', label=self.collection.name + '_min', lw=3) self.min_line = self.ax.lines[-1] self.update() def toggle_std(self): if self.std: self.std = False else: self.std = True if not self.std_line: self.collection.std().plot(ax=self.ax, c='c', label=self.collection.name + '_std', lw=3) self.std_line = self.ax.lines[-1] self.update()
class FittingWindow: def __init__(self, master,ramanspectrum =None): global guessplot,dataplot self.master = master self.textframe= Frame(master = self.master) self.scroll = Tkinter.Scrollbar(self.textframe) self.scroll.grid(row=0,column=1) self.t = Tkinter.Text(self.textframe,yscrollcommand=self.scroll.set,width=30) self.scroll.config(command=self.t.yview) self.t.grid(row=0,column=0) self.plotframe = Frame(master = self.master) self.frame = Frame(master = self.master) self.textframe.grid(row=0,column=0,columnspan=3) self.plotframe.grid(row = 0, column = 3, columnspan = 11,sticky = 'ew') self.frame.grid(column = 1, row = 1,columnspan = 11, rowspan = 3,sticky = 'snew') self.buttonframe = Frame(master = self.master) self.buttonframe.grid(column=0, row =1) ############################## self.menubar = Menu(self.master) self.filemenu = Menu(self.menubar, tearoff=0) self.filemenu.add_command(label="New window", command = lambda: FittingWindow(Toplevel())) self.filemenu.add_command(label="Open", command = self.open_data) self.filemenu.add_command(label="Save",command = None) self.filemenu.add_command(label="SaveFig",command = None) self.filemenu.add_command(label="ViewNotebook",command = None) self.filemenu.add_command(label="Exit", command = self.quitproc) self.menubar.add_cascade(label="File", menu=self.filemenu) self.master.config(menu= self.menubar) ############################## self.fit_button = Tkinter.Button(master = self.buttonframe, command = self.fit_and_draw, width = 5, text = 'FitNow') self.fit_button.grid(row = 3, column = 0) self.open_button = Tkinter.Button(master = self.buttonframe, command = self.open_data, width = 5, text = 'open') self.open_button.grid(row = 4, column = 0) # self.open_ref_button = Tkinter.Button(master = self.buttonframe, command = self.open_ref, width = 5, text = 'Choose reference') # self.open_ref_button.grid(row = 5, column = 0) # self.open_bkg_button = Tkinter.Button(master = self.buttonframe, command = self.open_bkg, width = 5, text = 'Choose background') # self.open_bkg_button.grid(row = 6, column = 0) # # self.ref_label = Tkinter.Label(master = self.buttonframe, text = '') # self.ref_label.grid(row = 7, column = 0) # # self.bkg_label = Tkinter.Label(master = self.buttonframe, text = '') # self.bkg_label.grid(row = 8, column = 0) self.smoothbutton = Tkinter.Button(master = self.buttonframe, command = self.smoothdata, width = 5, text = 'Smooth') self.smoothbutton.grid(row = 5, column = 0) self.function_list=[ 'OneGaussian', 'TwoGaussian', 'ThreeGaussian', 'FourGaussian', 'FiveGaussian'] self.var_func = StringVar() self.var_func.set(self.function_list[0]) self.MotorMenu = OptionMenu(self.buttonframe,self.var_func, *self.function_list, command = self.init_function) self.MotorMenu.grid(row = 3,column =1,sticky = W) self.normalization_constant = 1 self.scale_list = [] self.var_list = [] self.fig = Figure(figsize=(5,3)) self.ax1 = self.fig.add_subplot(111) dataplot = self.ax1.plot(arange(2800,3605,5),zeros((161,)),animated = True)[0]#(ax = self.ax1).lines[-1] guessplot = self.ax1.plot(arange(2800,3605,5),zeros((161,)),animated = True)[0] self.canvas = FigureCanvasTkAgg(self.fig,master = self.plotframe) self.canvas.show() self.canvas._tkcanvas.pack(side=TOP, fill = BOTH,expand =True) self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.plotframe) self.toolbar.update() self.toolbar.pack(side= BOTTOM, fill = BOTH,expand =True)#,expand = True)#fill=BOTH)#, expand=1) self.canvas.draw() self.canvas.show() self.background = self.canvas.copy_from_bbox(self.ax1.bbox) self.reference = numpy.ndarray((161,)) self.reference[:] = 1 self.reference_name = '' self.guess = list() self.name = [''] if ramanspectrum is None: self.a = pandas.Series(zeros(161),arange(2800,3605,5)) else: self.a = copy(ramanspectrum) print self.a self.t.insert(END,'data multiplied by'+str(1/max(self.a))) self.a[:]/=max(self.a.values) self.startfreq_text = Entry(self.buttonframe,width = 5) self.startfreq = min(array(self.a.index)) self.startfreq_text.insert(END,str(self.startfreq)) self.startfreq_text.bind("<Return>", self.update_limits) self.startfreq_text.grid(row = 0 , column =1,sticky = W) self.endfreq_text = Entry(self.buttonframe,width = 5) self.endfreq = max(array(self.a.index)) self.endfreq_text.insert(END,str(self.endfreq)) self.endfreq_text.bind("<Return>", self.update_limits) self.endfreq_text.grid(row = 1 , column =1,sticky = W) if self.init_function('OneLorentzian') == -1: self.t.insert(END, 'error initializing fit function') self.ax1.cla() self.canvas.show() self.background = self.canvas.copy_from_bbox(self.ax1.bbox) dataplot = self.ax1.plot(array(self.a.index),self.a.values,animated=True)[0] x= array(self.a.index) guessplot = self.ax1.plot(x,zeros(self.a.size),animated=True)[0] self.ax1.set_xlim(min(self.a.index),max(self.a.index)) self.ax1.set_ylim(min(self.a.values),1) self.canvas.show() self.raw = self.a.copy self.startidx = 0 self.endidx = -1 self.update_limits(0) return None def nothing(self): return 0 def quitproc(self): self.master.destroy() return 0 def init_function(self,functiontype): global function import inspect if functiontype == None: functiontype = self.var_func.get() self.functiontype = functiontype if functiontype == 'OneLorentzian': def function(x,A1,w1,G1,m,b): return m*x/1000+b + A1**2/((x-w1)**2+G1**2) elif functiontype == 'TwoLorentzian': def function(x,A1,A2,w1,w2,G1,G2,m,b): return m*x/1000+b + A1**2/((x-w1)**2+G1**2) +A2**2/((x-w2)**2+G2**2) elif functiontype == 'ThreeLorentzian': def function(x,A1,A2,A3,w1,w2,w3,G1,G2,G3,m,b): return m*x/1000+b + A1**2/((x-w1)**2+G1**2) +A2**2/((x-w2)**2+G2**2) +A3**2/((x-w3)**2+G3**2) elif functiontype == 'FourLorentzian': def function(x,A1,A2,A3,A4,w1,w2,w3,w4,G1,G2,G3,G4,m,b): return m*x/1000+b + A1**2/((x-w1)**2+G1**2) +A2**2/((x-w2)**2+G2**2) +A3**2/((x-w3)**2+G3**2) +A4**2/((x-w4)**2+G4**2) elif functiontype == 'FiveLorentzian': def function(x,A1,A2,A3,A4,A5,w1,w2,w3,w4,w5,G1,G2,G3,G4,G5,m,b): return m*x/1000+b + A1**2/((x-w1)**2+G1**2) +A2**2/((x-w2)**2+G2**2) +A3**2/((x-w3)**2+G3**2) +A4**2/((x-w4)**2+G4**2)+A5**2/((x-w5)**2+G5**2) elif functiontype == 'OneGaussian': def function(x,A1,w1,G1,m,b): return m*x/1000+b + A1*exp(-(x-w1)**2/G1) elif functiontype == 'TwoGaussian': def function(x,A1,A2,w1,w2,G1,G2,m,b): return A1*exp(-(x-w1)**2/G1) +A2*exp(-(x-w2)**2/G2) +m*x/1000+b elif functiontype == 'ThreeGaussian': def function(x,A1,A2,A3,w1,w2,w3,G1,G2,G3,m,b): return m*x/1000+b + A1*exp(-(x-w1)**2/G1) +A2*exp(-(x-w2)**2/G2) +A3*exp(-(x-w3)**2/G3) elif functiontype == 'FourGaussian': def function(x,A1,A2,A3,A4,w1,w2,w3,w4,G1,G2,G3,G4,m,b): return m*x/1000+b +A1*exp(-(x-w1)**2/G1) +A2*exp(-(x-w2)**2/G2) +A3*exp(-(x-w3)**2/G3) +A4*exp(-(x-w4)**2/G4) elif functiontype == 'FiveGaussian': def function(x,A1,A2,A3,A4,A5,w1,w2,w3,w4,w5,G1,G2,G3,G4,G5,m,b): return m*x/1000+b + (A1/G1*numpy.sqrt(2*pi))*exp(-(x-w1)**2/(2*G1**2)) +(A2/G2*numpy.sqrt(2*pi))*exp(-(x-w2)**2/(2*G2**2)) +(A3/G3*numpy.sqrt(2*pi))*exp(-(x-w3)**2/(2*G3**2)) +(A4/G4*numpy.sqrt(2*pi))*exp(-(x-w4)**2/(2*G4**2)) +(A5/G5*numpy.sqrt(2*pi))*exp(-(x-w5)**2/(2*G5**2)) else: tkMessageBox.showerror('Not regconized function') def function(x,m,b): return m*x/1000+b #============================================================================== self.w_name = list() self.w_name = inspect.getargspec(function).args[1:] self.guess= [] for w in self.w_name: if 'A' in w: self.guess.append(0.1) elif 'w' in w: self.guess.append(1000) elif 'G' in w: self.guess.append(10) else: self.guess.append(0) #================================================================================ for i in range(len(self.scale_list),len(self.w_name)): self.scale_list.append(Scale(master = self.frame,command = self.guessdraw)) for i in range(len(self.w_name),len(self.scale_list)): self.scale_list[i].grid_forget() for i in range(len(self.w_name)): self.scale_list[i].config(label = self.w_name[i]) self.scale_list[i].grid(row = int(i/8), column = i%8) if self.w_name[i][0] == 'A': self.scale_list[i].config(from_ =0, to = 5,resolution = 0.1) self.scale_list[i].set(1) elif self.w_name[i][0] == 'w': self.scale_list[i].config(from_ = self.startfreq-20, to = self.endfreq+20,resolution = 1) self.scale_list[i].set(2800) elif self.w_name[i][0] == 'G': self.scale_list[i].config(from_ = 1, to = 50) self.scale_list[i].set(7) elif self.w_name[i][0] == 'm': self.scale_list[i].config(from_ = -5, to = 5, resolution = 0.05) self.scale_list[i].set(0) elif self.w_name[i][0] == 'b': self.scale_list[i].config(from_ = -5, to = 5, resolution = 0.05) self.scale_list[i].set(0) else: self.t.insert(END, "Variable not recognized:", self.w_name[i] ) self.scale_list[i].bind('<B1-Motion>',self.guessdraw) return 0 def smoothdata(self): self.a.smoooth() self.guessdraw(0) def update_limits(self,extra): functiontype = self.var_func.get() self.startfreq = max(float(self.startfreq_text.get()),min(array(self.a.index))) self.endfreq = min(float(self.endfreq_text.get()),max(array(self.a.index))) self.startidx = self.a.nearest(self.startfreq) self.endidx = self.a.nearest(self.endfreq) for i in range(len(self.guess)): self.guess[i] = self.scale_list[i].get() self.ax1.cla() self.canvas.show() self.background = self.canvas.copy_from_bbox(self.ax1.bbox) dataplot = self.ax1.plot(array(self.a.index[self.startidx:self.endidx]),self.a.values[self.startidx:self.endidx],animated=True)[0]#(ax = self.ax1).lines[-1] x= array(self.a.index[self.startidx:self.endidx]) guessplot = self.ax1.plot(x,zeros(x.size),animated=True)[0] #self.ax1.set_xlim(min(self.a.index),max(self.a.index)) #self.ax1.set_ylim(min(self.a.values),1) self.canvas.show() self.guessdraw(0) return 0 def open_data(self): global dataplot,guessplot self.name = self.DisplaySpectrum() if self.name == -1: return 0 try: self.a = RamanSpectrum(self.name) self.normalization_constant= 1/max(self.a) self.startfreq = min(array(self.a.index)) self.startfreq_text.insert(END,str(self.startfreq)) self.endfreq = max(array(self.a.index)) self.endfreq_text.insert(END,str(self.endfreq)) except: self.t.insert(END, 'error opening spectrum') return -1 self.raw = self.a.copy self.ax1.cla() self.canvas.show() self.background = self.canvas.copy_from_bbox(self.ax1.bbox) dataplot = self.ax1.plot(array(self.a.index),self.a.values,animated=True)[0]#(ax = self.ax1).lines[-1] x= array(self.a.index) guessplot = self.ax1.plot(x,zeros(self.a.size),animated=True)[0] self.ax1.set_xlim(min(self.a.index),max(self.a.index)) self.ax1.set_ylim(min(self.a.values),1) self.canvas.show() self.update_limits(0) return 0 def open_ref(self): pass return 0 def open_bkg(self): pass return 0 def recalc_data(self): if self.raw.shape == self.reference.shape: self.a= SG_Smooth(self.raw,width = 11,order = 3)/self.reference/self.normalization_constant self.ax1.cla() plot(self.a[0],self.raw/self.reference) plot(self.a[0],self.a[1]) else: self.t.insert(END, "reference invalid") self.reference_name = "No IR Reference" self.ax1.cla() self.ax1.plot(self.a[0],self.a[1]) self.canvas.draw() return 0 def guessdraw(self,ex): global function,guessplot,dataplot x = array(self.a.index[self.startidx:self.endidx+1]) for i in range(len(self.guess)): self.guess[i] = self.scale_list[i].get() self.canvas.restore_region(self.background) self.ax1.lines[-1].set_xdata(x) self.ax1.lines[-1].set_ydata(function(x,*self.guess)) for l in self.ax1.lines: self.ax1.draw_artist(l) self.canvas.blit(self.ax1.bbox) return 0 def fit_and_draw(self): global function print type(self.a) if type(self.a) == pandas.core.series.Series: self.a = RamanSpectrum(self.a) #result = fitspectrum(self.a, (float(self.a.index[self.startidx]),float(self.a.index[self.endidx+1])),'Custom',self.guess,function = function) print self.guess result = fitspectrum(self.a, (self.startfreq,self.endfreq),'Custom',self.guess,function = function) fittingparameters = result[0] xfit = result[1] yfit = result[2] if result == -1: tkMessageBox.showerror('Fit Failed') return 0 z = list(fittingparameters[0]) self.canvas.restore_region(self.background) self.ax1.lines[-1].set_xdata(xfit) self.ax1.lines[-1].set_ydata(yfit) for l in self.ax1.lines: self.ax1.draw_artist(l) for i in range(len(self.w_name)): #self.scale_list[i].set(result[0][i]) if "w" in self.w_name[i]: self.ax1.axvline(x = z[i], ymin = 0, ymax = 1 ,color = 'r') self.canvas.blit(self.ax1.bbox) self.t.insert(END, " \nResult Found for " + str(self.name)) self.t.insert(END, " \nReferenced to IR spectrum " +self.reference_name) self.t.insert(END, " \nNormalized by constant "+str(self.normalization_constant)) for i in range(len(self.w_name)): self.t.insert(END, ' \n'+self.w_name[i]+': '+str(z[i])) return 0 def DisplaySpectrum(self): import re global name_list, str_name_list file_opt = options = {} options['defaultextension'] = '.spe' options['filetypes'] = [('all files', '.*'),('SPE files', '.SPE'), ('csv files','.csv'),('text files','.txt')] options['title'] = 'Open Spectrum...' options['initialdir'] = '/home/chris/Documents/DataWeiss' str_name_list = tkFileDialog.askopenfilename(**options) if str_name_list == '': return -1 return str_name_list
class Root(Widget, Generic[_T]): tk_widget: Optional[tk.Frame] canvas: Optional[FigureCanvasTkAgg] background_img: Optional[FigureImage] def __init__(self, params, w: int = 1429, h: int = 799, dpi: int = 141, color: str = '#000000', **kwargs) -> None: Widget.__init__(self) self.params = params self.w = w self.h = h self.dpi = dpi self.face_color = color matplotlib_config() self.fig: plt.Figure = plt.figure( figsize=(self.w / self.dpi, self.h / self.dpi), dpi=self.dpi, facecolor=self.face_color, ) self.gs = self.fig.add_gridspec(nrows=90, ncols=160, left=0, right=1, top=1, bottom=0, hspace=0, wspace=0) self.properties = kwargs self.artists = {} self.tk_widget = None self.canvas = None self.background_img = None self._bg = None self._initialized = None self.__post_init__() def for_tk(self, parent: Union[tk.Tk, tk.Frame, tk.Canvas]) -> tk.Canvas: self.tk_widget = parent self.canvas = FigureCanvasTkAgg(self.fig, master=parent) widget = self.canvas.get_tk_widget() widget.pack(fill=tk.BOTH, expand=1) return widget @staticmethod def load_img(fp: PATH_LIKE) -> np.array: return plt.imread(cbook.get_sample_data(fp)) @timer def save_img(self, fp: PATH_LIKE) -> None: self.fig.savefig(fp, transparent=True) @timer def set_background_from_img(self, img_array: np.array) -> None: self.background_img = plt.figimage(img_array) self.background_img.set_zorder(-999) def update(self, iteration_data: ITERATION_DATA) -> None: raise NotImplementedError def init(self) -> None: self._initialized = self._initialized or self.init_results() or True self.reset_results() self._bg = self._bg or self.canvas.copy_from_bbox( self.canvas.figure.bbox) self.draw_artists() def draw_artists(self) -> None: self.canvas.restore_region(self._bg) list(map(self.canvas.figure.draw_artist, self.animated)) self.canvas.blit(self.canvas.figure.bbox) def __call__(self, iteration_data: List[ITERATION_DATA]): if hasattr(iteration_data, '__iter__'): list(map(self.update, iteration_data)) else: self.update(iteration_data) self.draw_artists() def _set_background(self) -> None: pass def populate_from_iteration(self, iteration) -> None: raise NotImplementedError def _init_results(self) -> None: pass def set_result(self, *args) -> None: pass def _reset_results(self) -> None: pass
class TensileTestView(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) # Initialize this class as a frame self.animationBgCol = "black" # Animation canvas background colour # Create and configure main frame mainArea = tk.Frame(self, bg=st.MAIN_AREA_BG) mainArea.grid(row=0, column=0, sticky="nsew") mainArea.grid_propagate(0) self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) # Create and configure main grids grid = tuple([i for i in range(20)]) mainArea.grid_columnconfigure(grid, weight=1, minsize=50) mainArea.grid_rowconfigure(grid, weight=1, minsize=35) # Create title label = tk.Label(mainArea, text="Mechanical Workshop", font=st.LARGE_FONT, bg=st.MAIN_AREA_BG, fg=st.TITLE_COLOUR) label.grid(row=0, column=14, columnspan=6, sticky='nse') # Create description label = tk.Label( mainArea, text="We are now going to perform a tensile test on the " + "sheet of aluminum that we cold rolled! We can see our sample of aluminum on the left side of " + "the screen as well as a stress-strain graph on the right side of the screen. " + "Press the start button to begin the tensile test!", bg=st.INPUT_BG, wraplength=650, fg='black', justify='left', relief='ridge') label.grid(row=1, column=6, rowspan=2, columnspan=14, sticky='nesw') # Canvas for graphic simulation self.height = 595 self.width = 250 self.animationWindow = tk.Canvas(mainArea, bg=self.animationBgCol, height=self.height, width=self.width, bd=0, highlightthickness=0, relief='ridge') self.animationWindow.grid(row=2, column=1, columnspan=5, rowspan=17, sticky='ne') # Create Plot self.f = Figure(figsize=(6, 6), dpi=100) self.a = self.f.add_subplot(111) self.a.grid(color='grey', linestyle='-', linewidth=0.3) self.a.set_ylabel('Stress (MPa)') self.a.set_xlabel('Strain (-)') self.line = self.a.plot([], [], "-") self.a.format_coord = lambda x, y: "Strain (-)={:6.4f}, Stress (MPa)={:6.3f}".format( x, y) # Create canvas for plot self.graphWindow = FigureCanvasTkAgg(self.f, mainArea) self.graphWindow.get_tk_widget().grid(row=5, column=6, rowspan=12, columnspan=12) self.BG = self.graphWindow.copy_from_bbox(self.a.bbox) self.OG = self.BG # Load arrow images self.arrow = ImageTk.PhotoImage(file=st.IMG_PATH + "left_arrow.png") self.arrowClicked = ImageTk.PhotoImage(file=st.IMG_PATH + "left_arrow_clicked.png") self.arrowDisabled = ImageTk.PhotoImage(file=st.IMG_PATH + "left_arrow_disabled.png") # Arrow for previous page self.nextPage = tk.Button(mainArea, image=self.arrow, borderwidth=0) self.nextPage.image = self.arrow self.nextPage.grid(row=9, rowspan=2, column=0, sticky='nesw') # Button to start simulation self.animationButton = ttk.Button(mainArea, text="Start") self.animationButton.grid(row=3, rowspan=2, column=6, columnspan=3, sticky='nesw') # Text for displaying cold work self.CW_text = tk.Label(mainArea, text='', borderwidth=2, bg=st.INPUT_BG, font=st.MEDIUM_FONT) self.CW_text.grid(row=0, rowspan=2, column=2, columnspan=3, sticky='nsew') # Draw the rod self.rod = Rod(self.animationWindow, self.width, self.height, self.animationBgCol) self.rod.drawRod() # Create toolbar for plot self.toolbar = NavigationToolbar2Tk(self.graphWindow, mainArea) self.toolbar.update() self.toolbar.grid(row=17, column=6, rowspan=2, columnspan=10, sticky='nsew') # Draw mac logo logoImg = ImageTk.PhotoImage(Image.open(st.IMG_PATH + "macLogo.png")) canvas = tk.Canvas(mainArea, bg=st.MAIN_AREA_BG, width=130, height=71, bd=0, highlightthickness=0, relief='ridge') canvas.create_image(130 / 2, 71 / 2, image=logoImg, anchor="center") canvas.image = logoImg canvas.grid(row=17, column=17, rowspan=3, columnspan=3) def setGraphSize(self, x, y): """ Set the graph size. Assume it is called before any other function """ self.a.set_xlim([-0.01, x + 0.3]) self.a.set_ylim([0, y + 50]) self.graphWindow.draw() self.BG = self.graphWindow.copy_from_bbox(self.a.bbox) def pressArrow(self): """ Change arrow picture to "pressed arrow" """ self.nextPage.config(image=self.arrowClicked) self.nextPage.image = self.arrowClicked def normalArrow(self): """ Change arrow picture to "normal arrow" """ self.nextPage.config(image=self.arrow) self.nextPage.image = self.arrow def disableArrow(self): """ Change arrow picture to "disabled arrow" """ self.nextPage.config(image=self.arrowDisabled) self.nextPage.image = self.arrowDisabled def resetCanvas(self): """ Reset the rod on canvas """ self.rod.resetRod() def setCW(self, val): """ Set the coldwork text """ self.CW_text.config(text="%CW = " + str(val)) def updateGraph(self, xvals, yvals): """ Update the graph with xvals and yvals. This function is called at a given framerate """ self.line[0].set_data(xvals, yvals) self.graphWindow.restore_region(self.BG) self.a.draw_artist(self.line[0]) self.graphWindow.blit(self.a.bbox) self.graphWindow.flush_events() def updateAnimation(self, elongation, widthFact, neckWidthFact, neckHeightFact): """ Update the canvas based on given values. This function is called at a given framerate """ self.rod.updateRod(elongation, widthFact, neckWidthFact, neckHeightFact) def generateFracture(self, EL): """ Generate a fracture in the Rod. Function is called at the end of the animation """ self.rod.drawFracture(EL)
class MultichannelPlot(matplotlib.figure.Figure): def __init__(self, master, row=0, column=0, rowspan=5, columnspan=5, width=5, height=3, update_prot="blit"): matplotlib.figure.Figure.__init__(self, figsize=(width, height)) self.update_prot = update_prot self.frame = Frame(master=master) self.frame.config(bd=5) self.frame.grid(row=row, column=column, padx=5, pady=5) self.channel_list = [] self.array_list = [] self.color_list = ["r", "b", "g", "c", "m", "y", "k"] self.a = self.add_subplot(MultichannelAxis(self, 111)) self.canvas = FigureCanvasTkAgg(self, master=self.frame) self.canvas.show() self.canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=True) self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.frame) self.toolbar.update() self.toolbar.pack(side=BOTTOM, fill=BOTH, expand=True) # ,expand = True)#fill=BOTH)#, expand=1) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.a.bbox) return None def AddChannel(self, spec_array, channel_type, color=None): if color == None or color == "": self.a.add_line(Channel([], [], self, spec_array, channel_type, color=self.color_list[0])) self.color_list.append(self.color_list[0]) del (self.color_list[0]) else: self.a.add_line(Channel([], [], self, spec_array, channel_type, color=color)) # self.Update() self.Redraw() return self.a.lines[-1] def RemoveChannel(self, channel): self.a.lines.remove(channel) self.Redraw() return 0 def SetChannelColor(self, channel, clr): if clr != "": channel.color = clr else: channel.color = self.color_list[0] self.color_list.append(self.color_list[0]) del (self.color_list[0]) return 0 def ShowChannel(self, channel): channel.set_visible(True) self.Update() return 0 def HideChannel(self, channel): channel.set_visible(False) self.Update() return 0 def Redraw(self): self.a.relim() self.a.autoscale_view(tight=False) self.canvas.draw() self.canvas.restore_region(self.background) for line in self.a.lines: self.a.draw_artist(line) self.canvas.blit(self.a.bbox) return 0 def SaveFigure(self, filename): self.f_copy = self.a self.f_copy.savefig("a.png") return 0 def Update(self): if self.update_prot == "draw": self.canvas.draw() return 0 (lower_y_lim, upper_y_lim) = self.a.get_ylim() (lower_x_lim, upper_x_lim) = self.a.get_xlim() scaley_bool = False scalex_bool = False scaley_down_bool = True for channel in self.a.lines: if channel.get_visible() == True: channel.ChannelUpdate() scaley_bool = ( scaley_bool or max(channel.get_ydata()) > upper_y_lim or min(channel.get_ydata()) < lower_y_lim ) scaley_down_bool = scaley_down_bool and max(channel.get_ydata()) < upper_y_lim * 0.6 scalex_bool = ( scalex_bool or max(channel.get_xdata()) != upper_x_lim or min(channel.get_xdata()) != lower_x_lim ) if scaley_bool or scalex_bool or scaley_down_bool: self.Redraw() else: self.canvas.restore_region(self.background) for line in self.a.lines: self.a.draw_artist(line) self.canvas.blit(self.a.bbox) return 0
class Gui : """ A tkinter GUI to quickly visualize ARPES data, i.e. cuts and maps. Should be built in a modular fashion such that any data reader can be 'plugged in'. data : 3D array; the raw data from the selected file. This is expected to be in the shape (z, x, y) (z may be equal to 1) pp_data : 3D array; the postprocessed data to be displayed. cmaps : list of str; a list of available matplotlib colormap names. xscale, yscale, zscale : 1D arrays; the x, y and z-axis data. cursor_xy : tuple; current location of the cursor in the bottom left plot. dpi : int; resolution at which to save .png`s. """ data = STARTUP_DATA pp_data = STARTUP_DATA.copy() cmaps = CMAPS xscale = None yscale = None zscale = None cursor_xy = None dpi = DPI main_mesh = None vmain_mesh = None cut1 = None cut2 = None def __init__(self, master, filename=None) : """ This init function mostly just calls all 'real' initialization functions where the actual work is outsourced to. """ # Create the main container/window frame = tk.Frame(master) # Define some elements self._set_up_load_button(master) self._set_up_pp_selectors(master) self._set_up_plots(master) self._set_up_colormap_sliders(master) self._set_up_z_slider(master) self._set_up_integration_range_selector(master) self._set_up_status_label(master) # Align all elements self._align() # Load the given file if filename : self.filepath.set(filename) self.load_data() # The setup of event handling requires there to be some data already self._set_up_event_handling() def _align(self) : """ Use the grid() layout manager to align the elements of the GUI. At this stage of development the grid has 12 columns of unequal size. """ # The plot takes up the space of PLOT_COLUMNSPAN widgets PLOT_COLUMNSPAN = 8 PLOT_ROWSPAN = 3 N_PATH_FIELD = PLOT_COLUMNSPAN # 'Load file' elements LOADROW = 0 c = 0 self.browse_button.grid(row=LOADROW, column=c, sticky='ew') c += 1 self.load_button.grid(row=LOADROW, column=c, sticky='ew') c += 1 self.decrement_button.grid(row=LOADROW, column=c, sticky='ew') c += 1 self.increment_button.grid(row=LOADROW, column=c, sticky='ew') c += 1 self.path_field.grid(row=LOADROW, column=c, columnspan=N_PATH_FIELD, sticky='ew') # Postprocessing selectors PPROW = LOADROW + 1 c = 0 for lst in self.radiobuttons : r = 0 for btn in lst : btn.grid(row=PPROW+r, column=c, sticky='ew') r += 1 c += 1 # Plot & colormap sliders & selector PLOTROW = PPROW + max([len(lst) for lst in self.radiobuttons]) PLOTCOLUMN = 1 self.canvas.get_tk_widget().grid(row=PLOTROW, column=PLOTCOLUMN, rowspan=PLOT_ROWSPAN, columnspan=PLOT_COLUMNSPAN) right_of_plot = PLOT_COLUMNSPAN + PLOTCOLUMN self.cm_min_slider.grid(row=PLOTROW, column=right_of_plot + 1) self.cm_max_slider.grid(row=PLOTROW, column=right_of_plot + 2) self.cmap_dropdown.grid(row=PLOTROW + 1, column=right_of_plot + 1) self.invert_cmap_checkbutton.grid(row=PLOTROW + 1, column=right_of_plot + 2) # Save png button self.save_button.grid(row=PLOTROW + 2, column=right_of_plot + 1) # z slider, integration range selector self.z_slider.grid(row=PLOTROW, column=0) self.integration_range_entry.grid(row=PLOTROW+1, column=0) # Put the status label at the very bottom left STATUSROW = PLOTROW + PLOT_ROWSPAN + 1 self.status_label.grid(row=STATUSROW, column=0, columnspan=10, sticky='ew') def _set_up_load_button(self, master) : """ Add a button which opens a filebrowser to choose the file to load and a textbox (Entry widget) where the filepath can be changed. """ # Define the Browse button self.browse_button = tk.Button(master, text='Browse', command=self.browse) # and the Load button self.load_button = tk.Button(master, text='Load', command=self.load_data) # and the entry field which holds the path to the current file self.filepath = tk.StringVar() self.path_field = tk.Entry(master, textvariable=self.filepath) # Also add inc and decrement buttons self.increment_button = tk.Button(master, text='>', command=lambda : self.increment(1)) self.decrement_button = tk.Button(master, text='<', command=lambda : self.increment(-1)) # Add a 'save' button for creating png s self.save_button = tk.Button(master, text='Save png', command=self.save_plot) def _set_up_pp_selectors(self, master) : """ Create radiobuttons for the selction of postprocessing methods. The order of the pp methods in all lists is: 0) Make map 1) BG subtraction 2) Normalization 3) derivative """ # Create control variables to hold the selections and store them in a # list for programmatic access later on self.map = tk.StringVar() self.subtractor = tk.StringVar() self.normalizer = tk.StringVar() self.derivative = tk.StringVar() self.selection = [self.map, self.subtractor, self.normalizer, self.derivative] # Create sets of radiobuttons and set all to default value 'Off' self.radiobuttons = [] for i, D in enumerate(PP_DICTS) : variable = self.selection[i] variable.set('Off') self.radiobuttons.append([]) for key in D : rb = tk.Radiobutton(master, text=key, variable=variable, value=key, command=self.process_data, indicatoron=0) self.radiobuttons[i].append(rb) def _set_up_plots(self, master) : """ Take care of all the matplotlib stuff for the plot. """ fig = Figure(figsize=FIGSIZE) fig.patch.set_alpha(0) ax_cut1 = fig.add_subplot(221) ax_cut2 = fig.add_subplot(224) ax_map = fig.add_subplot(223)#, sharex=ax_cut1, sharey=ax_cut2) ax_energy = fig.add_subplot(222) # Virtual figure and ax for creation of png's self.vfig = Figure(figsize=VFIGSIZE) vax = self.vfig.add_subplot(111) self.vcanvas = FigureCanvasTkAgg(self.vfig, master=master) # Remove padding between min and max of data and plot border ax_cut2.set_ymargin(0) # Move ticks to the other side ax_energy.xaxis.tick_top() ax_energy.yaxis.tick_right() ax_cut1.xaxis.tick_top() ax_cut2.yaxis.tick_right() # Get a handle on all axes through the dictionary self.axes self.axes = {'cut1': ax_cut1, 'cut2': ax_cut2, 'map': ax_map, 'energy': ax_energy, 'vax': vax} # Set bg color for ax in self.axes.values() : ax.set_facecolor(BGCOLOR) # Remove spacing between plots fig.subplots_adjust(wspace=PLOT_SPACING, hspace=PLOT_SPACING) self.canvas = FigureCanvasTkAgg(fig, master=master) self.canvas.show() def _set_up_colormap_sliders(self, master) : """ Add the colormap adjust sliders, set their starting position and add its binding such that it only triggers upon release. Also, couple them to the variables vmin/max_index. Then, also create a dropdown with all available cmaps and a checkbox to invert the cmap. """ self.vmin_index = tk.IntVar() self.vmax_index = tk.IntVar() cm_slider_kwargs = { 'showvalue' : 0, 'to' : CM_SLIDER_RESOLUTION, 'length':SLIDER_LENGTH } self.cm_min_slider = tk.Scale(master, variable=self.vmin_index, label='Min', **cm_slider_kwargs) self.cm_min_slider.set(CM_SLIDER_RESOLUTION) self.cm_max_slider = tk.Scale(master, variable=self.vmax_index, label='Max', **cm_slider_kwargs) self.cm_max_slider.set(0) # StringVar to keep track of the cmap and whether it's inverted self.cmap = tk.StringVar() self.invert_cmap = tk.StringVar() # Default to the first cmap self.cmap.set(self.cmaps[0]) self.invert_cmap.set('') # Register callbacks for colormap-range change for var in [self.vmin_index, self.vmax_index, self.cmap, self.invert_cmap] : var.trace('w', self.redraw_mesh) # Create the dropdown menu, populated with all strings in self.cmaps self.cmap_dropdown = tk.OptionMenu(master, self.cmap, *self.cmaps) # And a button to invert self.invert_cmap_checkbutton = tk.Checkbutton(master, text='Invert', variable=self.invert_cmap, onvalue='_r', offvalue='') def _set_up_z_slider(self, master) : """ Create a Slider which allows to select the z value of the data. This value is stored in the DoubleVar self.z """ self.z = tk.IntVar() self.z.set(0) self.zmax = tk.IntVar() self.zmax.set(1) self.z_slider = tk.Scale(master, variable=self.z, label='z', to=self.zmax.get(), showvalue=1, length=SLIDER_LENGTH) self.z_slider.bind('<ButtonRelease-1>', self.process_data) def _set_up_integration_range_selector(self, master) : """ Create widgets that will allow setting the integration range when creating a map. """ self.integrate = tk.IntVar() self.integration_range_entry = tk.Entry(master, width=3, textvariable=self.integrate) def _set_up_status_label(self, master) : """ Create a label which can hold informative text about the current state of the GUI or success/failure of certain operations. This text is held in the StringVar self.status. """ self.status = tk.StringVar() # Initialize the variable with the default status self.update_status() self.status_label = tk.Label(textvariable=self.status, justify=tk.LEFT, anchor='w') def redraw_mesh(self, *args, **kwargs) : """ Efficiently redraw the pcolormesh without having to redraw the axes, ticks, labels, etc. """ # Get the new colormap parameters and apply them to the pcolormesh cmap = self.get_cmap() vmin, vmax = self.vminmax(self.pp_data) mesh = self.main_mesh mesh.set_clim(vmin=vmin, vmax=vmax) mesh.set_cmap(cmap) # Redraw the mesh ax = self.axes['map'] ax.draw_artist(mesh) # Cursors need to be redrawn - the blitting happens there self.redraw_cursors() # Also redraw the cuts if they are pcolormeshes if self.map.get() != 'Off' : self.redraw_cuts() def redraw_cuts(self, *args, **kwargs) : """ Efficiently redraw the cuts (meshes or lines, depending on state of `self.map`) without having to redraw the axes, ticks, labels, etc. """ axes = [self.axes['cut1'], self.axes['cut2']] data = [self.cut1, self.cut2] artists = [self.cut1_plot, self.cut2_plot] cmap = self.get_cmap() for i,ax in enumerate(axes) : artist = artists[i] if self.map.get() != 'Off' : vmin, vmax = self.vminmax(data[i]) artist.set_clim(vmin=vmin, vmax=vmax) artist.set_cmap(cmap) ax.draw_artist(artist) self.canvas.blit(ax.bbox) def redraw_cursors(self, *args, **kwargs) : """ Efficiently redraw the cursors in the bottom left plot without having to redraw the axes, ticks, labels, etc. """ ax = self.axes['map'] #self.canvas.restore_region(self.bg_mesh) ax.draw_artist(self.xcursor) ax.draw_artist(self.ycursor) self.canvas.blit(ax.bbox) def update_z_slider(self, state) : """ Change the relief of the z slider to indicate that it is inactive/active. Also update the z slider range""" if state == 'active' : config = dict(sliderrelief='raised', showvalue=1) else : config = dict(sliderrelief='flat', showvalue=0) self.zmax.set( len(self.data) - 1) config.update(dict(to=self.zmax.get())) self.z_slider.config(**config) def get_filename(self) : """ Return the filename (without path) of the currently selected file. """ return self.filepath.get().split('/')[-1] def increment(self, plusminus) : # Get the path and the name of the current file filepath = self.filepath.get() split = filepath.split('/') # If just a filename is given, assume the current directory if filepath[0] is not '/' : path = './' else : #path = '/' + '/'.join(split[:-1]) + '/' path = '/'.join(split[:-1]) + '/' old_filename = split[-1] # Get a list of the files in the current directory dir_content = sorted( os.listdir(path) ) dir_size = len(dir_content) # Get the index of the current file in the directory index = dir_content.index(old_filename) # Raise/lower the index until a not-obiously skippable entry is found while True : index += plusminus # Cycle through the list index %= dir_size new_filename = dir_content[index] suffix = new_filename.split('.')[-1] if suffix not in SKIPPABLE : self.filepath.set(path+new_filename) break def update_status(self, status=DEFAULT_STATUS) : """ Update the status StringVar with the current time and the given status argument. """ now = datetime.now().strftime('%H:%M:%S') new_status = '[{}] {}'.format(now, status) self.status.set(new_status) def browse(self) : """ Open a filebrowser dialog and put the selected file into self.path_field. """ # If the file entry field already contains a path to a file use it # as the default file for the browser old_filepath = self.filepath.get() if old_filepath : default_file = old_filepath initialdir = None else : default_file = None initialdir = '/' # Open a browser dialog new_filepath = askopenfilename(initialfile=default_file, initialdir=initialdir) # Update the path only if a selection was made if new_filepath != "" : self.filepath.set(new_filepath) def load_data(self) : """ Load data from the file currently selected by self.filepath. And reset and prepare several things such that the GUI is able to handle the new data properly. """ # Show the user that something is happening self.update_status('Loading data...') # Try to load the data with the given dataloader try : ns = dl.load_data(self.filepath.get()) except Exception as e : print(e) self.update_status('Failed to load data.') # Leave the function return 1 # Extract the fields from the namespace self.data = ns.data self.xscale = ns.xscale self.yscale = ns.yscale try : self.zscale = ns.zscale except KeyError : # Set zscale to None self.zscale = None pass # Notify user of success self.update_status('Loaded data: {}.'.format(self.get_filename())) # Update the max z value/toggle z slider (should better be handled in # background by tkinter) if self.data.shape[0] == 1 : self.update_z_slider('disabled') else : self.update_z_slider('active') # Initiate new cursors self.cursor_xy = None # The postprocessing also creates copy of the raw data and replots self.process_data() def process_data(self, event=None) : """ Apply all the selected postprocessing routines in the following order: 0) Make map 1) bg subtraction 2) normalization 3) derivative """ # Retain a copy of the raw data self.pp_data = self.data.copy() z = self.z.get() # Make a map if necessary if self.map.get() != 'Off' : integrate = self.integrate.get() self.pp_data = pp.make_slice(self.pp_data, d=0, i=z, integrate=integrate) #self.pp_data = pp.make_map(self.pp_data, z, integrate=integrate) # Need to reshape shape = self.pp_data.shape self.pp_data = self.pp_data.reshape(1, shape[0], shape[1]) # Apply all pp, unless they are set to 'Off' (None) for i, D in enumerate(PP_DICTS) : pp_operation = D[self.selection[i].get()] if pp_operation : self.pp_data = pp_operation(self.pp_data) #self.pp_data[z,:,:] = pp_operation(self.pp_data[z,:,:]) # Replot self.plot_data() def plot_intensity(self) : """ Plot the binding energy distribution in the top right if we have a map. """ # Clear the current distribution ax = self.axes['energy'] ax.clear() # Write the value of the energy in the upper right plot z = self.z.get() if self.zscale is not None : z_val = self.zscale[z] else : z_val = z ax.text(0.1, 0.05, z_val, color='red', transform=ax.transAxes) # Nothing else to do if we don't have a map if self.map.get() == 'Off' : return # Get the energies and number of energies if self.zscale is not None : energies = self.zscale else : energies = np.arange(len(self.data)) N_e = len(energies) # Get the intensities intensities = [] for i in range(N_e) : this_slice = self.data[i,:,:] intensity = sum( sum(this_slice) ) intensities.append(intensity) # Plot energy distribution ax.plot(energies, intensities, **intensity_kwargs) # Plot a cursor indicating the current value of z y0 = min(intensities) y1 = max(intensities) ylim = [y0, y1] ax.plot(2*[z_val], ylim, **intensity_cursor_kwargs) ax.set_ylim(ylim) def calculate_cuts(self) : """ """ if self.map.get() != 'Off' : # Create a copy of the original map (3D) data data = self.data.copy() # Slice and dice it self.cut1 = pp.make_slice(data, d=1, i=self.yind, integrate=1) self.cut2 = pp.make_slice(data, d=2, i=self.xind, integrate=1) else : z = self.z.get() self.cut1 = self.pp_data[z, self.yind, :] self.cut2 = self.pp_data[z, :, self.xind] def plot_cuts(self) : """ Plot cuts of whatever is in the bottom left ('map') axis along the current positions of the cursors. """ self.calculate_cuts() # Clear the current cuts for ax in ['cut1', 'cut2'] : self.axes[ax].clear() # Get the right xscale/yscale information xscale, yscale = self.get_xy_scales() if self.map.get() != 'Off' : kwargs = dict(cmap=self.get_cmap()) # Ensure zscale is defined if self.zscale is None : zscale = np.arange(self.cut1.shape[0]) else : zscale = self.zscale # Plot x cut in upper left vmin, vmax = self.vminmax(self.cut1) kwargs.update(dict(vmin=vmin, vmax=vmax)) self.cut1_plot = self.axes['cut1'].pcolormesh(xscale, zscale, self.cut1, **kwargs) # Plot y cut in lower right (rotated by 90 degrees) vmin, vmax = self.vminmax(self.cut2) kwargs.update(dict(vmin=vmin, vmax=vmax)) self.cut2_plot = self.axes['cut2'].pcolormesh(zscale, yscale, self.cut2.T, **kwargs) else : # Plot the x cut in the upper left self.cut1_plot = self.axes['cut1'].plot(xscale, self.cut1, **cut_kwargs)[0] # Plot the y cut in the lower right self.cut2_plot = self.axes['cut2'].plot(self.cut2, yscale, **cut_kwargs)[0] # Make sure the cut goes over the full range of the plot #self.axes['cut2'].set_ymargin(0) # For some reason this doesn't work ymin = min(yscale) ymax = max(yscale) self.axes['cut2'].set_ylim([ymin, ymax]) def get_plot_args_and_kwargs(self) : """ Prepare args and kwargs for plotting, depending on the circumstances. """ # Add x and y scales to args if available args = [] if self.xscale is not None and self.yscale is not None : args.append(self.xscale) args.append(self.yscale) # Use z=0 in case of a map (as pp_data is of length 1 along this # dimension as a result of pp_make_cut()) if self.map.get() != 'Off' : z = 0 else : z = self.z.get() args.append(self.pp_data[z,:,:]) vmin, vmax = self.vminmax(self.pp_data) kwargs = dict(cmap=self.get_cmap(), vmin=vmin, vmax=vmax) return args, kwargs def plot_data(self, event=None, *args, **kwargs) : """ Update the colormap range and (re)plot the data. """ # Remove old plots for ax in self.axes.values() : ax.clear() args, kwargs = self.get_plot_args_and_kwargs() # Do the actual plotting with just defined args and kwargs ax = self.axes['map'] self.main_mesh = ax.pcolormesh(*args, **kwargs) self.bg_mesh = self.canvas.copy_from_bbox(ax.bbox) # Update the cursors (such that they are above the pcolormesh) and cuts self.plot_cursors() self.plot_cuts() self.plot_intensity() self.canvas.draw() def get_cmap(self) : """ Build the name of the colormap by combining the value stored in `self.cmap` (the basename of the colormap) and `self.invert_cmap` (either empty string or '_r', which is the suffix for inverted cmaps in matplotlib) """ # Build the name of the cmap cmap_name = self.cmap.get() + self.invert_cmap.get() # Make sure this name exists in the list of cmaps. Otherwise reset to # default cmap try : cmap = get_cmap(cmap_name) except ValueError : # Notify user message = \ 'Colormap {} not found. Using default instead.'.format(cmap_name) self.update_status(message) # Set default cmap = get_cmap(self.cmaps[0]) return cmap def vminmax(self, data) : """ Helper function that returns appropriate values for vmin and vmax for a given set of data. """ # Note: vmin_index goes from 100 to 0 and vice versa for vmax_index. # This is to turn the sliders upside down. # Crude method to avoid unreasonable colormap settings if self.vmin_index.get() < self.vmax_index.get() : self.vmin_index.set(CM_SLIDER_RESOLUTION) # Split the data value range into equal parts #drange = np.linspace(self.pp_data.min(), data.max(), # CM_SLIDER_RESOLUTION + 1) drange = np.linspace(data.min(), data.max(), CM_SLIDER_RESOLUTION + 1) # Get the appropriate vmin and vmax values from the data vmin = drange[CM_SLIDER_RESOLUTION - self.vmin_index.get()] vmax = drange[CM_SLIDER_RESOLUTION - self.vmax_index.get()] return vmin, vmax def get_xy_minmax(self) : """ Return the min and max for the x and y axes, depending on whether xscale and yscale are defined. """ xscale, yscale = self.get_xy_scales() xmin = min(xscale) xmax = max(xscale) ymin = min(yscale) ymax = max(yscale) return xmin, xmax, ymin, ymax def get_xy_scales(self) : """ Depending on whether we have actual data scales (self.xscale and self.yscale are defined) or not, return arrays which represent data coordinates. """ if self.xscale is None or self.yscale is None : shape = self.data.shape yscale = np.arange(0, shape[1], 1) xscale = np.arange(0, shape[2], 1) else : xscale = self.xscale yscale = self.yscale return xscale, yscale def snap_to(self, x, y) : """ Return the closest data value to the given values of x and y. """ xscale, yscale = self.get_xy_scales() # Find the index where element x/y would have to be inserted in the # sorted array. self.xind = np.searchsorted(xscale, x) self.yind = np.searchsorted(yscale, y) # Find out whether the lower or upper 'neighbour' is closest x_lower = xscale[self.xind-1] y_lower = yscale[self.yind-1] # NOTE In principle, these IndexErrors shouldn't occur. Try-except # only helps when debugging. try : x_upper = xscale[self.xind] except IndexError : x_upper = max(xscale) try : y_upper = yscale[self.yind] except IndexError : y_upper = max(yscale) dx_upper = x_upper - x dx_lower = x - x_lower dy_upper = y_upper - y dy_lower = y - y_lower # Assign the exact data value and update self.xind/yind if necessary if dx_upper < dx_lower : x_snap = x_upper else : x_snap = x_lower self.xind -= 1 if dy_upper < dy_lower : y_snap = y_upper else : y_snap = y_lower self.yind -= 1 return x_snap, y_snap def plot_cursors(self) : """ Plot the cursors in the bottom left axis. """ # Delete current cursors (NOTE: this is dangerous if there are any # other lines in the plot) ax = self.axes['map'] ax.lines = [] # Retrieve information about current data range xmin, xmax, ymin, ymax = self.get_xy_minmax() xlimits = [xmin, xmax] ylimits = [ymin, ymax] # Initiate cursors in the center of graph if necessary if self.cursor_xy is None : x = 0.5 * (xmax + xmin) y = 0.5 * (ymax + ymin) # Keep a handle on cursor positions self.cursor_xy = (x, y) else : x, y = self.cursor_xy # Make the cursor snap to actual data points x, y = self.snap_to(x, y) # Plot cursors and keep handles on them (need the [0] because plot() # returns a list of Line objects) self.xcursor = ax.plot([x, x], ylimits, zorder=3, **cursor_kwargs)[0] self.ycursor = ax.plot(xlimits, [y, y], zorder=3, **cursor_kwargs)[0] def _set_up_event_handling(self) : """ Define what happens when user clicks in the plot (move cursors to clicked position) or presses an arrow key (move cursors in specified direction). """ def on_click(event): event_ax = event.inaxes if event_ax == self.axes['map'] : self.cursor_xy = (event.xdata, event.ydata) self.plot_cursors() # Also update the cuts self.plot_cuts() self.canvas.draw() elif event_ax == self.axes['energy'] and \ self.map.get() != 'Off' : if self.zscale is not None : z = np.where(self.zscale > event.xdata)[0][0] else : z = int(event.xdata) self.z.set(z) # Since z changed we need to apply the whole data processing # and replot self.process_data() def on_press(event): # Get the name of the pressed key and info on the current cursors key = event.key x, y = self.cursor_xy xmin, xmax, ymin, ymax = self.get_xy_minmax() # Stop if no arrow key was pressed if key not in ['up', 'down', 'left', 'right'] : return # Move the cursor by one unit in data points xscale, yscale = self.get_xy_scales() dx = xscale[1] - xscale[0] dy = yscale[1] - yscale[0] # In-/decrement cursor positions depending on what button was # pressed and only if we don't leave the axis if key == 'up' and y+dy <= ymax : y += dy elif key == 'down' and y-dy >= ymin : y -= dy elif key == 'right' and x+dx <= xmax : x += dx elif key == 'left' and x-dx >= xmin: x -= dx # Update the cursor position and redraw it self.cursor_xy = (x, y) #self.plot_cursors() self.redraw_cursors() # Now the cuts have to be redrawn as well self.calculate_cuts() #self.plot_cuts() self.redraw_cuts() cid = self.canvas.mpl_connect('button_press_event', on_click) pid = self.canvas.mpl_connect('key_press_event', on_press) # Inititate the cursors self.plot_cursors() def save_plot(self) : """ Save a png image of the currently plotted data (only what is in bottom left) """ # Plot the same thing into a virtual figure such that a png can be # created args, kwargs = self.get_plot_args_and_kwargs() self.vmain_mesh = self.axes['vax'].pcolormesh(*args, **kwargs) # Open a filebrowser where user can select a place to store the result filename = asksaveasfilename(filetypes=[('PNG', '*.png')]) if filename : self.vfig.savefig(filename, transparent=True, dpi=self.dpi) self.update_status('Saved file {}.'.format(filename)) else : self.update_status('Saving file aborted.')
class PlotFrame(tk.Frame): def __init__(self, master): tk.Frame.__init__(self, master) self.fwidth = 512 self.fheight = 720 self.configure(bd=2, width=self.fwidth, height=self.fheight, relief='sunken') self.grid_propagate(0) self.dpi = 100 self.f = Figure(figsize=(self.fwidth / self.dpi, self.fheight / self.dpi), dpi=self.dpi, facecolor=(1, 1, 1, 0)) self.canvas = FigureCanvasTkAgg(self.f, self) self.top = self.f.add_subplot(311) self.middle = self.f.add_subplot(312) self.bottom = self.f.add_subplot(313) self.xData = np.linspace(0, 100 * np.pi, 1000, endpoint=False) self.yDataB = np.sin(self.xData) self.yDataM = np.sin(self.xData) * np.sin(self.xData * 0.5 + .4) self.yDataT = np.sin(self.xData)**2 + np.cos(self.xData * 1.1 + 0.15) self.startTime = time.time() self.canvas.draw() self.backgroundB = self.canvas.copy_from_bbox(self.bottom.bbox) self.backgroundT = self.canvas.copy_from_bbox(self.top.bbox) self.backgroundM = self.canvas.copy_from_bbox(self.middle.bbox) self.pointsB = self.bottom.plot(self.xData, self.yDataB)[0] self.pointsM = self.middle.plot(self.xData, self.yDataM)[0] self.pointsT = self.top.plot(self.xData, self.yDataT)[0] def animate2(self, reScaled): scale = True if reScaled: self.f.clf() self.showCanvas.grid_forget() self.f = Figure(figsize=(self.fwidth / self.dpi, self.fheight / self.dpi), dpi=self.dpi, facecolor=(1, 1, 1, 0)) self.canvas = FigureCanvasTkAgg(self.f, self) self.top = self.f.add_subplot(311) self.middle = self.f.add_subplot(312) self.bottom = self.f.add_subplot(313) self.canvas.draw() self.backgroundB = self.canvas.copy_from_bbox(self.bottom.bbox) self.backgroundT = self.canvas.copy_from_bbox(self.top.bbox) self.backgroundM = self.canvas.copy_from_bbox(self.middle.bbox) self.pointsB = self.bottom.plot(self.xData, self.yDataB)[0] self.pointsM = self.middle.plot(self.xData, self.yDataM)[0] self.pointsT = self.top.plot(self.xData, self.yDataT)[0] scale = False if scale: t = time.time() - self.startTime N = len(self.xData) currentIdx = int(N * ((t / 4) % 1)) scalingNumber = N * 0.1 waveShape = np.exp(-np.arange(N) / scalingNumber)[::-1] colorArray = np.array([(1, 0, 0, a) for a in np.roll(waveShape, currentIdx)]) self.yDataB = np.roll(self.yDataB, 1) self.yDataT = np.roll(self.yDataT, 1) self.yDataM = np.roll(self.yDataM, 1) self.pointsB.set_data(self.xData, self.yDataB) self.pointsT.set_data(self.xData, self.yDataT) self.pointsM.set_data(self.xData, self.yDataM) self.canvas.restore_region(self.backgroundB) self.canvas.restore_region(self.backgroundT) self.canvas.restore_region(self.backgroundM) self.bottom.draw_artist(self.pointsB) self.middle.draw_artist(self.pointsM) self.top.draw_artist(self.pointsT) self.canvas.blit(self.bottom.bbox) self.canvas.blit(self.top.bbox) self.canvas.blit(self.middle.bbox) self.showCanvas = self.canvas.get_tk_widget() self.showCanvas.grid()