def combo_box(self, selected, items=["None", "Happy", "Concentrated", "Bored", "Annoyed", "Angry"]): """ Returns a combo box of the available emotions. """ combo = QComboBox() combo.setStyleSheet("QComboBox { combobox-popup: 0; }") combo.addItems(items) if selected in items: combo.setCurrentIndex(items.index(selected)) combo.resize(300, 30) return combo
def function_box(self, selected): """ Returns the combo box of the available functions. """ items = ["B_i = ln(sum_from_j=1_to_n(t_j^(-d)))", "B_i = ln(n / (1-d)) - d * ln(L)"] combo = QComboBox() combo.setStyleSheet("QComboBox { combobox-popup: 0; }") combo.addItems(items) names = ["baselevel", "optimized"] if selected in names: combo.setCurrentIndex(names.index(selected)) combo.resize(300, 30) return combo
class Panel(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.clean_up_queue = [] self.summoner = SummonerData() self.summoner.getStaticData() ### Search Bar ### ################## #label self.search_label = QLabel(self) self.search_label.move(20, 15) self.search_label.resize(220, 25) self.search_label.setText('Enter summoner name(s):') self.search_label.setStyleSheet("QLabel {font:14pt}") #text field self.search_field = QLineEdit(self) self.search_field.move(260, 15) self.search_field.resize(250, 25) self.search_field.setPlaceholderText("ex: mcnuggets, teltor, ...") self.search_field.setFocusPolicy(Qt.ClickFocus) #search button self.search_button = QPushButton(self) self.search_button.move(520, 15) self.search_button.resize(150, 25) self.search_button.setText('Search Summoner') #region combobox self.region_list = QComboBox(self) self.region_list.move(680, 15) self.region_list.resize(75, 25) regions = ['NA', 'LAN', 'BR', 'LAS', 'EUW', 'EUNE', 'TR', 'RU', 'OCE'] self.region_list.addItems(regions) #error label self.error_label = QLabel(self) self.error_label.move(775, 15) self.error_label.resize(160, 25) self.error_label.setStyleSheet("QLabel {font:14pt}") ### Summoner Information ### ############################ #summoner Icon label self.icon_label = QLabel(self) self.icon_label.setScaledContents(True) self.icon_label.move(260, 50) self.icon_label.resize(110, 110) #name label self.name_label = QLabel(self) self.name_label.move(380, 50) self.name_label.resize(620, 50) self.name_label.setText('SUMMONER NAME') self.name_label.setStyleSheet("QLabel {font:32pt}") #rank label self.rank_label = QLabel(self) self.rank_label.move(380, 100) self.rank_label.resize(200, 60) self.rank_label.setText('summoner rank') self.rank_label.setStyleSheet("QLabel {font:18pt}") #miniseries labels self.series_labels = {} self.pixmap_win = QPixmap() self.pixmap_loss = QPixmap() self.pixmap_n = QPixmap() self.pixmap_win.load("./images/win.png") self.pixmap_loss.load("./images/loss.png") self.pixmap_n.load("./images/n.png") xPos = 600 for x in range(5): match_label = QLabel(self) match_label.move(xPos, 120) match_label.resize(35, 35) match_label.setScaledContents(True) match_label.hide() self.series_labels[x] = match_label xPos += 40 #mastery image labels print 'loading mastery images ...' self.ferocity_tree_images = self.getMasteryImages( self.summoner.ferocityMasteryTree()) self.cunning_tree_images = self.getMasteryImages( self.summoner.cunningMasteryTree()) self.resolve_tree_images = self.getMasteryImages( self.summoner.resolveMasteryTree()) print 'Done' #champion icon image labels print 'loading champion icon images ...' self.championIcons = self.getChampionIconImages( self.summoner.championList()) print 'Done' #overview widget self.overview_widget = QWidget() self.overview_menu = QTabWidget(self.overview_widget) self.overview_menu.resize(720, 270) #runes widget self.runes_widget = QWidget() self.runes_menu = QTabWidget(self.runes_widget) self.runes_menu.resize(720, 270) #masteries widget self.masteries_widget = QWidget() self.masteries_menu = QTabWidget(self.masteries_widget) self.masteries_menu.resize(720, 270) #summoner menu self.menu = QTabWidget(self) self.menu.move(260, 180) self.menu.resize(720, 300) self.menu.addTab(self.overview_widget, "Overview") self.menu.addTab(self.runes_widget, "Runes") self.menu.addTab(self.masteries_widget, "Masteries") self.menu.hide() #summoners buttons self.button_list = {} yPos = 150 for x in range(10): sum_button = QPushButton(self) sum_button.move(50, yPos) sum_button.resize(150, 25) sum_button.hide() self.button_list[x] = sum_button yPos += 25 ### Connecting Widgets ### ########################## self.connect(self.search_button, QtCore.SIGNAL("clicked()"), self.getData) self.connect(self.search_button, QtCore.SIGNAL("clicked()"), self.getRankedData) self.connect(self.button_list[0], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[0].text()))) self.connect(self.button_list[1], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[1].text()))) self.connect(self.button_list[2], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[2].text()))) self.connect(self.button_list[3], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[3].text()))) self.connect(self.button_list[4], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[4].text()))) self.connect(self.button_list[5], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[5].text()))) self.connect(self.button_list[6], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[6].text()))) self.connect(self.button_list[7], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[7].text()))) self.connect(self.button_list[8], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[8].text()))) self.connect(self.button_list[9], QtCore.SIGNAL("clicked()"), lambda: self.displayData(str(self.button_list[9].text()))) ### Window Configuration ### ############################ #window settings self.setGeometry(200, 150, 1000, 500) self.setMaximumSize(1000, 500) self.setWindowTitle('summoner App') self.show() ### GUI methods ### ################### ############### GUI get methods ############### #get data related to given summoner names def getData(self): self.cleanUp() name_list = str(self.search_field.text()).replace( ' ', '').lower().split(',') region = str(self.region_list.currentText()).lower() if name_list != ['']: try: self.summoner.getSummonerData(name_list, region) for x in range(len(name_list)): sum_name = self.summoner.getName(name_list[x]) if sum_name != None: self.button_list[x].setText(sum_name) self.clean_up_queue.append(self.button_list[x]) else: self.button_list[x].setText(name_list[x]) self.clean_up_queue.append(self.button_list[x]) self.button_list[x].setEnabled(False) self.button_list[x].show() except RiotError as e: response = e.message print response if response == 'ServiceUnavailable': self.error_label.setText(response) elif response == '429': self.error_label.setText('Rate limit reached') elif response == 'InternalServerError': self.error_label.setText(response) elif response == 'Unauthorized': self.error_label.setText('Invalid Input') elif response == 'ServerError': self.error_label.setText(response) else: self.error_label.setText('Not Found') except KeyError as k: self.error_label.setText('Invalid Input') #get summoner ranked data def getRankedData(self): if str(self.search_field.text()) != '': region = str(self.region_list.currentText()).lower() try: self.summoner.getRankedData(region) except RiotError: print 'Rank info not found' #get mastery images def getMasteryImages(self, masteryList): pixmap_list = collections.OrderedDict() empty_spaces = 0 for row in masteryList: if len(row['masteryTreeItems']) == 2: #if len(row) == 3: row['masteryTreeItems'].append(None) #row.append(None) for element in row['masteryTreeItems']: #for element in row: if element != None: pixmap = QPixmap() pixmap.loadFromData( self.summoner.getImage( 'mastery', str(element['masteryId']) + '.png')) pixmap_list[element['masteryId']] = pixmap else: pixmap_list['null' + str(empty_spaces)] = None empty_spaces += 1 return pixmap_list #get champion icon images def getChampionIconImages(self, clist): pixmap_list = {} for champ in clist.values(): pixmap = QPixmap() pixmap.loadFromData( self.summoner.getImage('champion', champ['key'] + '.png')) pixmap_list[champ['name']] = pixmap return pixmap_list ############### GUI update methods ############### #removes previous data from GUI def cleanUp(self): self.error_label.setText("") #clears summoner info self.icon_label.setPixmap(QPixmap()) self.icon_label.setStyleSheet("QLabel {}") self.name_label.setText("") self.rank_label.setText("") #hides elements in clean up queue for x in self.clean_up_queue: x.hide() x.setEnabled(True) #display data def displayData(self, buttonName): sum_ID = self.summoner.getID(buttonName) self.displayIcon(buttonName.replace(' ', '').lower()) self.name_label.setText(buttonName) self.displayRank(sum_ID) self.displayMenu(sum_ID) #display summoner icon def displayIcon(self, summoner_name): iconName = self.summoner.getIcon(summoner_name) iconPixmap = QPixmap() self.icon_label.setStyleSheet( "QLabel {border-style: outset; border-width: 3px; border-color: gold}" ) try: iconPixmap.loadFromData( self.summoner.getImage('profileicon', iconName)) self.icon_label.setPixmap(iconPixmap) except RiotError: iconPixmap.load("./images/no_image.png") self.icon_label.setPixmap(iconPixmap) #display summoner rank def displayRank(self, sumID): for x in range(len(self.series_labels)): self.series_labels[x].setPixmap(QPixmap()) try: tier = self.summoner.getTier(sumID) division = self.summoner.getDivision(sumID) points = self.summoner.getLeaguePoints(sumID) self.rank_label.setText(tier + ': ' + division + '\n' + str(points) + ' league points') if points == 100: self.displayMiniseries(sumID) except KeyError: self.rank_label.setText('UNRANKED') #display promotion series def displayMiniseries(self, sumID): progress = self.summoner.getRankSeries(sumID) i = 0 for x in progress: if x == 'W': self.series_labels[i].setPixmap(self.pixmap_win) elif x == 'L': self.series_labels[i].setPixmap(self.pixmap_loss) else: self.series_labels[i].setPixmap(self.pixmap_n) self.clean_up_queue.append(self.series_labels[i]) self.series_labels[i].show() i += 1 #display summoner menu def displayMenu(self, sumID): self.displayOverviewMenu(sumID) self.displayRuneMenu(sumID) self.displayMasteryMenu(sumID) self.clean_up_queue.append(self.menu) self.menu.show() #display overview menu def displayOverviewMenu(self, sumID): self.overview_menu.clear() overview_normal = OverviewWidget() overview_ranked_solo = OverviewWidget() overview_ranked_team = OverviewWidget() overview_champions = ChampionsWidget() overview_normal.showStats(self.summoner.getStats(sumID, 'Normal')) overview_ranked_solo.showStats( self.summoner.getStats(sumID, 'Ranked Solo')) overview_ranked_team.showStats( self.summoner.getStats(sumID, 'Ranked Team')) overview_champions.showStats(self.summoner.getChampionStats(sumID), self.championIcons) self.overview_menu.addTab(overview_normal, 'Normal') self.overview_menu.addTab(overview_ranked_solo, 'Ranked Solo') self.overview_menu.addTab(overview_ranked_team, 'Ranked Team') self.overview_menu.addTab(overview_champions, 'Champions') #display rune menu def displayRuneMenu(self, sumID): self.runes_menu.clear() for x in range(self.summoner.getNumOfRunePages(sumID)): rune_page = RuneWidget() rune_page.setSize(700, 225) rune_page.setName(self.summoner.getRunePageName(sumID, x)) rune_data = self.summoner.getRunes(sumID, x) if rune_data != None: for y in rune_data: rid = self.summoner.getRuneID(y) desc = self.summoner.runeDescription(rid) rname = self.summoner.runeName(rid) rune_page.addRune(rid, desc, rname) rune_page.showRunes() self.runes_menu.addTab(rune_page, str(x + 1)) #display mastery menu def displayMasteryMenu(self, sumID): self.masteries_menu.clear() for x in range(self.summoner.getNumOfMasteryPages(sumID)): mastery_page = MasteryWidget() mastery_page.setMasteryLabels(self.ferocity_tree_images, self.cunning_tree_images, self.resolve_tree_images) mastery_page.setName(self.summoner.getMasteryPageName(sumID, x)) mastery_data = self.summoner.getMasteries(sumID, x) if mastery_data != None: for y in mastery_data: mid = self.summoner.getMasteryID(y) rank = self.summoner.getMasteryRank(y) mastery_page.setMasteryRank(mid, rank) mastery_page.setTotalPoints() self.masteries_menu.addTab(mastery_page, str(x + 1))
class Master(QMainWindow): """A manager to synchronise and control experiment modules. Initiates the Andor camera and connects its completed acquisition signal to the image analysis and the image saving modules. Uses the queue module to create the list of sequences to run, and the bridge module to communicate with Dexter. This master module will define the run number. It must confirm that each Dexter sequence has run successfully in order to stay synchronised. Keyword arguments: state_config -- path to the file that saved the previous state. Default directories for camera settings and image saving are also saved in this file. image_analysis -- a class inheriting QMainWindow that can perform all of the required image analysis methods """ def __init__(self, state_config='.\\state', image_analysis=settings_window): super().__init__() self.types = OrderedDict([('File#', int), ('Date', str), ('CameraConfig', str), ('SaveConfig', str), ('MasterGeometry', intstrlist), ('AnalysisGeometry', intstrlist), ('SequencesGeometry', intstrlist)]) self.stats = OrderedDict([ ('File#', 0), ('Date', time.strftime("%d,%B,%Y")), ('CameraConfig', '.\\andorcamera\\Standard modes\\ExExposure_config.dat'), ('SaveConfig', '.\\config\\config.dat'), ('MasterGeometry', [10, 10, 500, 150]), ('AnalysisGeometry', [1400, 400, 600, 500]), ('SequencesGeometry', [20, 100, 1000, 800]) ]) self.camera_pause = 0 # time in seconds to wait for camera to start acquisition. self.ts = { label: time.time() for label in ['init', 'waiting', 'blocking', 'msg start', 'msg end'] } sv_dirs = event_handler.get_dirs(self.stats['SaveConfig']) # if not any(os.path.exists(svd) for svd in sv_dirs.values()): # ask user to choose valid config file startn = self.restore_state(file_name=state_config) # choose which image analyser to use from number images in sequence self.init_UI(startn) # initialise the thread controlling run # and emitting images self.rn = runnum( camera(config_file=self.stats['CameraConfig']), # Andor camera event_handler(self.stats['SaveConfig']), # image saver image_analysis(results_path=sv_dirs['Results Path: '], im_store_path=sv_dirs['Image Storage Path: '] ), # image analysis atom_window(last_im_path=sv_dirs['Image Storage Path: '] ), # check if atoms are in ROIs to trigger experiment Previewer(), # sequence editor n=startn, m=1, k=0) # now the signals are connected, send camera settings to image analysis if self.rn.cam.initialised > 2: check = self.rn.cam.ApplySettingsFromConfig( self.stats['CameraConfig']) self.rn.server.dxnum.connect( self.Dx_label.setText) # synchronise run number self.rn.server.textin.connect(self.respond) # read TCP messages self.status_label.setText('Initialising...') QTimer.singleShot( 0, self.idle_state) # takes a while for other windows to load # self.rn.check.showMaximized() self.rn.seq.setGeometry(*self.stats['SequencesGeometry']) self.rn.seq.show() self.rn.sw.setGeometry(*self.stats['AnalysisGeometry']) self.rn.sw.show() self.rn.sw.show_analyses(show_all=True) self.mon_win = MonitorStatus() self.mon_win.start_button.clicked.connect(self.start_monitor) self.mon_win.stop_button.clicked.connect(self.stop_monitor) self.rn.monitor.textin[str].connect(self.mon_win.set_label) self.rn.monitor.textin.connect(self.mon_win.set_connected) # set a timer to update the dates 2s after midnight: t0 = time.localtime() QTimer.singleShot((86402 - 3600 * t0[3] - 60 * t0[4] - t0[5]) * 1e3, self.reset_dates) def idle_state(self): """When the master thread is not processing user events, it is in the idle states. The status label is also used as an indicator for DExTer's current state.""" self.status_label.setText('Idle') def restore_state(self, file_name='./state'): """Use the data stored in the given file to restore the file # for synchronisation if it is the same day, and use the same config files.""" try: with open(file_name, 'r') as f: for line in f: if len(line.split( '=')) == 2: # there should only be one = per line key, val = line.replace('\n', '').split('=') try: self.stats[key] = self.types[key](val) except KeyError as e: logger.warning( 'Failed to load PyDex state line: ' + line + '\n' + str(e)) except FileNotFoundError as e: logger.warning( 'PyDex master settings could not find the state file.\n' + str(e)) if self.stats['Date'] == time.strftime( "%d,%B,%Y"): # restore file number return self.stats['File#'] # [Py]DExTer file number else: return 0 def make_label_edit(self, label_text, layout, position=[0, 0, 1, 1], default_text='', validator=False): """Make a QLabel with an accompanying QLineEdit and add them to the given layout with an input validator. The position argument should be [row number, column number, row width, column width].""" label = QLabel(label_text, self) layout.addWidget(label, *position) line_edit = QLineEdit(self) if np.size(position) == 4: position[1] += 1 layout.addWidget(line_edit, *position) line_edit.setText(default_text) if validator: line_edit.setValidator(validator) return label, line_edit def init_UI(self, startn=0): """Create all of the widget objects required startn: the initial run number loaded from previous state""" self.centre_widget = QWidget() self.centre_widget.layout = QGridLayout() self.centre_widget.setLayout(self.centre_widget.layout) self.setCentralWidget(self.centre_widget) #### menubar at top gives options #### menubar = self.menuBar() show_windows = menubar.addMenu('Windows') menu_items = [] for window_title in [ 'Image Analyser', 'Camera Status', 'Image Saver', 'TCP Server', 'Sequence Previewer', 'Atom Checker', 'Monitor' ]: menu_items.append(QAction(window_title, self)) menu_items[-1].triggered.connect(self.show_window) show_windows.addAction(menu_items[-1]) sync_menu = menubar.addMenu('Run Settings') self.sync_toggle = QAction('Sync with DExTer', sync_menu, checkable=True, checked=True) self.sync_toggle.setChecked(True) self.sync_toggle.toggled.connect(self.sync_mode) sync_menu.addAction(self.sync_toggle) self.check_rois = QAction('Trigger on atoms loaded', sync_menu, checkable=True, checked=False) self.check_rois.setChecked(False) self.check_rois.setEnabled(False) # not functional yet sync_menu.addAction(self.check_rois) reset_date = QAction('Reset date', sync_menu, checkable=False) reset_date.triggered.connect(self.reset_dates) sync_menu.addAction(reset_date) #### status of the master program #### self.status_label = QLabel('Initiating...', self) self.centre_widget.layout.addWidget(self.status_label, 0, 0, 1, 3) Dx_label = QLabel('Run #: ', self) self.centre_widget.layout.addWidget(Dx_label, 1, 0, 1, 1) self.Dx_label = QLabel(str(startn), self) self.centre_widget.layout.addWidget(self.Dx_label, 1, 1, 1, 1) # actions that can be carried out self.actions = QComboBox(self) self.actions.addItems([ 'Run sequence', 'Multirun run', 'Pause multirun', 'Resume multirun', 'Cancel multirun', 'TCP load sequence', 'TCP load sequence from string', 'Save DExTer sequence', 'Cancel Python Mode', 'Resync DExTer', 'Start acquisition' ]) self.actions.resize(self.actions.sizeHint()) self.centre_widget.layout.addWidget(self.actions, 2, 0, 1, 1) self.action_button = QPushButton('Go', self, checkable=False) self.action_button.clicked[bool].connect(self.start_action) self.action_button.resize(self.action_button.sizeHint()) self.centre_widget.layout.addWidget(self.action_button, 2, 1, 1, 1) # text box to allow user to specify DExTer sequence file _, self.seq_edit = self.make_label_edit('DExTer sequence file: ', self.centre_widget.layout, position=[3, 0, 1, 1]) # button to load sequence location from file browser self.seq_browse = QPushButton('Browse', self, checkable=False) self.seq_browse.clicked[bool].connect(self.browse_sequence) self.seq_browse.resize(self.seq_browse.sizeHint()) self.centre_widget.layout.addWidget(self.seq_browse, 3, 2, 1, 1) #### choose main window position, dimensions: (xpos,ypos,width,height) self.setGeometry(*self.stats['MasterGeometry']) self.setWindowTitle('PyDex Master') self.setWindowIcon(QIcon('docs/pydexicon.png')) def reset_dates(self, auto=True): """Reset the date in the image saving and analysis, then display the updated date""" t0 = time.localtime() self.stats['Date'] = time.strftime("%d,%B,%Y", t0) date = self.rn.reset_dates(t0) if not hasattr(self.sender(), 'text'): # don't set timer if user pushed button QTimer.singleShot( (86402 - 3600 * t0[3] - 60 * t0[4] - t0[5]) * 1e3, self.reset_dates) # set the next timer to reset dates logger.info(time.strftime("Date reset: %d %B %Y", t0)) def show_window(self): """Show the window of the submodule or adjust its settings.""" if self.sender().text() == 'Image Analyser': self.rn.sw.show() elif self.sender().text() == 'Camera Status': if self.rn.cam.initialised: msg = 'Current state: ' + self.rn.cam.AF.GetStatus( ) + '\nChoose a new config file: ' else: msg = 'Camera not initialised. See log file for details. Press OK to retry.' newfile = self.rn.sw.try_browse( title='Choose new config file', file_type='config (*.dat);;all (*)', defaultpath=os.path.dirname(self.stats['CameraConfig'])) text, ok = QInputDialog.getText( self, 'Camera Status', msg, text=newfile if newfile else self.stats['CameraConfig']) if text and ok: if self.rn.cam.initialised > 2: if self.rn.cam.AF.GetStatus() == 'DRV_ACQUIRING': self.rn.cam.AF.AbortAcquisition() check = self.rn.cam.ApplySettingsFromConfig(text) if not any(check): self.status_label.setText('Camera settings config: ' + text) self.stats['CameraConfig'] = text else: self.status_label.setText( 'Failed to update camera settings.') else: self.reset_camera(text) elif self.sender().text() == 'Image Saver': text, ok = QInputDialog.getText( self, 'Image Saver', self.rn.sv.print_dirs(self.rn.sv.dirs_dict.items()) + '\nEnter the path to a config file to reset the image saver: ', text=self.stats['SaveConfig']) if text and ok: remove_slot(self.rn.im_save, self.rn.sv.add_item, False) self.rn.sv = event_handler(text) if self.rn.sv.image_storage_path: self.status_label.setText('Image Saver config: ' + text) remove_slot(self.rn.im_save, self.rn.sv.add_item, True) self.stats['SaveConfig'] = text else: self.status_label.setText('Failed to find config file.') elif self.sender().text() == 'Sequence Previewer': self.rn.seq.show() elif self.sender().text() == 'TCP Server': info = 'Trigger server is running.\n' if self.rn.trigger.isRunning( ) else 'Trigger server stopped.\n' if self.rn.server.isRunning(): msgs = self.rn.server.get_queue() info += "TCP server is running. %s queued message(s)." % len( msgs) info += '\nCommand Enum | Length |\t Message\n' for enum, textlength, text in msgs[:5]: info += enum + ' | ' + text[:20] if textlength > 20: info += '...' info += '\n' if len(msgs) > 5: info += '...\n' else: info += "TCP server stopped." reply = QMessageBox.question( self, 'TCP Server Status', info + "\nDo you want to restart the server?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.action_button.setEnabled(True) self.rn.seq.mr.mr_queue = [] self.rn.multirun = False self.rn.reset_server( force=True) # stop and then restart the servers self.rn.server.add_message( TCPENUM['TCP read'], 'Sync DExTer run number\n' + '0' * 2000) elif reply == QMessageBox.No: self.rn.reset_server( force=False) # restart the server if it stopped elif self.sender().text() == 'Atom Checker': self.rn.check.showMaximized() elif self.sender().text() == 'Monitor': self.mon_win.show() def start_monitor(self, toggle=True): """Send a TCP command to the monitor to start its acquisition.""" self.mon_win.start_check() self.rn.monitor.add_message(self.rn._n, 'start') def stop_monitor(self, toggle=True): """Send a TCP command to the monitor to stop its acquisition.""" self.mon_win.start_check() self.rn.monitor.add_message(self.rn._n, 'stop') def browse_sequence(self, toggle=True): """Open the file browser to search for a sequence file, then insert the file path into the DExTer sequence file line edit start_dir: the directory to open initially.""" try: if 'PyQt4' in sys.modules: file_name = QFileDialog.getOpenFileName( self, 'Select A Sequence', '', 'Sequence (*.xml);;all (*)') elif 'PyQt5' in sys.modules: file_name, _ = QFileDialog.getOpenFileName( self, 'Select A Sequence', '', 'Sequence (*.xml);;all (*)') if file_name: self.seq_edit.setText(file_name.replace('/', '\\')) self.rn.seq.load_seq_from_file(file_name) except OSError: pass # user cancelled - file not found def reset_camera(self, ancam_config='./andorcamera/ExExposure_config.dat'): """Close the camera and then start it up again with the new setting. Sometimes after being in crop mode the camera fails to reset the ROI and so must be closed and restarted.""" try: self.rn.cam.SafeShutdown() except: logger.warning('Andor camera safe shutdown failed' ) # probably not initialised self.rn.cam = camera(config_file=ancam_config) # Andor camera remove_slot(self.rn.cam.AcquireEnd, self.rn.receive, True) # connect signal self.status_label.setText('Camera settings config: ' + ancam_config) self.stats['CameraConfig'] = ancam_config def start_action(self): """Perform the action currently selected in the actions combobox. Run sequence: Start the camera acquisition, then make DExTer perform a single run of the sequence that is currently loaded. Multirun run: Start the camera acquisition, then make DExTer perform a multirun with the preloaded multirun settings. TCP load sequence from string: Tell DExTer to load in the sequence from a string in XML format. TCP load sequence: Tell DExTer to load in the sequence file at the location in the 'DExTer sequence file' label. Cancel python mode: send the text 'python mode off' which triggers DExTer to exit python mode. Resync DExTer: send a null message just to resync the run number. Start acquisition: start the camera acquiring without telling DExTer to run. Used in unsynced mode.""" action_text = self.actions.currentText() if action_text == 'Start acquisition' and self.action_button.text( ) == 'Go': if self.rn.cam.initialised > 2: if self.sync_toggle.isChecked(): QMessageBox.warning( self, 'Unscyned acquisition', 'Warning: started acquisition in synced mode. Without messages to DExTer, the file ID will not update.' + '\nTry unchecking: "Run Settings" > "Sync with DExTer".' ) self.actions.setEnabled( False) # don't process other actions in this mode self.rn._k = 0 # reset image per run count self.action_button.setText('Stop acquisition') self.rn.cam.start() # start acquisition self.wait_for_cam( ) # wait for camera to initialise before running self.status_label.setText('Camera acquiring') else: logger.warning( 'Master: Tried to start camera acquisition but camera is not initialised.' ) elif action_text == 'Start acquisition' and self.action_button.text( ) == 'Stop acquisition': self.actions.setEnabled(True) self.action_button.setText('Go') self.end_run() if self.rn.server.isRunning(): if action_text == 'Run sequence': # queue up messages: start acquisition, check run number self.action_button.setEnabled( False) # only process 1 run at a time self.rn._k = 0 # reset image per run count self.rn.server.add_message(TCPENUM['TCP read'], 'start acquisition\n' + '0' * 2000) self.rn.monitor.add_message(self.rn._n, 'update run number') elif action_text == 'Multirun run': if self.rn.seq.mr.check_table(): if not self.sync_toggle.isChecked(): self.sync_toggle.setChecked( True) # it's better to multirun in synced mode logger.warning( 'Multirun has changed the "sync with DExTer" setting.' ) status = self.rn.seq.mr.check_mr_params( self.rn.sv.results_path) # add to queue if valid self.check_mr_queue( ) # prevent multiple multiruns occurring simultaneously else: QMessageBox.warning( self, 'Invalid multirun', 'All cells in the multirun table must be populated.') elif action_text == 'Resume multirun': self.rn.multirun_resume(self.status_label.text()) elif action_text == 'Pause multirun': if 'multirun' in self.status_label.text(): self.rn.multirun_go(False, stillrunning=True) elif action_text == 'Cancel multirun': if 'multirun' in self.status_label.text() or self.rn.multirun: if self.rn.check.checking: self.rn.check.rh.trigger.emit( 1) # send software trigger to end self.rn.multirun_go(False) self.rn.seq.mr.ind = 0 self.rn.seq.mr.reset_sequence(self.rn.seq.tr.copy()) elif action_text == 'TCP load sequence from string': self.rn.server.add_message(TCPENUM[action_text], self.rn.seq.tr.seq_txt) elif action_text == 'TCP load sequence': self.rn.server.add_message( TCPENUM[action_text], self.seq_edit.text() + '\n' + '0' * 2000) elif action_text == 'Save DExTer sequence': self.rn.server.add_message( TCPENUM['Save sequence'], 'save log file automatic name\n' + '0' * 2000) elif action_text == 'Cancel Python Mode': self.rn.server.add_message(TCPENUM['TCP read'], 'python mode off\n' + '0' * 2000) self.rn.server.add_message( TCPENUM['TCP read'], 'Resync DExTer\n' + '0' * 2000) # for when it reconnects elif action_text == 'Resync DExTer': self.rn.server.add_message(TCPENUM['TCP read'], 'Resync DExTer\n' + '0' * 2000) def trigger_exp_start(self, n=None): """Atom checker sends signal saying all ROIs have atoms in, start the experiment""" self.rn.check.timer.stop( ) # in case the timer was going to trigger the experiment as well remove_slot(self.rn.trigger.dxnum, self.reset_cam_signals, True) # swap signals when msg confirmed self.rn.trigger.add_message(TCPENUM['TCP read'], 'Go!' * 600) # trigger experiment # QTimer.singleShot(20, self.resend_exp_trigger) # wait in ms def resend_exp_trigger(self, wait=20): """DExTer doesn't always receive the first trigger, send another. wait -- time in ms before checking if the message was received.""" if self.rn.check.checking: self.rn.trigger.add_message(TCPENUM['TCP read'], 'Go!' * 600) # in case the first fails QTimer.singleShot(wait, self.resend_exp_trigger) # wait in ms def reset_cam_signals(self, toggle=True): """Stop sending images to the atom checker, send them to image analysis instead""" self.rn.check.checking = False remove_slot(self.rn.cam.AcquireEnd, self.rn.receive, not self.rn.multirun) # send images to analysis remove_slot(self.rn.cam.AcquireEnd, self.rn.mr_receive, self.rn.multirun) remove_slot(self.rn.cam.AcquireEnd, self.rn.check_receive, False) remove_slot(self.rn.trigger.dxnum, self.reset_cam_signals, False) # only trigger once self.rn.trigger.add_message(TCPENUM['TCP read'], 'Go!' * 600) # flush TCP def sync_mode(self, toggle=True): """Toggle whether to receive the run number from DExTer, or whether to increment the run number every time the expected number of images per sequence is received.""" remove_slot(self.rn.cam.AcquireEnd, self.rn.receive, toggle) remove_slot(self.rn.cam.AcquireEnd, self.rn.unsync_receive, not toggle) def wait_for_cam(self, timeout=10): """Wait (timeout / 10) ms, periodically checking whether the camera has started acquiring yet.""" for i in range(int(timeout)): if self.rn.cam.AF.GetStatus() == 'DRV_ACQUIRING': time.sleep(self.camera_pause) # wait for camera to initialise break time.sleep(1e-4) # poll camera status to check if acquiring def check_mr_queue(self): """Check whether it is appropriate to start the queued multiruns. This prevents multiple multiruns being sent to DExTer at the same time.""" num_mrs = len(self.rn.seq.mr.mr_queue) # number of multiruns queued if num_mrs: if not self.rn.multirun: self.rn.multirun = True self.rn.server.add_message( TCPENUM['TCP read'], # send the first multirun to DExTer 'start measure %s' % (self.rn.seq.mr.mr_param['measure'] + num_mrs - 1) + '\n' + '0' * 2000) else: QTimer.singleShot(10e3, self.check_mr_queue) # check again in 10s. def respond(self, msg=''): """Read the text from a TCP message and then execute the appropriate function.""" self.ts['msg start'] = time.time() self.ts['waiting'] = time.time() - self.ts['msg end'] if 'finished run' in msg: self.end_run(msg) elif 'start acquisition' in msg: self.status_label.setText('Running') if self.check_rois.isChecked( ): # start experiment when ROIs have atoms remove_slot(self.rn.check.rh.trigger, self.trigger_exp_start, True) self.rn.atomcheck_go() # start camera acuiring elif self.rn.cam.initialised: self.rn.cam.start() # start acquisition self.wait_for_cam( ) # wait for camera to initialise before running else: logger.warning('Run %s started without camera acquisition.' % (self.rn._n)) self.rn.server.priority_messages([ (TCPENUM['Save sequence'], 'save log file automatic name\n' + '0' * 2000), (TCPENUM['Run sequence'], 'single run ' + str(self.rn._n) + '\n' + '0' * 2000), (TCPENUM['TCP read'], 'finished run ' + str(self.rn._n) + '\n' + '0' * 2000) ]) # second message confirms end elif 'start measure' in msg: remove_slot(self.rn.seq.mr.progress, self.status_label.setText, True) if self.check_rois.isChecked( ): # start experiment when ROIs have atoms remove_slot(self.rn.check.rh.trigger, self.trigger_exp_start, True) self.rn.atomcheck_go() # start camera acquiring elif self.rn.cam.initialised: self.rn.cam.start() # start acquisition self.wait_for_cam() else: logger.warning('Run %s started without camera acquisition.' % (self.rn._n)) if 'restart' not in msg: self.rn.multirun_go( msg ) # might be resuming multirun instead of starting a new one elif 'multirun run' in msg: if self.check_rois.isChecked( ): # start experiment when ROIs have atoms remove_slot(self.rn.check.rh.trigger, self.trigger_exp_start, True) self.rn.atomcheck_go() # start camera in internal trigger mode self.rn.multirun_step(msg) self.rn._k = 0 # reset image per run count elif 'save and reset histogram' in msg: self.rn.multirun_save(msg) elif 'end multirun' in msg: remove_slot(self.rn.seq.mr.progress, self.status_label.setText, False) self.rn.multirun_end(msg) # self.rn.server.save_times() self.end_run(msg) elif 'STOPPED' in msg: self.status_label.setText(msg) self.ts['msg end'] = time.time() self.ts['blocking'] = time.time() - self.ts['msg start'] # self.print_times() def end_run(self, msg=''): """At the end of a single run or a multirun, stop the acquisition, check for unprocessed images, and check synchronisation. First, disconnect the server.textin signal from this slot to it only triggers once.""" self.action_button.setEnabled(True) # allow another command to be sent # reset atom checker trigger remove_slot(self.rn.check.rh.trigger, self.trigger_exp_start, False) if self.rn.trigger.connected: remove_slot(self.rn.trigger.textin, self.rn.trigger.clear_queue, True) self.rn.trigger.add_message(TCPENUM['TCP read'], 'end connection' * 150) try: unprocessed = self.rn.cam.EmptyBuffer() self.rn.cam.AF.AbortAcquisition() except Exception as e: logger.warning( 'Failed to abort camera acquisition at end of run.\n' + str(e)) # if unprocessed: # reply = QMessageBox.question(self, 'Unprocessed Images', # "Process the remaining %s images from the buffer?"%len(unprocessed), # QMessageBox.Yes | QMessageBox.No, QMessageBox.No) # if reply == QMessageBox.Yes: # for im in unprocessed: # # image dimensions: (# kscans, width pixels, height pixels) # self.rn.receive(im[0]) self.idle_state() def print_times(self, keys=['waiting', 'blocking']): """Print the timings between messages.""" print(*[key + ': %.4g,\t' % self.ts[key] for key in keys]) def save_state(self, file_name='./state'): """Save the file number and date and config file paths so that they can be loaded again when the program is next started.""" self.stats['File#'] = self.rn._n with open(file_name, 'w+') as f: for key, val in self.stats.items(): f.write(key + '=' + str(val) + '\n') def closeEvent(self, event): """Proper shut down procedure""" try: self.rn.cam.SafeShutdown() except Exception as e: logger.warning('camera safe shutdown failed.\n' + str(e)) self.rn.check.send_rois( ) # give ROIs from atom checker to image analysis self.rn.sw.save_settings('.\\imageanalysis\\default.config') for key, g in [['AnalysisGeometry', self.rn.sw.geometry()], ['SequencesGeometry', self.rn.seq.geometry()], ['MasterGeometry', self.geometry()]]: self.stats[key] = [g.x(), g.y(), g.width(), g.height()] for obj in self.rn.sw.mw + self.rn.sw.rw + [ self.rn.sw, self.rn.seq, self.rn.server, self.rn.trigger, self.rn.check, self.mon_win ]: obj.close() self.save_state() event.accept()