def openservice(self, club_id): #---------------------------------------------------------------------- ''' initialize service recommended that the overriding method save service instance in `self.service` must be overridden when ResultsCollect is instantiated :param club_id: club.id for club this service is operating on ''' # create location server self.locsvr = LocationServer() # remember club id we're working on self.club_id = club_id # debug file for races saved # set debugrace to False if not debugging debugrace = True if debugrace: clubslug = Club.query.filter_by(id=club_id).first().shname self.racefile = '{}/{}-athlinks-race.csv'.format(app.config['MEMBERSHIP_DIR'], clubslug) else: self.racefile = None if self.racefile: self._RACE = open(self.racefile, 'wb') self.racefields = 'id,name,date,distmiles,status,runner'.split(',') self.RACE = csv.DictWriter(self._RACE, self.racefields) self.RACE.writeheader() # open service key = ApiCredentials.query.filter_by(name=self.servicename).first().key self.service = athlinks.Athlinks(debug=True, key=key)
def summarize(thistask, club_id, sources, status, summaryfile, detailfile, resultsurl, minage=12, minagegrade=20, minraces=3 , mintrend=2, numyears=3, begindate=None, enddate=None): #---------------------------------------------------------------------- ''' render collected results :param thistask: this is required for task thistask.update_state() :param club_id: identifies club for which results are to be stored :param sources: list of sources / services we're keeping status for :param summaryfile: summary file name (.csv) :param detailfile: detail file name (.csv) :param resultsurl: base url to send results to, for link in summary table :param minage: minimum age to keep track of stats :param minagegrade: minimum age grade :param minraces: minimum races in the same year as enddate :param mintrend: minimum races over the full period for trendline :param begindate: render races between begindate and enddate, datetime :param enddate: render races between begindate and enddate, datetime ''' # get club slug and location for later club = Club.query.filter_by(id=club_id).first() clubslug = club.shname locsvr = LocationServer() clublocation = locsvr.getlocation(club.location) # get maxdistance by service services = RaceResultService.query.filter_by(club_id=club_id).join(ApiCredentials).all() maxdistance = {} for service in services: attrs = ServiceAttributes(club_id, service.apicredentials.name) # app.logger.debug('service {} attrs {}'.format(service, attrs.__dict__)) if attrs.maxdistance: maxdistance[service.apicredentials.name] = attrs.maxdistance else: maxdistance[service.apicredentials.name] = None maxdistance[productname] = None # set up date range. begindate and enddate take precedence, else use numyears from today if not (begindate and enddate): etoday = time.time() today = timeu.epoch2dt(etoday) begindate = datetime(today.year-numyears+1,1,1) enddate = datetime(today.year,12,31) firstyear = begindate.year lastyear = enddate.year yearrange = range(firstyear,lastyear+1) # get all the requested result data from the database and save in a data structure indexed by runner ## first get the data from the database results = RaceResult.query.join(Race).join(Runner).filter(RaceResult.club_id==club_id, Race.date.between(ftime.dt2asc(begindate), ftime.dt2asc(enddate)), Runner.member==True, Runner.active==True).order_by(Runner.lname, Runner.fname).all() ## then set up our status and pass to the front end for source in sources: status[source]['status'] = 'summarizing' status[source]['lastname'] = '' status[source]['processed'] = 0 status[source]['total'] = sum([1 for result in results if result.source==source]) thistask.update_state(state='PROGRESS', meta={'progress':status}) ## prepare to save detail file, for debugging detlfields = 'runnername,runnerid,dob,gender,resultid,racename,racedate,series,distmiles,distkm,time,timesecs,agpercent,source,sourceid'.split(',') detailfname = detailfile _DETL = open(detailfname,'wb') DETL = csv.DictWriter(_DETL,detlfields) DETL.writeheader() ## then fill in data structure to hold AnalyzeAgeGrade objects ## use OrderedDict to force aag to be in same order as DETL file, for debugging aag = collections.OrderedDict() for result in results: # skip results which are too far away, if a maxdistance is defined for this source if maxdistance[result.source]: locationid = result.race.locationid if not locationid: continue racelocation = Location.query.filter_by(id=locationid).first() distance = get_distance(clublocation, racelocation) if distance == None or distance > maxdistance[result.source]: continue thisname = (result.runner.name.lower(), result.runner.dateofbirth) initaagrunner(aag, thisname, result.runner.fname, result.runner.lname, result.runner.gender, ftime.asc2dt(result.runner.dateofbirth), result.runner.id) # determine location name. any error gets null string locationname = '' if result.race.locationid: location = Location.query.filter_by(id=result.race.locationid).first() if location: locationname = location.name thisstat = aag[thisname].add_stat(ftime.asc2dt(result.race.date), result.race.distance*METERSPERMILE, result.time, race=result.race.name, loc=locationname, fuzzyage=result.fuzzyage, source=result.source, priority=priority[result.source]) ### TODO: store result's agpercent, in AgeGrade.crunch() skip agegrade calculation if already present DETL.writerow(dict( runnername = result.runner.name, runnerid = result.runner.id, dob = result.runner.dateofbirth, gender = result.runner.gender, resultid = result.id, racename = result.race.name, racedate = result.race.date, series = result.series.name if result.seriesid else None, distmiles = result.race.distance, distkm = result.race.distance*(METERSPERMILE/1000), timesecs = result.time, time = rendertime(result.time,0), agpercent = result.agpercent, source = result.source, sourceid = result.sourceid, )) ## close detail file _DETL.close() # initialize summary file summfields = ['name', 'lname', 'fname', 'age', 'gender'] datafields = copy(summfields) distcategories = ['overall'] + [TRENDLIMITS[tlimit][0] for tlimit in TRENDLIMITS] datacategories = ['overall'] + [TRENDLIMITS[tlimit][1] for tlimit in TRENDLIMITS] stattypes = ['1yr agegrade','avg agegrade','trend','numraces','stderr','r-squared','pvalue'] statdatatypes = ['1yr-agegrade','avg-agegrade','trend','numraces','stderr','r-squared','pvalue'] for stattype, statdatatype in zip(stattypes, statdatatypes): for distcategory, datacategory in zip(distcategories, datacategories): summfields.append('{}\n{}'.format(stattype, distcategory)) datafields.append('{}-{}'.format(statdatatype, datacategory)) if stattype == 'numraces': for year in yearrange: summfields.append('{}\n{}'.format(stattype, year)) datafields.append('{}-{}'.format(statdatatype, lastyear-year)) # save summary file columns for resultsanalysissummary dtcolumns = json.dumps([{ 'data':d, 'name':d, 'label':l } for d,l in zip(datafields, summfields)]) columnsfilename = summaryfile + '.cols' with open(columnsfilename, 'w') as cols: cols.write(dtcolumns) # set up summary file summaryfname = summaryfile _SUMM = open(summaryfname,'wb') SUMM = csv.DictWriter(_SUMM,summfields) SUMM.writeheader() # loop through each member we've recorded information about for thisname in aag: fullname, fname, lname, gender, dob, runnerid = aag[thisname].get_runner() rendername = fullname.title() # check stats before deduplicating statcount = {} stats = aag[thisname].get_stats() for source in sources: statcount[source] = sum([1 for s in stats if s.source == source]) # remove duplicate entries aag[thisname].deduplicate() # crunch the numbers aag[thisname].crunch() # calculate age grade for each result stats = aag[thisname].get_stats() jan1 = ftime.asc2dt('{}-1-1'.format(lastyear)) runnerage = timeu.age(jan1, dob) # filter out runners younger than allowed if runnerage < minage: continue # filter out runners who have not run enough races stats = aag[thisname].get_stats() if enddate: lastyear = enddate.year else: lastyear = timeu.epoch2dt(time.time()).year lastyearstats = [s for s in stats if s.date.year==lastyear] if len(lastyearstats) < minraces: continue # fill in row for summary output summout = {} # get link for this runner's results chart # see http://stackoverflow.com/questions/2506379/add-params-to-given-url-in-python url_parts = list(urlparse(resultsurl)) query = dict(parse_qsl(url_parts[4])) query.update({'club': clubslug, 'runnerid': runnerid, 'begindate': ftime.dt2asc(begindate), 'enddate': ftime.dt2asc(enddate)}) url_parts[4] = urlencode(query) resultslink = urlunparse(url_parts) summout['name'] = '<a href={} target=_blank>{}</a>'.format(resultslink, rendername) summout['fname'] = fname summout['lname'] = lname summout['age'] = runnerage summout['gender'] = gender # set up to collect averages avg = collections.OrderedDict() # draw trendlines, write output allstats = aag[thisname].get_stats() if len(allstats) > 0: avg['overall'] = mean([s.ag for s in allstats]) trend = aag[thisname].get_trendline() oneyrstats = [s.ag for s in allstats if s.date.year == lastyear] if len(oneyrstats) > 0: summout['1yr agegrade\noverall'] = mean(oneyrstats) if len(allstats) > 0: summout['avg agegrade\noverall'] = avg['overall'] if len(allstats) >= mintrend and allstats[0].date != allstats[-1].date: summout['trend\noverall'] = trend.improvement summout['stderr\noverall'] = trend.stderr summout['r-squared\noverall'] = trend.r2**2 summout['pvalue\noverall'] = trend.pvalue summout['numraces\noverall'] = len(allstats) for year in yearrange: summout['numraces\n{}'.format(year)] = len([s for s in allstats if s.date.year==year]) for tlimit in TRENDLIMITS: distcategory,distcolor = TRENDLIMITS[tlimit] tstats = [s for s in allstats if s.dist >= tlimit[0] and s.dist < tlimit[1]] if len(tstats) > 0: avg[distcategory] = mean([s.ag for s in tstats]) summout['avg agegrade\n{}'.format(distcategory)] = avg[distcategory] summout['numraces\n{}'.format(distcategory)] = len(tstats) oneyrcategory = [s.ag for s in tstats if s.date.year == lastyear] if len(oneyrcategory) > 0: summout['1yr agegrade\n{}'.format(distcategory)] = mean(oneyrcategory) if len(tstats) >= mintrend and tstats[0].date != tstats[-1].date: try: trend = aag[thisname].get_trendline(thesestats=tstats) except ZeroDivisionError: app.logger.debug('ZeroDivisionError - processing {}'.format(rendername)) trend = None # ignore trends which can't be calculated if trend: summout['trend\n{}'.format(distcategory)] = trend.improvement summout['stderr\n{}'.format(distcategory)] = trend.stderr summout['r-squared\n{}'.format(distcategory)] = trend.r2 summout['pvalue\n{}'.format(distcategory)] = trend.pvalue SUMM.writerow(summout) # update status for source in sources: status[source]['processed'] += statcount[source] status[source]['lastname'] = rendername thistask.update_state(state='PROGRESS', meta={'progress':status}) _SUMM.close()
class FrameMain(wx.Frame): def __init__(self, title, pool): self.pool = pool self.lock = threading.Lock() self.sdr = None self.threadScan = None self.threadUpdate = None self.threadLocation = None self.queueScan = Queue.Queue() self.serverLocation = None self.isNewScan = True self.isScanning = False self.stopAtEnd = False self.stopScan = False self.dlgCal = None self.dlgSats = None self.dlgLog = None self.menuMain = None self.menuPopup = None self.graph = None self.toolbar = None self.canvas = None self.buttonStart = None self.buttonStop = None self.controlGain = None self.choiceMode = None self.choiceDwell = None self.choiceNfft = None self.spinCtrlStart = None self.spinCtrlStop = None self.choiceDisplay = None self.spectrum = OrderedDict() self.scanInfo = ScanInfo() self.locations = OrderedDict() self.lastLocation = [None] * 4 self.isSaved = True self.settings = Settings() self.devicesRtl = get_devices_rtl(self.settings.devicesRtl) self.settings.indexRtl = limit(self.settings.indexRtl, 0, len(self.devicesRtl) - 1) self.filename = "" self.oldCal = 0 self.remoteControl = None self.log = Log() self.pageConfig = wx.PageSetupDialogData() self.pageConfig.GetPrintData().SetOrientation(wx.LANDSCAPE) self.pageConfig.SetMarginTopLeft((20, 20)) self.pageConfig.SetMarginBottomRight((20, 20)) self.printConfig = wx.PrintDialogData(self.pageConfig.GetPrintData()) self.printConfig.EnableSelection(False) self.printConfig.EnablePageNumbers(False) wx.Frame.__init__(self, None, title=title) self.timerGpsRetry = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.__on_gps_retry, self.timerGpsRetry) self.Bind(wx.EVT_CLOSE, self.__on_exit) self.status = Statusbar(self, self.log) self.status.set_info(title) self.SetStatusBar(self.status) add_colours() self.__create_widgets() self.__create_menu() self.__create_popup_menu() self.__set_control_state(True) self.Show() displaySize = wx.DisplaySize() toolbarSize = self.toolbar.GetBestSize() self.SetClientSize((toolbarSize[0] + 10, displaySize[1] / 2)) self.SetMinSize((displaySize[0] / 4, displaySize[1] / 4)) self.Connect(-1, -1, EVENT_THREAD, self.__on_event) self.SetDropTarget(DropTarget(self)) self.SetIcon(load_icon('rtlsdr_scan')) self.steps = 0 self.stepsTotal = 0 self.__start_gps() self.__start_location_server() def __create_widgets(self): self.remoteControl = RemoteControl() self.graph = PanelGraph(self, self, self.settings, self.status, self.remoteControl) self.toolbar = wx.Panel(self) self.buttonStart = MultiButton(self.toolbar, ['Start', 'Continue'], ['Start new scan', 'Continue scanning']) self.buttonStart.SetSelected(self.settings.startOption) self.buttonStop = MultiButton(self.toolbar, ['Stop', 'Stop at end'], ['Stop scan', 'Stop scan at end']) self.buttonStop.SetSelected(self.settings.stopOption) self.Bind(wx.EVT_BUTTON, self.__on_start, self.buttonStart) self.Bind(wx.EVT_BUTTON, self.__on_stop, self.buttonStop) textRange = wx.StaticText(self.toolbar, label="Range (MHz)", style=wx.ALIGN_CENTER) textStart = wx.StaticText(self.toolbar, label="Start") textStop = wx.StaticText(self.toolbar, label="Stop") self.spinCtrlStart = wx.SpinCtrl(self.toolbar) self.spinCtrlStop = wx.SpinCtrl(self.toolbar) self.spinCtrlStart.SetToolTipString('Start frequency') self.spinCtrlStop.SetToolTipString('Stop frequency') self.spinCtrlStart.SetRange(F_MIN, F_MAX - 1) self.spinCtrlStop.SetRange(F_MIN + 1, F_MAX) self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStart) self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStop) textGain = wx.StaticText(self.toolbar, label="Gain (dB)") self.controlGain = wx.Choice(self.toolbar, choices=['']) textMode = wx.StaticText(self.toolbar, label="Mode") self.choiceMode = wx.Choice(self.toolbar, choices=MODE[::2]) self.choiceMode.SetToolTipString('Scanning mode') textDwell = wx.StaticText(self.toolbar, label="Dwell") self.choiceDwell = wx.Choice(self.toolbar, choices=DWELL[::2]) self.choiceDwell.SetToolTipString('Scan time per step') textNfft = wx.StaticText(self.toolbar, label="FFT size") self.choiceNfft = wx.Choice(self.toolbar, choices=map(str, NFFT)) self.choiceNfft.SetToolTipString('Higher values for greater' 'precision') textDisplay = wx.StaticText(self.toolbar, label="Display") self.choiceDisplay = wx.Choice(self.toolbar, choices=DISPLAY[::2]) self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceDisplay) self.choiceDisplay.SetToolTipString('Spectrogram available in' 'continuous mode') grid = wx.GridBagSizer(5, 5) grid.Add(self.buttonStart, pos=(0, 0), span=(3, 1), flag=wx.ALIGN_CENTER) grid.Add(self.buttonStop, pos=(0, 1), span=(3, 1), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 2)) grid.Add(textRange, pos=(0, 3), span=(1, 4), flag=wx.ALIGN_CENTER) grid.Add(textStart, pos=(1, 3), flag=wx.ALIGN_CENTER) grid.Add(self.spinCtrlStart, pos=(1, 4)) grid.Add(textStop, pos=(1, 5), flag=wx.ALIGN_CENTER) grid.Add(self.spinCtrlStop, pos=(1, 6)) grid.Add(textGain, pos=(0, 7), flag=wx.ALIGN_CENTER) grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 8)) grid.Add(textMode, pos=(0, 9), flag=wx.ALIGN_CENTER) grid.Add(self.choiceMode, pos=(1, 9), flag=wx.ALIGN_CENTER) grid.Add(textDwell, pos=(0, 10), flag=wx.ALIGN_CENTER) grid.Add(self.choiceDwell, pos=(1, 10), flag=wx.ALIGN_CENTER) grid.Add(textNfft, pos=(0, 11), flag=wx.ALIGN_CENTER) grid.Add(self.choiceNfft, pos=(1, 11), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 12)) grid.Add(textDisplay, pos=(0, 13), flag=wx.ALIGN_CENTER) grid.Add(self.choiceDisplay, pos=(1, 13), flag=wx.ALIGN_CENTER) self.__set_controls() self.__set_gain_control() self.toolbar.SetSizer(grid) self.toolbar.Layout() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.graph, 1, wx.EXPAND) sizer.Add(self.toolbar, 0, wx.EXPAND) self.SetSizer(sizer) self.Layout() def __create_menu(self): self.menuMain = MenuMain(self, self.settings) self.Bind(wx.EVT_MENU, self.__on_new, self.menuMain.new) self.Bind(wx.EVT_MENU, self.__on_open, self.menuMain.open) self.Bind(wx.EVT_MENU, self.__on_merge, self.menuMain.merge) self.Bind(wx.EVT_MENU_RANGE, self.__on_file_history, id=wx.ID_FILE1, id2=wx.ID_FILE9) self.Bind(wx.EVT_MENU, self.__on_save, self.menuMain.save) self.Bind(wx.EVT_MENU, self.__on_export_scan, self.menuMain.exportScan) self.Bind(wx.EVT_MENU, self.__on_export_image, self.menuMain.exportImage) self.Bind(wx.EVT_MENU, self.__on_export_image_seq, self.menuMain.exportSeq) self.Bind(wx.EVT_MENU, self.__on_export_geo, self.menuMain.exportGeo) self.Bind(wx.EVT_MENU, self.__on_export_track, self.menuMain.exportTrack) self.Bind(wx.EVT_MENU, self.__on_page, self.menuMain.page) self.Bind(wx.EVT_MENU, self.__on_preview, self.menuMain.preview) self.Bind(wx.EVT_MENU, self.__on_print, self.menuMain.printer) self.Bind(wx.EVT_MENU, self.__on_properties, self.menuMain.properties) self.Bind(wx.EVT_MENU, self.__on_exit, self.menuMain.close) self.Bind(wx.EVT_MENU, self.__on_pref, self.menuMain.pref) self.Bind(wx.EVT_MENU, self.__on_adv_pref, self.menuMain.advPref) self.Bind(wx.EVT_MENU, self.__on_formatting, self.menuMain.formatting) self.Bind(wx.EVT_MENU, self.__on_devices_rtl, self.menuMain.devicesRtl) self.Bind(wx.EVT_MENU, self.__on_devices_gps, self.menuMain.devicesGps) self.Bind(wx.EVT_MENU, self.__on_reset, self.menuMain.reset) self.Bind(wx.EVT_MENU, self.__on_clear_select, self.menuMain.clearSelect) self.Bind(wx.EVT_MENU, self.__on_show_measure, self.menuMain.showMeasure) self.Bind(wx.EVT_MENU, self.__on_start, self.menuMain.start) self.Bind(wx.EVT_MENU, self.__on_continue, self.menuMain.cont) self.Bind(wx.EVT_MENU, self.__on_stop, self.menuMain.stop) self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuMain.stopEnd) self.Bind(wx.EVT_MENU, self.__on_compare, self.menuMain.compare) self.Bind(wx.EVT_MENU, self.__on_smooth, self.menuMain.smooth) self.Bind(wx.EVT_MENU, self.__on_cal, self.menuMain.cal) self.Bind(wx.EVT_MENU, self.__on_gearth, self.menuMain.gearth) self.Bind(wx.EVT_MENU, self.__on_gmaps, self.menuMain.gmaps) self.Bind(wx.EVT_MENU, self.__on_sats, self.menuMain.sats) self.Bind(wx.EVT_MENU, self.__on_loc_clear, self.menuMain.locClear) self.Bind(wx.EVT_MENU, self.__on_log, self.menuMain.log) self.Bind(wx.EVT_MENU, self.__on_help, self.menuMain.helpLink) self.Bind(wx.EVT_MENU, self.__on_update, self.menuMain.update) self.Bind(wx.EVT_MENU, self.__on_sys_info, self.menuMain.sys) self.Bind(wx.EVT_MENU, self.__on_about, self.menuMain.about) idF1 = wx.wx.NewId() self.Bind(wx.EVT_MENU, self.__on_help, id=idF1) accelTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_F1, idF1)]) self.SetAcceleratorTable(accelTable) self.Bind(wx.EVT_MENU_HIGHLIGHT, self.__on_menu_highlight) self.SetMenuBar(self.menuMain.menuBar) def __create_popup_menu(self): self.menuPopup = PopMenuMain(self.settings) self.Bind(wx.EVT_MENU, self.__on_start, self.menuPopup.start) self.Bind(wx.EVT_MENU, self.__on_continue, self.menuPopup.cont) self.Bind(wx.EVT_MENU, self.__on_stop, self.menuPopup.stop) self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuPopup.stopEnd) self.Bind(wx.EVT_MENU, self.__on_range_lim, self.menuPopup.rangeLim) self.Bind(wx.EVT_MENU, self.__on_points_lim, self.menuPopup.pointsLim) self.Bind(wx.EVT_MENU, self.__on_clear_select, self.menuPopup.clearSelect) self.Bind(wx.EVT_MENU, self.__on_show_measure, self.menuPopup.showMeasure) self.Bind(wx.EVT_CONTEXT_MENU, self.__on_popup_menu) def __on_menu_highlight(self, event): item = self.GetMenuBar().FindItemById(event.GetId()) if item is not None: help = item.GetHelp() else: help = '' self.status.set_general(help, level=None) def __on_popup_menu(self, event): if not isinstance(event.GetEventObject(), NavigationToolbar): pos = event.GetPosition() pos = self.ScreenToClient(pos) self.PopupMenu(self.menuPopup.menu, pos) def __on_new(self, _event): if self.__save_warn(Warn.NEW): return True self.spectrum.clear() self.locations.clear() self.__saved(True) self.__set_plot(self.spectrum, False) self.graph.clear_selection() self.__set_control_state(True) return False def __on_open(self, _event): if self.__save_warn(Warn.OPEN): return dlg = wx.FileDialog(self, "Open a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.OPEN) if dlg.ShowModal() == wx.ID_OK: self.open(dlg.GetDirectory(), dlg.GetFilename()) dlg.Destroy() def __on_merge(self, _event): if self.__save_warn(Warn.MERGE): return dlg = wx.FileDialog(self, "Merge a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.OPEN) if dlg.ShowModal() == wx.ID_OK: self.__merge(dlg.GetDirectory(), dlg.GetFilename()) dlg.Destroy() def __on_file_history(self, event): selection = event.GetId() - wx.ID_FILE1 path = self.settings.fileHistory.GetHistoryFile(selection) self.settings.fileHistory.AddFileToHistory(path) dirname, filename = os.path.split(path) self.open(dirname, filename) def __on_save(self, _event): dlg = wx.FileDialog(self, "Save a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Saving...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.filename = os.path.splitext(fileName)[0] self.settings.dirScans = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.SAVE) fullName = os.path.join(dirName, fileName) save_plot(fullName, self.scanInfo, self.spectrum, self.locations) self.__saved(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory(fullName) dlg.Destroy() def __on_export_scan(self, _event): dlg = wx.FileDialog(self, "Export a scan", self.settings.dirExport, self.filename, File.get_type_filters(), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.PLOT) fullName = os.path.join(dirName, fileName) export_plot(fullName, dlg.GetFilterIndex(), self.spectrum) self.status.set_general("Finished") dlg.Destroy() def __on_export_image(self, _event): dlgFile = wx.FileDialog(self, "Export image to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.IMAGE), wx.SAVE | wx.OVERWRITE_PROMPT) dlgFile.SetFilterIndex(File.ImageType.PNG) if dlgFile.ShowModal() == wx.ID_OK: dlgImg = DialogImageSize(self, self.settings) if dlgImg.ShowModal() != wx.ID_OK: dlgFile.Destroy() return self.status.set_general("Exporting...") fileName = dlgFile.GetFilename() dirName = dlgFile.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlgFile.GetFilterIndex(), File.Types.IMAGE) fullName = os.path.join(dirName, fileName) exportType = dlgFile.GetFilterIndex() export_image(fullName, exportType, self.graph.get_figure(), self.settings) self.status.set_general("Finished") dlgFile.Destroy() def __on_export_image_seq(self, _event): dlgSeq = DialogExportSeq(self, self.spectrum, self.settings) dlgSeq.ShowModal() dlgSeq.Destroy() def __on_export_geo(self, _event): dlgGeo = DialogExportGeo(self, self.spectrum, self.locations, self.settings) if dlgGeo.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") extent = dlgGeo.get_extent() dlgFile = wx.FileDialog(self, "Export map to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.GEO), wx.SAVE | wx.OVERWRITE_PROMPT) dlgFile.SetFilterIndex(File.GeoType.KMZ) if dlgFile.ShowModal() == wx.ID_OK: fileName = dlgFile.GetFilename() dirName = dlgFile.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlgFile.GetFilterIndex(), File.Types.GEO) fullName = os.path.join(dirName, fileName) exportType = dlgFile.GetFilterIndex() image = None xyz = None if exportType == File.GeoType.CSV: xyz = dlgGeo.get_xyz() else: image = dlgGeo.get_image() export_map(fullName, exportType, extent, image, xyz) self.status.set_general("Finished") dlgFile.Destroy() dlgGeo.Destroy() def __on_export_track(self, _event): dlg = wx.FileDialog(self, "Export GPS to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.TRACK), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.TRACK) fullName = os.path.join(dirName, fileName) export_gpx(fullName, self.locations, self.GetName()) self.status.set_general("Finished") dlg.Destroy() def __on_page(self, _event): dlg = wx.PageSetupDialog(self, self.pageConfig) if dlg.ShowModal() == wx.ID_OK: self.pageConfig = wx.PageSetupDialogData(dlg.GetPageSetupDialogData()) self.printConfig.SetPrintData(self.pageConfig.GetPrintData()) dlg.Destroy() def __on_preview(self, _event): printout = PrintOut(self.graph, self.filename, self.pageConfig) printoutPrinting = PrintOut(self.graph, self.filename, self.pageConfig) preview = wx.PrintPreview(printout, printoutPrinting, self.printConfig) frame = wx.PreviewFrame(preview, self, 'Print Preview') frame.Initialize() frame.SetSize(self.GetSize()) frame.Show(True) def __on_print(self, _event): printer = wx.Printer(self.printConfig) printout = PrintOut(self.graph, self.filename, self.pageConfig) if printer.Print(self, printout, True): self.printConfig = wx.PrintDialogData(printer.GetPrintDialogData()) self.pageConfig.SetPrintData(self.printConfig.GetPrintData()) def __on_properties(self, _event): if len(self.spectrum) > 0: self.scanInfo.timeFirst = min(self.spectrum) self.scanInfo.timeLast = max(self.spectrum) dlg = DialogProperties(self, self.scanInfo) dlg.ShowModal() dlg.Destroy() def __on_exit(self, _event): self.Unbind(wx.EVT_CLOSE) if self.__save_warn(Warn.EXIT): self.Bind(wx.EVT_CLOSE, self.__on_exit) return self.__scan_stop(False) self.__stop_gps(False) self.__stop_location_server() self.__get_controls() self.settings.devicesRtl = self.devicesRtl self.settings.save() self.graph.close() self.Destroy() def __on_pref(self, _event): self.__get_controls() dlg = DialogPrefs(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.graph.create_plot() self.__set_control_state(True) self.__set_controls() dlg.Destroy() def __on_adv_pref(self, _event): dlg = DialogAdvPrefs(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.__set_control_state(True) dlg.Destroy() def __on_formatting(self, _event): dlg = DialogFormatting(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.__set_control_state(True) self.graph.update_measure() self.graph.redraw_plot() dlg.Destroy() def __on_devices_rtl(self, _event): self.__get_controls() self.devicesRtl = self.__refresh_devices() dlg = DialogDevicesRTL(self, self.devicesRtl, self.settings) if dlg.ShowModal() == wx.ID_OK: self.devicesRtl = dlg.get_devices() self.settings.indexRtl = dlg.get_index() self.__set_gain_control() self.__set_control_state(True) self.__set_controls() dlg.Destroy() def __on_devices_gps(self, _event): self.__stop_gps() self.status.set_gps('GPS Stopped') dlg = DialogDevicesGPS(self, self.settings) dlg.ShowModal() dlg.Destroy() self.__start_gps() def __on_reset(self, _event): dlg = wx.MessageDialog(self, 'Reset all settings to the default values\n' '(cannot be undone)?', 'Reset Settings', wx.YES_NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: self.devicesRtl = [] self.settings.reset() self.__set_controls() self.graph.create_plot() dlg.Destroy() def __on_compare(self, _event): dlg = DialogCompare(self, self.settings, self.filename) dlg.Show() def __on_smooth(self, _event): dlg = DialogSmooth(self, self.spectrum, self.settings) if dlg.ShowModal() == wx.ID_OK: saved = self.isSaved self.isSaved = False if not self.__on_new(None): self.spectrum.clear() spectrum = dlg.get_spectrum() self.spectrum.update(OrderedDict(sorted(spectrum.items()))) self.__set_plot(self.spectrum, False) self.graph.update_measure() self.graph.redraw_plot() self.__saved(False) else: self.__saved(saved) def __on_clear_select(self, _event): self.graph.clear_selection() def __on_show_measure(self, event): show = event.Checked() self.menuMain.showMeasure.Check(show) self.menuPopup.showMeasure.Check(show) self.settings.showMeasure = show self.graph.show_measure_table(show) self.Layout() def __on_cal(self, _event): self.dlgCal = DialogAutoCal(self, self.settings.calFreq, self.__auto_cal) self.dlgCal.ShowModal() def __on_gearth(self, _event): tempPath = tempfile.mkdtemp() tempFile = os.path.join(tempPath, 'RTLSDRScannerLink.kml') handle = open(tempFile, 'wb') create_gearth(handle) handle.close() if not run_file(tempFile): wx.MessageBox('Error starting Google Earth', 'Error', wx.OK | wx.ICON_ERROR) def __on_gmaps(self, _event): url = 'http://localhost:{}/rtlsdr_scan.html'.format(LOCATION_PORT) webbrowser.open_new(url) def __on_sats(self, _event): if self.dlgSats is None: self.dlgSats = DialogSats(self) self.dlgSats.Show() def __on_loc_clear(self, _event): result = wx.MessageBox('Remove {} locations from scan?'.format(len(self.locations)), 'Clear location data', wx.YES_NO, self) if result == wx.YES: self.locations.clear() self.__set_control_state(True) def __on_log(self, _event): if self.dlgLog is None: self.dlgLog = DialogLog(self, self.log) self.dlgLog.Show() def __on_help(self, _event): webbrowser.open("http://eartoearoak.com/software/rtlsdr-scanner") def __on_update(self, _event): if self.threadUpdate is None: self.status.set_general("Checking for updates", level=None) self.threadUpdate = Thread(target=self.__update_check) self.threadUpdate.start() def __on_sys_info(self, _event): dlg = DialogSysInfo(self) dlg.ShowModal() dlg.Destroy() def __on_about(self, _event): dlg = DialogAbout(self) dlg.ShowModal() dlg.Destroy() def __on_spin(self, event): control = event.GetEventObject() if control == self.spinCtrlStart: self.spinCtrlStop.SetRange(self.spinCtrlStart.GetValue() + 1, F_MAX) def __on_choice(self, _event): self.__get_controls() self.graph.create_plot() def __on_start(self, event): self.__get_controls() if self.settings.start >= self.settings.stop: wx.MessageBox('Stop frequency must be greater that start', 'Warning', wx.OK | wx.ICON_WARNING) return self.devicesRtl = self.__refresh_devices() if len(self.devicesRtl) == 0: wx.MessageBox('No devices found', 'Error', wx.OK | wx.ICON_ERROR) else: if event.GetInt() == 0: self.isNewScan = True else: self.isNewScan = False self.__scan_start() if not self.settings.retainScans: self.status.set_info('Warning: Averaging is enabled in preferences', level=Log.WARN) def __on_continue(self, event): event.SetInt(1) self.__on_start(event) def __on_stop(self, event): if event.GetInt() == 0: self.stopScan = True self.stopAtEnd = False self.__scan_stop() else: self.stopScan = False self.stopAtEnd = True def __on_stop_end(self, _event): self.stopAtEnd = True def __on_range_lim(self, _event): xmin, xmax = self.graph.get_axes().get_xlim() xmin = int(xmin) xmax = math.ceil(xmax) if xmax < xmin + 1: xmax = xmin + 1 self.settings.start = xmin self.settings.stop = xmax self.__set_controls() def __on_points_lim(self, _event): self.settings.pointsLimit = self.menuPopup.pointsLim.IsChecked() self.__set_plot(self.spectrum, self.settings.annotate) def __on_gps_retry(self, _event): self.timerGpsRetry.Stop() self.__stop_gps() self.__start_gps() def __on_event(self, event): status = event.data.get_status() arg1 = event.data.get_arg1() arg2 = event.data.get_arg2() if status == Event.STARTING: self.status.set_general("Starting") self.isScanning = True elif status == Event.STEPS: self.stepsTotal = (arg1 + 1) * 2 self.steps = self.stepsTotal self.status.set_progress(0) self.status.show_progress() elif status == Event.CAL: self.__auto_cal(Cal.DONE) elif status == Event.INFO: if self.threadScan is not None: self.sdr = self.threadScan.get_sdr() if arg2 is not None: self.devicesRtl[self.settings.indexRtl].tuner = arg2 self.scanInfo.tuner = arg2 elif status == Event.DATA: self.__saved(False) cal = self.devicesRtl[self.settings.indexRtl].calibration freq, scan = self.queueScan.get() self.pool.apply_async(anaylse_data, (freq, scan, cal, self.settings.nfft, self.settings.overlap, self.settings.winFunc), callback=self.__on_process_done) self.__progress() elif status == Event.STOPPED: self.__cleanup() self.status.set_general("Stopped") elif status == Event.FINISHED: self.threadScan = None elif status == Event.ERROR: self.__cleanup() self.status.set_general("Error: {}".format(arg2), level=Log.ERROR) if self.dlgCal is not None: self.dlgCal.Destroy() self.dlgCal = None elif status == Event.PROCESSED: offset = self.settings.devicesRtl[self.settings.indexRtl].offset if self.settings.alert: alert = self.settings.alertLevel else: alert = None Thread(target=update_spectrum, name='Update', args=(self, self.lock, self.settings.start, self.settings.stop, arg1, arg2, offset, self.spectrum, not self.settings.retainScans, alert)).start() elif status == Event.LEVEL: wx.Bell() elif status == Event.UPDATED: if arg2 and self.settings.liveUpdate: self.__set_plot(self.spectrum, self.settings.annotate and self.settings.retainScans and self.settings.mode == Mode.CONTIN) self.__progress() elif status == Event.DRAW: self.graph.draw() elif status == Event.VER_UPD: self.__update_checked(True, arg1, arg2) elif status == Event.VER_NOUPD: self.__update_checked(False) elif status == Event.VER_UPDFAIL: self.__update_checked(failed=True) elif status == Event.LOC_WARN: self.status.set_gps("{}".format(arg2), level=Log.WARN) self.status.warn_gps() elif status == Event.LOC_ERR: self.status.set_gps("{}".format(arg2), level=Log.ERROR) self.status.error_gps() self.threadLocation = None if self.settings.gpsRetry: if not self.timerGpsRetry.IsRunning(): self.timerGpsRetry.Start(20000, True) elif status == Event.LOC: self.__update_location(arg2) elif status == Event.LOC_SAT: if self.dlgSats is not None: self.dlgSats.set_sats(arg2) wx.YieldIfNeeded() def __on_process_done(self, data): timeStamp, freq, scan = data post_event(self, EventThread(Event.PROCESSED, freq, (timeStamp, scan))) def __auto_cal(self, status): freq = self.dlgCal.get_arg1() if self.dlgCal is not None: if status == Cal.START: self.spinCtrlStart.SetValue(int(freq)) self.spinCtrlStop.SetValue(math.ceil(freq)) self.oldCal = self.devicesRtl[self.settings.indexRtl].calibration self.devicesRtl[self.settings.indexRtl].calibration = 0 self.__get_controls() self.spectrum.clear() self.locations.clear() if not self.__scan_start(isCal=True): self.dlgCal.reset_cal() elif status == Cal.DONE: ppm = self.__calc_ppm(freq) self.dlgCal.set_cal(ppm) self.__set_control_state(True) elif status == Cal.OK: self.devicesRtl[self.settings.indexRtl].calibration = self.dlgCal.get_cal() self.settings.calFreq = freq self.dlgCal = None elif status == Cal.CANCEL: self.dlgCal = None if len(self.devicesRtl) > 0: self.devicesRtl[self.settings.indexRtl].calibration = self.oldCal def __calc_ppm(self, freq): with self.lock: timeStamp = max(self.spectrum) spectrum = self.spectrum[timeStamp].copy() for x, y in spectrum.iteritems(): spectrum[x] = (((x - freq) * (x - freq)) + 1) * y peak = max(spectrum, key=spectrum.get) return ((freq - peak) / freq) * 1e6 def __scan_start(self, isCal=False): if self.isNewScan and self.__save_warn(Warn.SCAN): return False if not self.threadScan: self.__set_control_state(False) samples = calc_samples(self.settings.dwell) if self.isNewScan: self.spectrum.clear() self.locations.clear() self.graph.clear_plots() self.isNewScan = False self.status.set_info('', level=None) self.scanInfo.set_from_settings(self.settings) self.scanInfo.time = format_iso_time(time.time()) self.scanInfo.lat = None self.scanInfo.lon = None self.scanInfo.desc = '' self.stopAtEnd = False self.stopScan = False self.threadScan = ThreadScan(self, self.queueScan, self.sdr, self.settings, self.settings.indexRtl, samples, isCal) self.filename = "Scan {0:.1f}-{1:.1f}MHz".format(self.settings.start, self.settings.stop) self.graph.set_plot_title() self.__start_gps() return True def __scan_stop(self, join=True): if self.threadScan: self.status.set_general("Stopping") self.threadScan.abort() if join: self.threadScan.join() self.threadScan = None if self.sdr is not None: self.sdr.close() self.__set_control_state(True) def __progress(self): if self.steps == self.stepsTotal: self.status.set_general("Scanning ({} sweeps)".format(len(self.spectrum))) self.steps -= 1 if self.steps > 0 and not self.stopScan: self.status.set_progress((self.stepsTotal - self.steps) * 100.0 / (self.stepsTotal - 1)) self.status.show_progress() else: self.status.hide_progress() self.__set_plot(self.spectrum, self.settings.annotate) if self.stopScan: self.status.set_general("Stopped") self.__cleanup() elif self.settings.mode == Mode.SINGLE: self.status.set_general("Finished") self.__cleanup() else: if self.settings.mode == Mode.CONTIN: if self.dlgCal is None and not self.stopAtEnd: self.__limit_spectrum() self.__scan_start() else: self.status.set_general("Stopped") self.__cleanup() def __cleanup(self): if self.sdr is not None: self.sdr.close() self.sdr = None self.status.hide_progress() self.steps = 0 self.threadScan = None self.__set_control_state(True) self.stopAtEnd = False self.stopScan = True self.isScanning = False def __remove_last(self, data): while len(data) >= self.settings.retainMax: timeStamp = min(data) del data[timeStamp] def __limit_spectrum(self): with self.lock: self.__remove_last(self.spectrum) self.__remove_last(self.locations) def __start_gps(self): if self.settings.gps and len(self.settings.devicesGps): self.status.enable_gps() if self.threadLocation is None: device = self.settings.devicesGps[self.settings.indexGps] self.threadLocation = ThreadLocation(self, device) else: self.status.disable_gps() def __stop_gps(self, join=True): if self.threadLocation and self.threadLocation.isAlive(): self.threadLocation.stop() if join: self.threadLocation.join() self.threadLocation = None def __start_location_server(self): self.serverLocation = LocationServer(self.locations, self.lastLocation, self.lock, self.log) def __stop_location_server(self): if self.serverLocation: self.serverLocation.close() def __update_location(self, data): i = 0 for loc in data: self.lastLocation[i] = loc i += 1 self.status.pulse_gps() if data[2] is None: gpsStatus = '{:.5f}, {:.5f}, {:.1f}'.format(data[0], data[1]) else: gpsStatus = '{:.5f}, {:.5f}, {:.1f}m'.format(data[0], data[1], data[2]) self.status.set_gps(gpsStatus, level=None) if not self.isScanning: return if self.scanInfo is not None: if data[0] and data[1]: self.scanInfo.lat = str(data[0]) self.scanInfo.lon = str(data[1]) with self.lock: if len(self.spectrum) > 0: self.locations[max(self.spectrum)] = (data[0], data[1], data[2]) def __saved(self, isSaved): self.isSaved = isSaved title = APP_NAME + " - " + self.filename if not isSaved: title += "*" self.SetTitle(title) def __set_plot(self, spectrum, annotate): if len(spectrum) > 0: total = count_points(spectrum) if total > 0: spectrum = sort_spectrum(spectrum) extent = Extent(spectrum) self.graph.set_plot(spectrum, self.settings.pointsLimit, self.settings.pointsMax, extent, annotate) else: self.graph.clear_plots() def __set_control_state(self, state): hasDevices = len(self.devicesRtl) > 0 self.spinCtrlStart.Enable(state) self.spinCtrlStop.Enable(state) self.controlGain.Enable(state) self.choiceMode.Enable(state) self.choiceDwell.Enable(state) self.choiceNfft.Enable(state) self.buttonStart.Enable(state and hasDevices) self.buttonStop.Enable(not state and hasDevices) self.menuMain.set_state(state, self.spectrum, self.locations) self.menuPopup.set_state(state, self.spectrum) def __set_controls(self): self.spinCtrlStart.SetValue(self.settings.start) self.spinCtrlStop.SetValue(self.settings.stop) self.choiceMode.SetSelection(MODE[1::2].index(self.settings.mode)) dwell = calc_real_dwell(self.settings.dwell) try: sel = DWELL[1::2].index(dwell) except ValueError: sel = DWELL[1::2][len(DWELL) / 4] self.choiceDwell.SetSelection(sel) self.choiceNfft.SetSelection(NFFT.index(self.settings.nfft)) self.choiceDisplay.SetSelection(DISPLAY[1::2].index(self.settings.display)) def __set_gain_control(self): grid = self.controlGain.GetContainingSizer() if len(self.devicesRtl) > 0: self.controlGain.Destroy() device = self.devicesRtl[self.settings.indexRtl] if device.isDevice: gains = device.get_gains_str() self.controlGain = wx.Choice(self.toolbar, choices=gains) gain = device.get_closest_gain_str(device.gain) self.controlGain.SetStringSelection(gain) else: self.controlGain = NumCtrl(self.toolbar, integerWidth=3, fractionWidth=1) font = self.controlGain.GetFont() dc = wx.WindowDC(self.controlGain) dc.SetFont(font) size = dc.GetTextExtent('####.#') self.controlGain.SetMinSize((size[0] * 1.2, -1)) self.controlGain.SetValue(device.gain) grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER) grid.Layout() def __get_controls(self): self.settings.start = self.spinCtrlStart.GetValue() self.settings.stop = self.spinCtrlStop.GetValue() self.settings.startOption = self.buttonStart.GetSelected() self.settings.stopOption = self.buttonStop.GetSelected() self.settings.mode = MODE[1::2][self.choiceMode.GetSelection()] self.settings.dwell = DWELL[1::2][self.choiceDwell.GetSelection()] self.settings.nfft = NFFT[self.choiceNfft.GetSelection()] self.settings.display = DISPLAY[1::2][self.choiceDisplay.GetSelection()] if len(self.devicesRtl) > 0: device = self.devicesRtl[self.settings.indexRtl] try: if device.isDevice: device.gain = float(self.controlGain.GetStringSelection()) else: device.gain = self.controlGain.GetValue() except ValueError: device.gain = 0 def __save_warn(self, warnType): if self.settings.saveWarn and not self.isSaved: dlg = DialogSaveWarn(self, warnType) code = dlg.ShowModal() if code == wx.ID_YES: self.__on_save(None) if self.isSaved: return False else: return True elif code == wx.ID_NO: return False else: return True return False def __update_check(self): local = get_version_timestamp(True) try: remote = get_version_timestamp_repo() except IOError: post_event(self, EventThread(Event.VER_UPDFAIL)) return if remote > local: post_event(self, EventThread(Event.VER_UPD, local, remote)) else: post_event(self, EventThread(Event.VER_NOUPD)) def __update_checked(self, updateFound=False, local=None, remote=None, failed=False): self.threadUpdate = None self.status.set_general("", level=None) if failed: icon = wx.ICON_ERROR message = "Update check failed" else: icon = wx.ICON_INFORMATION if updateFound: message = "Update found\n\n" message += "Local: " + time.strftime('%c', time.localtime(local)) message += "\nRemote: " + time.strftime('%c', time.localtime(remote)) else: message = "No updates found" dlg = wx.MessageDialog(self, message, "Update", wx.OK | icon) dlg.ShowModal() dlg.Destroy() def __refresh_devices(self): self.settings.devicesRtl = get_devices_rtl(self.devicesRtl, self.status) self.settings.indexRtl = limit(self.settings.indexRtl, 0, len(self.devicesRtl) - 1) self.settings.save() return self.settings.devicesRtl def __merge(self, dirname, filename): if not os.path.exists(os.path.join(dirname, filename)): wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR) return self.filename = os.path.splitext(filename)[0] self.settings.dirScans = dirname self.status.set_general("Merging: {}".format(filename)) _scanInfo, spectrum, locations = open_plot(dirname, filename) if len(spectrum) > 0: spectrum.update(self.spectrum) locations.update(self.locations) self.spectrum.clear() self.locations.clear() self.spectrum.update(OrderedDict(sorted(spectrum.items()))) self.locations.update(OrderedDict(sorted(locations.items()))) self.__set_plot(self.spectrum, self.settings.annotate) self.graph.scale_plot(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory(os.path.join(dirname, filename)) else: self.status.set_general("Merge failed", level=Log.ERROR) def open(self, dirname, filename): if not os.path.exists(os.path.join(dirname, filename)): wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR) return self.__on_new(None) self.graph.get_canvas().draw() self.filename = os.path.splitext(filename)[0] self.settings.dirScans = dirname self.status.set_general("Opening: {}".format(filename)) self.scanInfo, spectrum, location = open_plot(dirname, filename) if len(spectrum) > 0: self.scanInfo.set_to_settings(self.settings) self.spectrum = spectrum self.locations.clear() self.locations.update(location) self.__saved(True) self.__set_controls() self.__set_control_state(True) self.__set_plot(spectrum, self.settings.annotate) self.graph.scale_plot(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory(os.path.join(dirname, filename)) else: self.status.set_general("Open failed", level=Log.ERROR)
def __start_location_server(self): self.serverLocation = LocationServer(self.locations, self.lastLocation, self.lock, self.log)
class AthlinksCollect(CollectServiceResults): ######################################################################## #---------------------------------------------------------------------- def __init__(self): #---------------------------------------------------------------------- ''' initialize object instance may be overridden when ResultsCollect is instantiated, but overriding method must call `super(<subclass>, self).__init__(servicename, resultfilehdr, resultattrs)` ''' super(AthlinksCollect, self).__init__('athlinks', resultfilehdr, resultattrs) #---------------------------------------------------------------------- def openservice(self, club_id): #---------------------------------------------------------------------- ''' initialize service recommended that the overriding method save service instance in `self.service` must be overridden when ResultsCollect is instantiated :param club_id: club.id for club this service is operating on ''' # create location server self.locsvr = LocationServer() # remember club id we're working on self.club_id = club_id # debug file for races saved # set debugrace to False if not debugging debugrace = True if debugrace: clubslug = Club.query.filter_by(id=club_id).first().shname self.racefile = '{}/{}-athlinks-race.csv'.format(app.config['MEMBERSHIP_DIR'], clubslug) else: self.racefile = None if self.racefile: self._RACE = open(self.racefile, 'wb') self.racefields = 'id,name,date,distmiles,status,runner'.split(',') self.RACE = csv.DictWriter(self._RACE, self.racefields) self.RACE.writeheader() # open service key = ApiCredentials.query.filter_by(name=self.servicename).first().key self.service = athlinks.Athlinks(debug=True, key=key) #---------------------------------------------------------------------- def getresults(self, name, fname, lname, gender, dt_dob, begindate, enddate): #---------------------------------------------------------------------- ''' retrieves a list of results for a single name must be overridden when ResultsCollect is instantiated use dt_dob to filter errant race results, based on age of runner on race day :param name: name of participant for which results are to be returned :param fname: first name of participant :param lname: last name of participant :param gender: 'M' or 'F' :param dt_dob: participant's date of birth, as datetime :param begindate: epoch time for start of results, 00:00:00 on date to begin :param end: epoch time for end of results, 23:59:59 on date to finish :rtype: list of serviceresults, each of which can be processed by convertresult ''' # remember participant data self.name = name self.fname = fname self.lname = lname self.gender = gender self.dt_dob = dt_dob self.dob = ftime.dt2asc(dt_dob) # get results for this athlete allresults = self.service.listathleteresults(name) # filter by date and by age filteredresults = [] for result in allresults: e_racedate = athlinks.gettime(result['Race']['RaceDate']) # skip result if outside the desired time window if e_racedate < begindate or e_racedate > enddate: continue # skip result if wrong gender resultgen = result['Gender'][0] if resultgen != gender: continue # skip result if runner's age doesn't match the age within the result # sometimes athlinks stores the age group of the runner, not exact age, # so also check if this runner's age is within the age group, and indicate if so dt_racedate = timeu.epoch2dt(e_racedate) racedateage = timeu.age(dt_racedate,dt_dob) resultage = int(result['Age']) result['fuzzyage'] = False if resultage != racedateage: # if results are not stored as age group, skip this result if (resultage/5)*5 != resultage: continue # result's age might be age group, not exact age else: # if runner's age consistent with race age, use result, but mark "fuzzy" if (racedateage/5)*5 == resultage: result['fuzzyage'] = True # otherwise skip result else: continue # if we reach here, the result is ok, and is added to filteredresults filteredresults.append(result) # back to caller return filteredresults #---------------------------------------------------------------------- def convertserviceresult(self, result): #---------------------------------------------------------------------- ''' converts a single service result to dict suitable to be saved in resultfile result must be converted to dict with keys in `resultfilehdr` provided at instance creation must be overridden when ResultsCollect is instantiated use return value of None for cases when results could not be filtered by `:meth:getresults` :param fname: participant's first name :param lname: participant's last name :param result: single service result, from list retrieved through `getresults` :rtype: dict with keys matching `resultfilehdr`, or None if result is not to be saved ''' # create output record and copy common fields outrec = {} # copy participant information outrec['name'] = self.name outrec['GivenName'] = self.fname outrec['FamilyName'] = self.lname outrec['DOB'] = self.dob outrec['Gender'] = self.gender # some debug items - assume everything is cached coursecached = True racecached = True # get course used for this result courseid = '{}/{}'.format(result['Race']['RaceID'], result['CourseID']) course = Course.query.filter_by(club_id=self.club_id, source='athlinks', sourceid=courseid).first() # cache course if not done already race = None if not course: coursecached = False coursedata = self.service.getcourse(result['Race']['RaceID'], result['CourseID']) distmiles = athlinks.dist2miles(coursedata['Courses'][0]['DistUnit'],coursedata['Courses'][0]['DistTypeID']) distkm = athlinks.dist2km(coursedata['Courses'][0]['DistUnit'],coursedata['Courses'][0]['DistTypeID']) if distkm < 0.050: return None # skip timed events, which seem to be recorded with 0 distance # skip result if not Running or Trail Running race thiscategory = coursedata['Courses'][0]['RaceCatID'] if thiscategory not in race_category: return None course = Course() course.club_id = self.club_id course.source = 'athlinks' course.sourceid = courseid # strip racename and coursename here to make sure detail file matches what is stored in database racename = csvu.unicode2ascii(coursedata['RaceName']).strip() coursename = csvu.unicode2ascii(coursedata['Courses'][0]['CourseName']).strip() course.name = '{} / {}'.format(racename,coursename) # maybe truncate to FIRST part of race name if len(course.name) > MAX_RACENAME_LEN: course.name = course.name[:MAX_RACENAME_LEN] course.date = ftime.epoch2asc(athlinks.gettime(coursedata['RaceDate'])) course.location = csvu.unicode2ascii(coursedata['Home']) # maybe truncate to LAST part of location name, to keep most relevant information (state, country) if len(course.location) > MAX_LOCATION_LEN: course.location = course.location[-MAX_LOCATION_LEN:] # TODO: adjust marathon and half marathon distances? course.distkm =distkm course.distmiles = distmiles course.surface = race_category[thiscategory] # retrieve or add race # flush should allow subsequent query per http://stackoverflow.com/questions/4201455/sqlalchemy-whats-the-difference-between-flush-and-commit # Race has uniqueconstraint for club_id/name/year/fixeddist. It's been seen that there are additional races in athlinks, # but just assume the first is the correct one. raceyear = ftime.asc2dt(course.date).year race = Race.query.filter_by(club_id=self.club_id, name=course.name, year=raceyear, fixeddist=race_fixeddist(course.distmiles)).first() ### TODO: should the above be .all() then check for first race within epsilon distance? if not race: racecached = False race = Race(self.club_id, raceyear) race.name = course.name race.distance = course.distmiles race.fixeddist = race_fixeddist(race.distance) race.date = course.date race.active = True race.external = True race.surface = course.surface loc = self.locsvr.getlocation(course.location) race.locationid = loc.id db.session.add(race) db.session.flush() # force id to be created course.raceid = race.id db.session.add(course) db.session.flush() # force id to be created # maybe course was cached but location of race wasn't # update location of result race, if needed, and if supplied # this is here to clean up old database data if not race: race = Race.query.filter_by(club_id=self.club_id, name=course.name, year=ftime.asc2dt(course.date).year, fixeddist=race_fixeddist(course.distmiles)).first() if not race.locationid and course.location: # app.logger.debug('updating race with location {}'.format(course.location)) loc = self.locsvr.getlocation(course.location) race.locationid = loc.id insert_or_update(db.session, Race, race, skipcolumns=['id'], club_id=self.club_id, name=course.name, year=ftime.asc2dt(course.date).year, fixeddist=race_fixeddist(course.distmiles)) # else: # app.logger.debug('race.locationid={} course.location="{}"'.format(race.locationid, course.location)) # debug races if self.racefile: racestatusl = [] if not coursecached: racestatusl.append('addcourse') if not racecached: racestatusl.append('addrace') if not racestatusl: racestatusl.append('cached') racestatus = '-'.join(racestatusl) racerow = {'status': racestatus, 'runner': self.name} for racefield in self.racefields: if racefield in ['status', 'runner']: continue racerow[racefield] = getattr(course,racefield) self.RACE.writerow(racerow) # fill in output record fields from result, course # combine name, get age outrec['age'] = result['Age'] outrec['fuzzyage'] = result['fuzzyage'] # leave athlid blank if result not from an athlink member athlmember = result['IsMember'] if athlmember: outrec['athlid'] = result['RacerID'] # remember the entryid, high water mark of which can be used to limit the work here outrec['entryid'] = result['EntryID'] # race name, location; convert from unicode if necessary # TODO: make function to do unicode translation -- apply to runner name as well (or should csv just store unicode?) outrec['race'] = course.name outrec['date'] = course.date outrec['loc'] = course.location outrec['miles'] = course.distmiles outrec['km'] = course.distkm outrec['category'] = course.surface resulttime = result['TicksString'] # strange case of TicksString = ':00' if resulttime[0] == ':': resulttime = '0'+resulttime while resulttime.count(':') < 2: resulttime = '0:'+resulttime outrec['time'] = resulttime # strange case of 0 time, causes ZeroDivisionError and is clearly not valid if timeu.timesecs(resulttime) == 0: return None # leave out age grade if exception occurs, skip results which have outliers try: # skip result if runner's age doesn't match the age within the result # sometimes athlinks stores the age group of the runner, not exact age, # so also check if this runner's age is within the age group, and indicate if so e_racedate = athlinks.gettime(result['Race']['RaceDate']) resultgen = result['Gender'][0] dt_racedate = timeu.epoch2dt(e_racedate) racedateage = timeu.age(dt_racedate,self.dt_dob) agpercent,agresult,agfactor = ag.agegrade(racedateage,resultgen,course.distmiles,timeu.timesecs(resulttime)) outrec['ag'] = agpercent if agpercent < 15 or agpercent >= 100: return None # skip obvious outliers except: app.logger.warning(traceback.format_exc()) pass # and we're done return outrec #---------------------------------------------------------------------- def closeservice(self): #---------------------------------------------------------------------- ''' closes service, if necessary may be overridden when ResultsCollect is instantiated ''' if self.racefile: self._RACE.close()
class FrameMain(wx.Frame): def __init__(self, title, pool): self.pool = pool self.lock = threading.Lock() self.sdr = None self.threadScan = None self.threadUpdate = None self.threadLocation = None self.serverLocation = None self.isNewScan = True self.isScanning = False self.stopAtEnd = False self.stopScan = False self.dlgCal = None self.dlgSats = None self.dlgLog = None self.menuMain = None self.menuPopup = None self.graph = None self.toolbar = None self.canvas = None self.buttonStart = None self.buttonStop = None self.controlGain = None self.choiceMode = None self.choiceDwell = None self.choiceNfft = None self.spinCtrlStart = None self.spinCtrlStop = None self.choiceDisplay = None self.spectrum = OrderedDict() self.scanInfo = ScanInfo() self.locations = OrderedDict() self.lastLocation = [None] * 4 self.isSaved = True self.settings = Settings() self.devicesRtl = get_devices_rtl(self.settings.devicesRtl) self.settings.indexRtl = limit(self.settings.indexRtl, 0, len(self.devicesRtl) - 1) self.filename = "" self.oldCal = 0 self.remoteControl = None self.log = Log() self.pageConfig = wx.PageSetupDialogData() self.pageConfig.GetPrintData().SetOrientation(wx.LANDSCAPE) self.pageConfig.SetMarginTopLeft((20, 20)) self.pageConfig.SetMarginBottomRight((20, 20)) self.printConfig = wx.PrintDialogData(self.pageConfig.GetPrintData()) self.printConfig.EnableSelection(False) self.printConfig.EnablePageNumbers(False) wx.Frame.__init__(self, None, title=title) self.timerGpsRetry = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.__on_gps_retry, self.timerGpsRetry) self.Bind(wx.EVT_CLOSE, self.__on_exit) self.status = Statusbar(self, self.log) self.status.set_info(title) self.SetStatusBar(self.status) add_colours() self.__create_widgets() self.__create_menu() self.__create_popup_menu() self.__set_control_state(True) self.Show() displaySize = wx.DisplaySize() toolbarSize = self.toolbar.GetBestSize() self.SetClientSize((toolbarSize[0] + 10, displaySize[1] / 2)) self.SetMinSize((displaySize[0] / 4, displaySize[1] / 4)) self.Connect(-1, -1, EVENT_THREAD, self.__on_event) self.SetDropTarget(DropTarget(self)) self.SetIcon(load_icon('rtlsdr_scan')) self.steps = 0 self.stepsTotal = 0 self.__start_gps() self.__start_location_server() def __create_widgets(self): self.remoteControl = RemoteControl() self.graph = PanelGraph(self, self, self.settings, self.status, self.remoteControl) self.toolbar = wx.Panel(self) self.buttonStart = MultiButton(self.toolbar, ['Start', 'Continue'], ['Start new scan', 'Continue scanning']) self.buttonStart.SetSelected(self.settings.startOption) self.buttonStop = MultiButton(self.toolbar, ['Stop', 'Stop at end'], ['Stop scan', 'Stop scan at end']) self.buttonStop.SetSelected(self.settings.stopOption) self.Bind(wx.EVT_BUTTON, self.__on_start, self.buttonStart) self.Bind(wx.EVT_BUTTON, self.__on_stop, self.buttonStop) textRange = wx.StaticText(self.toolbar, label="Range (MHz)", style=wx.ALIGN_CENTER) textStart = wx.StaticText(self.toolbar, label="Start") textStop = wx.StaticText(self.toolbar, label="Stop") self.spinCtrlStart = wx.SpinCtrl(self.toolbar) self.spinCtrlStop = wx.SpinCtrl(self.toolbar) self.spinCtrlStart.SetToolTipString('Start frequency') self.spinCtrlStop.SetToolTipString('Stop frequency') self.spinCtrlStart.SetRange(F_MIN, F_MAX - 1) self.spinCtrlStop.SetRange(F_MIN + 1, F_MAX) self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStart) self.Bind(wx.EVT_SPINCTRL, self.__on_spin, self.spinCtrlStop) textGain = wx.StaticText(self.toolbar, label="Gain (dB)") self.controlGain = wx.Choice(self.toolbar, choices=['']) textMode = wx.StaticText(self.toolbar, label="Mode") self.choiceMode = wx.Choice(self.toolbar, choices=MODE[::2]) self.choiceMode.SetToolTipString('Scanning mode') textDwell = wx.StaticText(self.toolbar, label="Dwell") self.choiceDwell = wx.Choice(self.toolbar, choices=DWELL[::2]) self.choiceDwell.SetToolTipString('Scan time per step') textNfft = wx.StaticText(self.toolbar, label="FFT size") self.choiceNfft = wx.Choice(self.toolbar, choices=map(str, NFFT)) self.choiceNfft.SetToolTipString('Higher values for greater' 'precision') textDisplay = wx.StaticText(self.toolbar, label="Display") self.choiceDisplay = wx.Choice(self.toolbar, choices=DISPLAY[::2]) self.Bind(wx.EVT_CHOICE, self.__on_choice, self.choiceDisplay) self.choiceDisplay.SetToolTipString('Spectrogram available in' 'continuous mode') grid = wx.GridBagSizer(5, 5) grid.Add(self.buttonStart, pos=(0, 0), span=(3, 1), flag=wx.ALIGN_CENTER) grid.Add(self.buttonStop, pos=(0, 1), span=(3, 1), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 2)) grid.Add(textRange, pos=(0, 3), span=(1, 4), flag=wx.ALIGN_CENTER) grid.Add(textStart, pos=(1, 3), flag=wx.ALIGN_CENTER) grid.Add(self.spinCtrlStart, pos=(1, 4)) grid.Add(textStop, pos=(1, 5), flag=wx.ALIGN_CENTER) grid.Add(self.spinCtrlStop, pos=(1, 6)) grid.Add(textGain, pos=(0, 7), flag=wx.ALIGN_CENTER) grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 8)) grid.Add(textMode, pos=(0, 9), flag=wx.ALIGN_CENTER) grid.Add(self.choiceMode, pos=(1, 9), flag=wx.ALIGN_CENTER) grid.Add(textDwell, pos=(0, 10), flag=wx.ALIGN_CENTER) grid.Add(self.choiceDwell, pos=(1, 10), flag=wx.ALIGN_CENTER) grid.Add(textNfft, pos=(0, 11), flag=wx.ALIGN_CENTER) grid.Add(self.choiceNfft, pos=(1, 11), flag=wx.ALIGN_CENTER) grid.Add((20, 1), pos=(0, 12)) grid.Add(textDisplay, pos=(0, 13), flag=wx.ALIGN_CENTER) grid.Add(self.choiceDisplay, pos=(1, 13), flag=wx.ALIGN_CENTER) self.__set_controls() self.__set_gain_control() self.toolbar.SetSizer(grid) self.toolbar.Layout() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.graph, 1, wx.EXPAND) sizer.Add(self.toolbar, 0, wx.EXPAND) self.SetSizer(sizer) self.Layout() def __create_menu(self): self.menuMain = MenuMain(self, self.settings) self.Bind(wx.EVT_MENU, self.__on_new, self.menuMain.new) self.Bind(wx.EVT_MENU, self.__on_open, self.menuMain.open) self.Bind(wx.EVT_MENU, self.__on_merge, self.menuMain.merge) self.Bind(wx.EVT_MENU_RANGE, self.__on_file_history, id=wx.ID_FILE1, id2=wx.ID_FILE9) self.Bind(wx.EVT_MENU, self.__on_save, self.menuMain.save) self.Bind(wx.EVT_MENU, self.__on_export_scan, self.menuMain.exportScan) self.Bind(wx.EVT_MENU, self.__on_export_image, self.menuMain.exportImage) self.Bind(wx.EVT_MENU, self.__on_export_image_seq, self.menuMain.exportSeq) self.Bind(wx.EVT_MENU, self.__on_export_geo, self.menuMain.exportGeo) self.Bind(wx.EVT_MENU, self.__on_export_track, self.menuMain.exportTrack) self.Bind(wx.EVT_MENU, self.__on_page, self.menuMain.page) self.Bind(wx.EVT_MENU, self.__on_preview, self.menuMain.preview) self.Bind(wx.EVT_MENU, self.__on_print, self.menuMain.printer) self.Bind(wx.EVT_MENU, self.__on_properties, self.menuMain.properties) self.Bind(wx.EVT_MENU, self.__on_exit, self.menuMain.close) self.Bind(wx.EVT_MENU, self.__on_pref, self.menuMain.pref) self.Bind(wx.EVT_MENU, self.__on_adv_pref, self.menuMain.advPref) self.Bind(wx.EVT_MENU, self.__on_formatting, self.menuMain.formatting) self.Bind(wx.EVT_MENU, self.__on_devices_rtl, self.menuMain.devicesRtl) self.Bind(wx.EVT_MENU, self.__on_devices_gps, self.menuMain.devicesGps) self.Bind(wx.EVT_MENU, self.__on_reset, self.menuMain.reset) self.Bind(wx.EVT_MENU, self.__on_clear_select, self.menuMain.clearSelect) self.Bind(wx.EVT_MENU, self.__on_show_measure, self.menuMain.showMeasure) self.Bind(wx.EVT_MENU, self.__on_start, self.menuMain.start) self.Bind(wx.EVT_MENU, self.__on_continue, self.menuMain.cont) self.Bind(wx.EVT_MENU, self.__on_stop, self.menuMain.stop) self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuMain.stopEnd) self.Bind(wx.EVT_MENU, self.__on_compare, self.menuMain.compare) self.Bind(wx.EVT_MENU, self.__on_smooth, self.menuMain.smooth) self.Bind(wx.EVT_MENU, self.__on_cal, self.menuMain.cal) self.Bind(wx.EVT_MENU, self.__on_gearth, self.menuMain.gearth) self.Bind(wx.EVT_MENU, self.__on_gmaps, self.menuMain.gmaps) self.Bind(wx.EVT_MENU, self.__on_sats, self.menuMain.sats) self.Bind(wx.EVT_MENU, self.__on_loc_clear, self.menuMain.locClear) self.Bind(wx.EVT_MENU, self.__on_log, self.menuMain.log) self.Bind(wx.EVT_MENU, self.__on_help, self.menuMain.helpLink) self.Bind(wx.EVT_MENU, self.__on_update, self.menuMain.update) self.Bind(wx.EVT_MENU, self.__on_sys_info, self.menuMain.sys) self.Bind(wx.EVT_MENU, self.__on_about, self.menuMain.about) idF1 = wx.wx.NewId() self.Bind(wx.EVT_MENU, self.__on_help, id=idF1) accelTable = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_F1, idF1)]) self.SetAcceleratorTable(accelTable) self.Bind(wx.EVT_MENU_HIGHLIGHT, self.__on_menu_highlight) self.SetMenuBar(self.menuMain.menuBar) def __create_popup_menu(self): self.menuPopup = PopMenuMain(self.settings) self.Bind(wx.EVT_MENU, self.__on_start, self.menuPopup.start) self.Bind(wx.EVT_MENU, self.__on_continue, self.menuPopup.cont) self.Bind(wx.EVT_MENU, self.__on_stop, self.menuPopup.stop) self.Bind(wx.EVT_MENU, self.__on_stop_end, self.menuPopup.stopEnd) self.Bind(wx.EVT_MENU, self.__on_range_lim, self.menuPopup.rangeLim) self.Bind(wx.EVT_MENU, self.__on_points_lim, self.menuPopup.pointsLim) self.Bind(wx.EVT_MENU, self.__on_clear_select, self.menuPopup.clearSelect) self.Bind(wx.EVT_MENU, self.__on_show_measure, self.menuPopup.showMeasure) self.Bind(wx.EVT_CONTEXT_MENU, self.__on_popup_menu) def __on_menu_highlight(self, event): item = self.GetMenuBar().FindItemById(event.GetId()) if item is not None: help = item.GetHelp() else: help = '' self.status.set_general(help, level=None) def __on_popup_menu(self, event): if not isinstance(event.GetEventObject(), NavigationToolbar): pos = event.GetPosition() pos = self.ScreenToClient(pos) self.PopupMenu(self.menuPopup.menu, pos) def __on_new(self, _event): if self.__save_warn(Warn.NEW): return True self.spectrum.clear() self.locations.clear() self.__saved(True) self.__set_plot(self.spectrum, False) self.graph.clear_selection() self.__set_control_state(True) return False def __on_open(self, _event): if self.__save_warn(Warn.OPEN): return dlg = wx.FileDialog(self, "Open a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.OPEN) if dlg.ShowModal() == wx.ID_OK: self.open(dlg.GetDirectory(), dlg.GetFilename()) dlg.Destroy() def __on_merge(self, _event): if self.__save_warn(Warn.MERGE): return dlg = wx.FileDialog(self, "Merge a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.OPEN) if dlg.ShowModal() == wx.ID_OK: self.__merge(dlg.GetDirectory(), dlg.GetFilename()) dlg.Destroy() def __on_file_history(self, event): selection = event.GetId() - wx.ID_FILE1 path = self.settings.fileHistory.GetHistoryFile(selection) self.settings.fileHistory.AddFileToHistory(path) dirname, filename = os.path.split(path) self.open(dirname, filename) def __on_save(self, _event): dlg = wx.FileDialog(self, "Save a scan", self.settings.dirScans, self.filename, File.get_type_filters(File.Types.SAVE), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Saving...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.filename = os.path.splitext(fileName)[0] self.settings.dirScans = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.SAVE) fullName = os.path.join(dirName, fileName) save_plot(fullName, self.scanInfo, self.spectrum, self.locations) self.__saved(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory(fullName) dlg.Destroy() def __on_export_scan(self, _event): dlg = wx.FileDialog(self, "Export a scan", self.settings.dirExport, self.filename, File.get_type_filters(), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.PLOT) fullName = os.path.join(dirName, fileName) export_plot(fullName, dlg.GetFilterIndex(), self.spectrum) self.status.set_general("Finished") dlg.Destroy() def __on_export_image(self, _event): dlgFile = wx.FileDialog(self, "Export image to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.IMAGE), wx.SAVE | wx.OVERWRITE_PROMPT) dlgFile.SetFilterIndex(File.ImageType.PNG) if dlgFile.ShowModal() == wx.ID_OK: dlgImg = DialogImageSize(self, self.settings) if dlgImg.ShowModal() != wx.ID_OK: dlgFile.Destroy() return self.status.set_general("Exporting...") fileName = dlgFile.GetFilename() dirName = dlgFile.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlgFile.GetFilterIndex(), File.Types.IMAGE) fullName = os.path.join(dirName, fileName) exportType = dlgFile.GetFilterIndex() export_image(fullName, exportType, self.graph.get_figure(), self.settings) self.status.set_general("Finished") dlgFile.Destroy() def __on_export_image_seq(self, _event): dlgSeq = DialogExportSeq(self, self.spectrum, self.settings) dlgSeq.ShowModal() dlgSeq.Destroy() def __on_export_geo(self, _event): dlgGeo = DialogExportGeo(self, self.spectrum, self.locations, self.settings) if dlgGeo.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") extent = dlgGeo.get_extent() dlgFile = wx.FileDialog(self, "Export map to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.GEO), wx.SAVE | wx.OVERWRITE_PROMPT) dlgFile.SetFilterIndex(File.GeoType.KMZ) if dlgFile.ShowModal() == wx.ID_OK: fileName = dlgFile.GetFilename() dirName = dlgFile.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlgFile.GetFilterIndex(), File.Types.GEO) fullName = os.path.join(dirName, fileName) exportType = dlgFile.GetFilterIndex() image = None xyz = None if exportType == File.GeoType.CSV: xyz = dlgGeo.get_xyz() else: image = dlgGeo.get_image() export_map(fullName, exportType, extent, image, xyz) self.status.set_general("Finished") dlgFile.Destroy() dlgGeo.Destroy() def __on_export_track(self, _event): dlg = wx.FileDialog(self, "Export GPS to file", self.settings.dirExport, self.filename, File.get_type_filters(File.Types.TRACK), wx.SAVE | wx.OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: self.status.set_general("Exporting...") fileName = dlg.GetFilename() dirName = dlg.GetDirectory() self.settings.dirExport = dirName fileName = extension_add(fileName, dlg.GetFilterIndex(), File.Types.TRACK) fullName = os.path.join(dirName, fileName) export_gpx(fullName, self.locations, self.GetName()) self.status.set_general("Finished") dlg.Destroy() def __on_page(self, _event): dlg = wx.PageSetupDialog(self, self.pageConfig) if dlg.ShowModal() == wx.ID_OK: self.pageConfig = wx.PageSetupDialogData( dlg.GetPageSetupDialogData()) self.printConfig.SetPrintData(self.pageConfig.GetPrintData()) dlg.Destroy() def __on_preview(self, _event): printout = PrintOut(self.graph, self.filename, self.pageConfig) printoutPrinting = PrintOut(self.graph, self.filename, self.pageConfig) preview = wx.PrintPreview(printout, printoutPrinting, self.printConfig) frame = wx.PreviewFrame(preview, self, 'Print Preview') frame.Initialize() frame.SetSize(self.GetSize()) frame.Show(True) def __on_print(self, _event): printer = wx.Printer(self.printConfig) printout = PrintOut(self.graph, self.filename, self.pageConfig) if printer.Print(self, printout, True): self.printConfig = wx.PrintDialogData(printer.GetPrintDialogData()) self.pageConfig.SetPrintData(self.printConfig.GetPrintData()) def __on_properties(self, _event): if len(self.spectrum) > 0: self.scanInfo.timeFirst = min(self.spectrum) self.scanInfo.timeLast = max(self.spectrum) dlg = DialogProperties(self, self.scanInfo) dlg.ShowModal() dlg.Destroy() def __on_exit(self, _event): self.Unbind(wx.EVT_CLOSE) if self.__save_warn(Warn.EXIT): self.Bind(wx.EVT_CLOSE, self.__on_exit) return self.__scan_stop(False) self.__stop_gps(False) self.__stop_location_server() self.__get_controls() self.settings.devicesRtl = self.devicesRtl self.settings.save() self.graph.close() self.Destroy() def __on_pref(self, _event): self.__get_controls() dlg = DialogPrefs(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.graph.create_plot() self.__set_control_state(True) self.__set_controls() dlg.Destroy() def __on_adv_pref(self, _event): dlg = DialogAdvPrefs(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.__set_control_state(True) dlg.Destroy() def __on_formatting(self, _event): dlg = DialogFormatting(self, self.settings) if dlg.ShowModal() == wx.ID_OK: self.__set_control_state(True) self.graph.update_measure() self.graph.redraw_plot() dlg.Destroy() def __on_devices_rtl(self, _event): self.__get_controls() self.devicesRtl = self.__refresh_devices() dlg = DialogDevicesRTL(self, self.devicesRtl, self.settings) if dlg.ShowModal() == wx.ID_OK: self.devicesRtl = dlg.get_devices() self.settings.indexRtl = dlg.get_index() self.__set_gain_control() self.__set_control_state(True) self.__set_controls() dlg.Destroy() def __on_devices_gps(self, _event): self.__stop_gps() self.status.set_gps('GPS Stopped') dlg = DialogDevicesGPS(self, self.settings) dlg.ShowModal() dlg.Destroy() self.__start_gps() def __on_reset(self, _event): dlg = wx.MessageDialog( self, 'Reset all settings to the default values\n' '(cannot be undone)?', 'Reset Settings', wx.YES_NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: self.devicesRtl = [] self.settings.reset() self.__set_controls() self.graph.create_plot() dlg.Destroy() def __on_compare(self, _event): dlg = DialogCompare(self, self.settings, self.filename) dlg.Show() def __on_smooth(self, _event): dlg = DialogSmooth(self, self.spectrum, self.settings) if dlg.ShowModal() == wx.ID_OK: saved = self.isSaved self.isSaved = False if not self.__on_new(None): self.spectrum.clear() spectrum = dlg.get_spectrum() self.spectrum.update(OrderedDict(sorted(spectrum.items()))) self.__set_plot(self.spectrum, False) self.graph.update_measure() self.graph.redraw_plot() self.__saved(False) else: self.__saved(saved) def __on_clear_select(self, _event): self.graph.clear_selection() def __on_show_measure(self, event): show = event.Checked() self.menuMain.showMeasure.Check(show) self.menuPopup.showMeasure.Check(show) self.settings.showMeasure = show self.graph.show_measure_table(show) self.Layout() def __on_cal(self, _event): self.dlgCal = DialogAutoCal(self, self.settings.calFreq, self.__auto_cal) self.dlgCal.ShowModal() def __on_gearth(self, _event): tempPath = tempfile.mkdtemp() tempFile = os.path.join(tempPath, 'RTLSDRScannerLink.kml') handle = open(tempFile, 'wb') create_gearth(handle) handle.close() if not run_file(tempFile): wx.MessageBox('Error starting Google Earth', 'Error', wx.OK | wx.ICON_ERROR) def __on_gmaps(self, _event): url = 'http://localhost:{}/rtlsdr_scan.html'.format(LOCATION_PORT) webbrowser.open_new(url) def __on_sats(self, _event): if self.dlgSats is None: self.dlgSats = DialogSats(self) self.dlgSats.Show() def __on_loc_clear(self, _event): result = wx.MessageBox( 'Remove {} locations from scan?'.format(len(self.locations)), 'Clear location data', wx.YES_NO, self) if result == wx.YES: self.locations.clear() self.__set_control_state(True) def __on_log(self, _event): if self.dlgLog is None: self.dlgLog = DialogLog(self, self.log) self.dlgLog.Show() def __on_help(self, _event): webbrowser.open("http://eartoearoak.com/software/rtlsdr-scanner") def __on_update(self, _event): if self.threadUpdate is None: self.status.set_general("Checking for updates", level=None) self.threadUpdate = Thread(target=self.__update_check) self.threadUpdate.start() def __on_sys_info(self, _event): dlg = DialogSysInfo(self) dlg.ShowModal() dlg.Destroy() def __on_about(self, _event): dlg = DialogAbout(self) dlg.ShowModal() dlg.Destroy() def __on_spin(self, event): control = event.GetEventObject() if control == self.spinCtrlStart: self.spinCtrlStop.SetRange(self.spinCtrlStart.GetValue() + 1, F_MAX) def __on_choice(self, _event): self.__get_controls() self.graph.create_plot() def __on_start(self, event): self.__get_controls() if self.settings.start >= self.settings.stop: wx.MessageBox('Stop frequency must be greater that start', 'Warning', wx.OK | wx.ICON_WARNING) return self.devicesRtl = self.__refresh_devices() if len(self.devicesRtl) == 0: wx.MessageBox('No devices found', 'Error', wx.OK | wx.ICON_ERROR) else: if event.GetInt() == 0: self.isNewScan = True else: self.isNewScan = False self.__scan_start() if not self.settings.retainScans: self.status.set_info( 'Warning: Averaging is enabled in preferences', level=Log.WARN) def __on_continue(self, event): event.SetInt(1) self.__on_start(event) def __on_stop(self, event): if event.GetInt() == 0: self.stopScan = True self.stopAtEnd = False self.__scan_stop() else: self.stopScan = False self.stopAtEnd = True def __on_stop_end(self, _event): self.stopAtEnd = True def __on_range_lim(self, _event): xmin, xmax = self.graph.get_axes().get_xlim() xmin = int(xmin) xmax = math.ceil(xmax) if xmax < xmin + 1: xmax = xmin + 1 self.settings.start = xmin self.settings.stop = xmax self.__set_controls() def __on_points_lim(self, _event): self.settings.pointsLimit = self.menuPopup.pointsLim.IsChecked() self.__set_plot(self.spectrum, self.settings.annotate) def __on_gps_retry(self, _event): self.timerGpsRetry.Stop() self.__stop_gps() self.__start_gps() def __on_event(self, event): status = event.data.get_status() freq = event.data.get_arg1() data = event.data.get_arg2() if status == Event.STARTING: self.status.set_general("Starting") self.isScanning = True elif status == Event.STEPS: self.stepsTotal = (freq + 1) * 2 self.steps = self.stepsTotal self.status.set_progress(0) self.status.show_progress() elif status == Event.CAL: self.__auto_cal(Cal.DONE) elif status == Event.INFO: if self.threadScan is not None: self.sdr = self.threadScan.get_sdr() if data is not None: self.devicesRtl[self.settings.indexRtl].tuner = data self.scanInfo.tuner = data elif status == Event.DATA: self.__saved(False) cal = self.devicesRtl[self.settings.indexRtl].calibration self.pool.apply_async( anaylse_data, (freq, data, cal, self.settings.nfft, self.settings.overlap, self.settings.winFunc), callback=self.__on_process_done) self.__progress() elif status == Event.STOPPED: self.__cleanup() self.status.set_general("Stopped") elif status == Event.FINISHED: self.threadScan = None elif status == Event.ERROR: self.__cleanup() self.status.set_general("Error: {}".format(data), level=Log.ERROR) if self.dlgCal is not None: self.dlgCal.Destroy() self.dlgCal = None elif status == Event.PROCESSED: offset = self.settings.devicesRtl[self.settings.indexRtl].offset if self.settings.alert: alert = self.settings.alertLevel else: alert = None Thread(target=update_spectrum, name='Update', args=(self, self.lock, self.settings.start, self.settings.stop, freq, data, offset, self.spectrum, not self.settings.retainScans, alert)).start() elif status == Event.LEVEL: wx.Bell() elif status == Event.UPDATED: if data and self.settings.liveUpdate: self.__set_plot( self.spectrum, self.settings.annotate and self.settings.retainScans and self.settings.mode == Mode.CONTIN) self.__progress() elif status == Event.DRAW: self.graph.draw() elif status == Event.VER_UPD: self.__update_checked(True, freq, data) elif status == Event.VER_NOUPD: self.__update_checked(False) elif status == Event.VER_UPDFAIL: self.__update_checked(failed=True) elif status == Event.LOC_WARN: self.status.set_gps("{}".format(data), level=Log.WARN) self.status.warn_gps() elif status == Event.LOC_ERR: self.status.set_gps("{}".format(data), level=Log.ERROR) self.status.error_gps() self.threadLocation = None if not self.timerGpsRetry.IsRunning(): self.timerGpsRetry.Start(5000, True) elif status == Event.LOC: self.__update_location(data) elif status == Event.LOC_SAT: if self.dlgSats is not None: self.dlgSats.set_sats(data) wx.YieldIfNeeded() def __on_process_done(self, data): timeStamp, freq, scan = data post_event(self, EventThread(Event.PROCESSED, freq, (timeStamp, scan))) def __auto_cal(self, status): freq = self.dlgCal.get_arg1() if self.dlgCal is not None: if status == Cal.START: self.spinCtrlStart.SetValue(int(freq)) self.spinCtrlStop.SetValue(math.ceil(freq)) self.oldCal = self.devicesRtl[ self.settings.indexRtl].calibration self.devicesRtl[self.settings.indexRtl].calibration = 0 self.__get_controls() self.spectrum.clear() self.locations.clear() if not self.__scan_start(isCal=True): self.dlgCal.reset_cal() elif status == Cal.DONE: ppm = self.__calc_ppm(freq) self.dlgCal.set_cal(ppm) self.__set_control_state(True) elif status == Cal.OK: self.devicesRtl[self.settings. indexRtl].calibration = self.dlgCal.get_cal() self.settings.calFreq = freq self.dlgCal = None elif status == Cal.CANCEL: self.dlgCal = None if len(self.devicesRtl) > 0: self.devicesRtl[ self.settings.indexRtl].calibration = self.oldCal def __calc_ppm(self, freq): with self.lock: timeStamp = max(self.spectrum) spectrum = self.spectrum[timeStamp].copy() for x, y in spectrum.iteritems(): spectrum[x] = (((x - freq) * (x - freq)) + 1) * y peak = max(spectrum, key=spectrum.get) return ((freq - peak) / freq) * 1e6 def __scan_start(self, isCal=False): if self.isNewScan and self.__save_warn(Warn.SCAN): return False if not self.threadScan: self.__set_control_state(False) samples = calc_samples(self.settings.dwell) if self.isNewScan: self.spectrum.clear() self.locations.clear() self.graph.clear_plots() self.isNewScan = False self.status.set_info('', level=None) self.scanInfo.set_from_settings(self.settings) self.scanInfo.time = format_iso_time(time.time()) self.scanInfo.lat = None self.scanInfo.lon = None self.scanInfo.desc = '' self.stopAtEnd = False self.stopScan = False self.threadScan = ThreadScan(self, self.sdr, self.settings, self.settings.indexRtl, samples, isCal) self.filename = "Scan {0:.1f}-{1:.1f}MHz".format( self.settings.start, self.settings.stop) self.graph.set_plot_title() self.__start_gps() return True def __scan_stop(self, join=True): if self.threadScan: self.status.set_general("Stopping") self.threadScan.abort() if join: self.threadScan.join() self.threadScan = None if self.sdr is not None: self.sdr.close() self.__set_control_state(True) def __progress(self): if self.steps == self.stepsTotal: self.status.set_general("Scanning ({} sweeps)".format( len(self.spectrum))) self.steps -= 1 if self.steps > 0 and not self.stopScan: self.status.set_progress( (self.stepsTotal - self.steps) * 100.0 / (self.stepsTotal - 1)) self.status.show_progress() else: self.status.hide_progress() self.__set_plot(self.spectrum, self.settings.annotate) if self.stopScan: self.status.set_general("Stopped") self.__cleanup() elif self.settings.mode == Mode.SINGLE: self.status.set_general("Finished") self.__cleanup() else: if self.settings.mode == Mode.CONTIN: if self.dlgCal is None and not self.stopAtEnd: self.__limit_spectrum() self.__scan_start() else: self.status.set_general("Stopped") self.__cleanup() def __cleanup(self): if self.sdr is not None: self.sdr.close() self.sdr = None self.status.hide_progress() self.steps = 0 self.threadScan = None self.__set_control_state(True) self.stopAtEnd = False self.stopScan = True self.isScanning = False def __remove_last(self, data): while len(data) >= self.settings.retainMax: timeStamp = min(data) del data[timeStamp] def __limit_spectrum(self): with self.lock: self.__remove_last(self.spectrum) self.__remove_last(self.locations) def __start_gps(self): if self.settings.gps and len(self.settings.devicesGps): self.status.enable_gps() if self.threadLocation is None: device = self.settings.devicesGps[self.settings.indexGps] self.threadLocation = ThreadLocation(self, device) else: self.status.disable_gps() def __stop_gps(self, join=True): if self.threadLocation and self.threadLocation.isAlive(): self.threadLocation.stop() if join: self.threadLocation.join() self.threadLocation = None def __start_location_server(self): self.serverLocation = LocationServer(self.locations, self.lastLocation, self.lock, self.log) def __stop_location_server(self): if self.serverLocation: self.serverLocation.close() def __update_location(self, data): i = 0 for loc in data: self.lastLocation[i] = loc i += 1 self.status.pulse_gps() if data[2] is None: gpsStatus = '{:.5f}, {:.5f}, {:.1f}'.format(data[0], data[1]) else: gpsStatus = '{:.5f}, {:.5f}, {:.1f}m'.format( data[0], data[1], data[2]) self.status.set_gps(gpsStatus, level=None) if not self.isScanning: return if self.scanInfo is not None: if data[0] and data[1]: self.scanInfo.lat = str(data[0]) self.scanInfo.lon = str(data[1]) with self.lock: if len(self.spectrum) > 0: self.locations[max(self.spectrum)] = (data[0], data[1], data[2]) def __saved(self, isSaved): self.isSaved = isSaved title = APP_NAME + " - " + self.filename if not isSaved: title += "*" self.SetTitle(title) def __set_plot(self, spectrum, annotate): if len(spectrum) > 0: total = count_points(spectrum) if total > 0: spectrum = sort_spectrum(spectrum) extent = Extent(spectrum) self.graph.set_plot(spectrum, self.settings.pointsLimit, self.settings.pointsMax, extent, annotate) else: self.graph.clear_plots() def __set_control_state(self, state): hasDevices = len(self.devicesRtl) > 0 self.spinCtrlStart.Enable(state) self.spinCtrlStop.Enable(state) self.controlGain.Enable(state) self.choiceMode.Enable(state) self.choiceDwell.Enable(state) self.choiceNfft.Enable(state) self.buttonStart.Enable(state and hasDevices) self.buttonStop.Enable(not state and hasDevices) self.menuMain.set_state(state, self.spectrum, self.locations) self.menuPopup.set_state(state, self.spectrum) def __set_controls(self): self.spinCtrlStart.SetValue(self.settings.start) self.spinCtrlStop.SetValue(self.settings.stop) self.choiceMode.SetSelection(MODE[1::2].index(self.settings.mode)) dwell = calc_real_dwell(self.settings.dwell) try: sel = DWELL[1::2].index(dwell) except ValueError: sel = DWELL[1::2][len(DWELL) / 4] self.choiceDwell.SetSelection(sel) self.choiceNfft.SetSelection(NFFT.index(self.settings.nfft)) self.choiceDisplay.SetSelection(DISPLAY[1::2].index( self.settings.display)) def __set_gain_control(self): grid = self.controlGain.GetContainingSizer() if len(self.devicesRtl) > 0: self.controlGain.Destroy() device = self.devicesRtl[self.settings.indexRtl] if device.isDevice: gains = device.get_gains_str() self.controlGain = wx.Choice(self.toolbar, choices=gains) gain = device.get_closest_gain_str(device.gain) self.controlGain.SetStringSelection(gain) else: self.controlGain = NumCtrl(self.toolbar, integerWidth=3, fractionWidth=1) font = self.controlGain.GetFont() dc = wx.WindowDC(self.controlGain) dc.SetFont(font) size = dc.GetTextExtent('####.#') self.controlGain.SetMinSize((size[0] * 1.2, -1)) self.controlGain.SetValue(device.gain) grid.Add(self.controlGain, pos=(1, 7), flag=wx.ALIGN_CENTER) grid.Layout() def __get_controls(self): self.settings.start = self.spinCtrlStart.GetValue() self.settings.stop = self.spinCtrlStop.GetValue() self.settings.startOption = self.buttonStart.GetSelected() self.settings.stopOption = self.buttonStop.GetSelected() self.settings.mode = MODE[1::2][self.choiceMode.GetSelection()] self.settings.dwell = DWELL[1::2][self.choiceDwell.GetSelection()] self.settings.nfft = NFFT[self.choiceNfft.GetSelection()] self.settings.display = DISPLAY[1::2][ self.choiceDisplay.GetSelection()] if len(self.devicesRtl) > 0: device = self.devicesRtl[self.settings.indexRtl] try: if device.isDevice: device.gain = float(self.controlGain.GetStringSelection()) else: device.gain = self.controlGain.GetValue() except ValueError: device.gain = 0 def __save_warn(self, warnType): if self.settings.saveWarn and not self.isSaved: dlg = DialogSaveWarn(self, warnType) code = dlg.ShowModal() if code == wx.ID_YES: self.__on_save(None) if self.isSaved: return False else: return True elif code == wx.ID_NO: return False else: return True return False def __update_check(self): local = get_version_timestamp(True) try: remote = get_version_timestamp_repo() except IOError: post_event(self, EventThread(Event.VER_UPDFAIL)) return if remote > local: post_event(self, EventThread(Event.VER_UPD, local, remote)) else: post_event(self, EventThread(Event.VER_NOUPD)) def __update_checked(self, updateFound=False, local=None, remote=None, failed=False): self.threadUpdate = None self.status.set_general("", level=None) if failed: icon = wx.ICON_ERROR message = "Update check failed" else: icon = wx.ICON_INFORMATION if updateFound: message = "Update found\n\n" message += "Local: " + time.strftime('%c', time.localtime(local)) message += "\nRemote: " + time.strftime( '%c', time.localtime(remote)) else: message = "No updates found" dlg = wx.MessageDialog(self, message, "Update", wx.OK | icon) dlg.ShowModal() dlg.Destroy() def __refresh_devices(self): self.settings.devicesRtl = get_devices_rtl(self.devicesRtl, self.status) self.settings.indexRtl = limit(self.settings.indexRtl, 0, len(self.devicesRtl) - 1) self.settings.save() return self.settings.devicesRtl def __merge(self, dirname, filename): if not os.path.exists(os.path.join(dirname, filename)): wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR) return self.filename = os.path.splitext(filename)[0] self.settings.dirScans = dirname self.status.set_general("Merging: {}".format(filename)) _scanInfo, spectrum, locations = open_plot(dirname, filename) if len(spectrum) > 0: spectrum.update(self.spectrum) locations.update(self.locations) self.spectrum.clear() self.locations.clear() self.spectrum.update(OrderedDict(sorted(spectrum.items()))) self.locations.update(OrderedDict(sorted(locations.items()))) self.__set_plot(self.spectrum, self.settings.annotate) self.graph.scale_plot(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory( os.path.join(dirname, filename)) else: self.status.set_general("Merge failed", level=Log.ERROR) def open(self, dirname, filename): if not os.path.exists(os.path.join(dirname, filename)): wx.MessageBox('File not found', 'Error', wx.OK | wx.ICON_ERROR) return self.__on_new(None) self.graph.get_canvas().draw() self.filename = os.path.splitext(filename)[0] self.settings.dirScans = dirname self.status.set_general("Opening: {}".format(filename)) self.scanInfo, spectrum, location = open_plot(dirname, filename) if len(spectrum) > 0: self.scanInfo.set_to_settings(self.settings) self.spectrum = spectrum self.locations.clear() self.locations.update(location) self.__saved(True) self.__set_controls() self.__set_control_state(True) self.__set_plot(spectrum, self.settings.annotate) self.graph.scale_plot(True) self.status.set_general("Finished") self.settings.fileHistory.AddFileToHistory( os.path.join(dirname, filename)) else: self.status.set_general("Open failed", level=Log.ERROR)