def __on_run_button_click(self): """ Creates a HapiWorker to run the Select query. """ selected_params = self.get_select_parameters() table_name = self.get_select_table_name() new_table_name = self.get_output_table_name() expression = self.get_select_expression() parsed_expression = DSL.parse_expression(expression) if parsed_expression == None and expression.strip() != '': err_log('Invalid select expression.') return if table_name == new_table_name: err_log( 'Cannot have select output table be the same as the input table' ) return self.run_button.setDisabled(True) args = HapiWorker.echo(ParameterNames=selected_params, TableName=table_name, DestinationTableName=new_table_name, Conditions=parsed_expression) worker = HapiWorker(WorkRequest.SELECT, args, self.__on_run_done) self.parent.workers.append(worker) worker.start()
def add_worker(self, graph_ty, work_object): id = self.cur_work_id self.cur_work_id += 1 worker = HapiWorker(GraphDisplayWidget.graph_ty_to_work_ty[graph_ty], work_object, lambda x: (self.plot(x), self.workers.pop(id))) self.workers[id] = worker worker.start()
def __on_fetch_clicked(self, _checked: bool): xscs = self.get_selected_xscs() if len(xscs) == 0: return args = HapiWorker.echo(xscs=xscs, molecule_name=self.molecule.currentText()) self.fetch_button.setDisabled(True) self.worker = HapiWorker(WorkRequest.DOWNLOAD_XSCS, args, self.__on_fetch_xsc_done) self.worker.start()
def __on_data_name_changed(self, new_table): """ Disables all graph buttons. (Inner method callback : enables graph buttons if necessary params to graph are supplied.) """ self.set_graph_buttons_enabled(False) self.same_window_checked = self.use_existing_window.isChecked() def callback(work_result): self.remove_worker_by_jid(work_result.job_id) result = work_result.result if result is None: return self.plot_name.setText(self.data_name.currentText()) if 'parameters' not in result: self.set_graph_buttons_enabled(True) return if not result['xsc']: for param in GraphingWidget.parameters_required_to_graph: if param not in result['parameters']: err_log('Table does not contain required parameters.') return self.numin.setValue(result['numin']) self.numax.setValue(result['numax']) self.set_graph_buttons_enabled(True) # Cross sections can only be graphed as cross sections if result['xsc'] is not None: self.set_xsc_mode(True) self.graph_type.clear() self.graph_type.addItems([GraphingWidget.XSC_STRING]) else: # Normal tables can be graphed as anything other than a cross section self.set_xsc_mode(False) self.graph_type.clear() graph_types = list(GraphingWidget.str_to_graph_ty.keys()) graph_types.remove(GraphingWidget.XSC_STRING) self.graph_type.addItems( list(GraphingWidget.str_to_graph_ty.keys())) self.xsc = result['xsc'] self.use_existing_window.setChecked(self.same_window_checked) self.broadener_input.set_table(new_table) worker = HapiWorker(WorkRequest.TABLE_META_DATA, {'table_name': new_table}, callback) self.workers.append(worker) worker.start()
def __on_select_table_name_selection_changed(self, new_selection): """ When the table that is being worked with changes, update the parameter list. """ self.run_button.setDisabled(True) if new_selection == '': return args = HapiWorker.echo(table_name=new_selection) worker = HapiWorker(WorkRequest.TABLE_META_DATA, args, self.__on_select_table_name_complete) worker.start() self.parent.workers.append(worker)
def graph_as(self, standard_params): path_length = self.get_path_length() instrumental_fn = self.get_instrumental_fn() AF_wing = self.get_instrumental_fn_wing() Resolution = self.get_instrumental_resolution() if standard_params['WavenumberStep'] is None: standard_params['WavenumberStep'] = Resolution / 2 elif standard_params['WavenumberStep'] <= Resolution: standard_params[ 'WavenumberStep'] = Resolution * 1.0001 # err_log('Wavenumber Step must be less # than Instrumental Resolution') # self.done_graphing() # return work = HapiWorker.echo(title=GraphingWidget.ABSORPTION_SPECTRUM_STRING, titlex="Wavenumber (cm$^{-1}$)", titley="Absorption Spectrum", path_length=path_length, instrumental_fn=instrumental_fn, Resolution=Resolution, AF_wing=AF_wing, **standard_params) if self.use_existing_window.isChecked(): selected_window = self.get_selected_window() if selected_window in GraphDisplayWidget.graph_windows: GraphDisplayWidget.graph_windows[selected_window].add_worker( GraphType.ABSORPTION_SPECTRUM, work) return GraphDisplayWidget(GraphType.ABSORPTION_SPECTRUM, work, self.backend.currentText()) self.update_existing_window_items()
def graph_bands(self, _standard_params): work = HapiWorker.echo(TableName=self.get_data_name(), title="Bands") if self.use_existing_window.isChecked(): selected_window = self.get_selected_window() if selected_window in GraphDisplayWidget.graph_windows: GraphDisplayWidget.graph_windows[selected_window].add_worker( GraphType.BANDS, work) return BandDisplayWidget(work, self.backend.currentText()) self.update_existing_window_items()
def graph_xsc(self, standard_params): work = HapiWorker.echo(title=GraphingWidget.XSC_STRING, titlex="Wavenumber (cm$^{-1}$)", titley="Intensity", **standard_params) if self.use_existing_window.isChecked(): selected_window = self.get_selected_window() if selected_window in GraphDisplayWidget.graph_windows: GraphDisplayWidget.graph_windows[selected_window].add_worker( GraphType.XSC, work) return _ = GraphDisplayWidget(GraphType.XSC, work, self.backend.currentText())
def __init__(self, graph_ty: GraphType, work_object: Dict, backend: str): """ Initializes the GUI and sends a work request for the graph to be plotted, and connect signals to the appropriate handler methods. :param ty the type of graph to be calculated. May be different for different types of graphs :param work_object has information about the graph that is to be made :param parent the parent QObject """ QMainWindow.__init__(self) self.n_plots = 0 self.plots = {} self.graph_ty = graph_ty self.graph_display_id = GraphDisplayWidget.graph_display_id() self.workers = { 0: HapiWorker(GraphDisplayWidget.graph_ty_to_work_ty[graph_ty], work_object, lambda x: (self.plot(x), self.workers.pop(0))) } GraphDisplayWidget.graph_windows[self.graph_display_id] = self self.workers[0].start() self.cur_work_id = 1 from widgets.graphing.graphing_widget import GraphingWidget self.done_signal.connect(lambda: GraphingWidget.GRAPHING_WIDGET_INSTANCE.done_graphing()) self.setWindowTitle(f"{work_object['title']} - {str(self.graph_display_id)}") self.setWindowIcon(program_icon()) self.as_json: QAction = None uic.loadUi('layouts/graph_display_window.ui', self) self.as_json.triggered.connect(self.__on_save_as_json_triggered) self.as_csv.triggered.connect(self.__on_save_as_csv_triggered) if backend == "matplotlib": self.backend = MplWidget(self) else: self.backend = VispyWidget(self) self.setCentralWidget(self.backend) self.series = [] self.setWindowTitle(f"Graphing window {self.graph_display_id}") self.show()
def __init__(self, work_object: Dict, backend: str): QMainWindow.__init__(self, None) self.graph_ty = GraphType.BANDS self.legend = BandLegend(self) self.graph_display_id = GraphDisplayWidget.graph_display_id() self.workers = { 0: HapiWorker(GraphDisplayWidget.graph_ty_to_work_ty[self.graph_ty], work_object, lambda x: (self.plot_bands(x), self.workers.pop(0))) } GraphDisplayWidget.graph_windows[self.graph_display_id] = self self.workers[0].start() self.cur_work_id = 1 from widgets.graphing.graphing_widget import GraphingWidget self.done_signal.connect( lambda: GraphingWidget.GRAPHING_WIDGET_INSTANCE.done_graphing()) self.setWindowTitle( f"{work_object['title']} - {str(self.graph_display_id)}") self.setWindowIcon(program_icon()) uic.loadUi('layouts/graph_display_window.ui', self) if backend == "matplotlib": self.backend = MplWidget(self) else: self.backend = VispyWidget(self) self.central_widget: QSplitter = QSplitter() self.central_widget.addWidget(self.backend) self.central_widget.addWidget(self.legend) self.setCentralWidget(self.central_widget) self.series = [] self.setWindowTitle(f"Graphing window {self.graph_display_id}") self.show()
def graph_abs_coef(self, standard_parameters): work = HapiWorker.echo( title=GraphingWidget.ABSORPTION_COEFFICIENT_STRING, titlex="Wavenumber (cm$^{-1}$)", titley='Absorption Coefficient ', **standard_parameters) if work['SourceTables'][0].endswith('.xsc'): work['titley'] = 'molecules / cm$^2$' work['title'] = 'Absorption Cross-Section' if self.use_existing_window.isChecked(): selected_window = self.get_selected_window() if selected_window in GraphDisplayWidget.graph_windows: GraphDisplayWidget.graph_windows[selected_window].add_worker( GraphType.ABSORPTION_COEFFICIENT, work) return # No need to store a reference to it, since the GraphDisplayWidget will add itself to a list _ = GraphDisplayWidget(GraphType.ABSORPTION_COEFFICIENT, work, self.backend.currentText()) self.update_existing_window_items()
def get_standard_parameters(self): data_name = self.get_data_name() backend = self.backend.currentText() if data_name.endswith(".xsc"): Components = [] SourceTables = [data_name] Environment = {'p': self.xsc.pressure, 'T': self.xsc.temp} WavenumberRange = (self.xsc.numin, self.xsc.numax) WavenumberStep = self.xsc.step Diluent = {'air': 0.0, 'self': 1.0} # TODO: Verify that these are the proper values. WavenumberWing = 0.0 WavenumberWingHW = 0.0 else: hmd = HapiMetaData(data_name) Components = hmd.iso_tuples SourceTables = [data_name] Environment = {'p': self.get_pressure(), 'T': self.get_temp()} Diluent = self.get_diluent() WavenumberRange = self.get_wn_range() WavenumberStep = self.get_wn_step() WavenumberWing = self.get_wn_wing() WavenumberWingHW = self.get_wn_wing_hw() name = self.plot_name.text() graph_fn = self.get_line_profile() return HapiWorker.echo(graph_fn=graph_fn, Components=Components, SourceTables=SourceTables, Environment=Environment, Diluent=Diluent, HITRAN_units=False, WavenumberRange=WavenumberRange, WavenumberStep=WavenumberStep, WavenumberWing=WavenumberWing, WavenumberWingHW=WavenumberWingHW, backend=backend, name=name)
def graph_ts(self, standard_params): path_length = self.get_path_length() instrumental_fn = self.get_instrumental_fn() AF_wing = self.get_instrumental_fn_wing() Resolution = self.get_instrumental_resolution() if standard_params['WavenumberStep'] == None: standard_params['WavenumberStep'] = Resolution / 2 elif standard_params['WavenumberStep'] <= Resolution: err_log( 'Wavenumber Step must be less than Instrumental Resolution') self.data_name_error.setText( '<span style="color:#aa0000;">' + 'Wavenumber Step must be less than the ' 'Instrumental Resolution' + '</span>') self.done_graphing() return work = HapiWorker.echo( title=GraphingWidget.TRANSMITTANCE_SPECTRUM_STRING, titlex="Wavenumber (cm$^{-1}$)", titley="Transmittance", path_length=path_length, instrumental_fn=instrumental_fn, Resolution=Resolution, AF_wing=AF_wing, **standard_params) if self.use_existing_window.isChecked(): selected_window = self.get_selected_window() if selected_window in GraphDisplayWidget.graph_windows: GraphDisplayWidget.graph_windows[selected_window].add_worker( GraphType.TRANSMITTANCE_SPECTRUM, work) return GraphDisplayWidget(GraphType.TRANSMITTANCE_SPECTRUM, work, self.backend.currentText()) self.update_existing_window_items()
class CrossSectionFetchWidget(QWidget): CROSS_SECTION_FETCH_WIDGET_INSTANCE = None @staticmethod def gen_toggle_function(other_widgets: List[QWidget]): return lambda checked: list( map(lambda widget: widget.setDisabled(not checked), other_widgets)) def __init__(self, parent=None): QWidget.__init__(self, parent) if CrossSectionFetchWidget.CROSS_SECTION_FETCH_WIDGET_INSTANCE is not None: raise Exception( "No more than one instance of CrossSectionFetchWidget" " should be created") CrossSectionFetchWidget.CROSS_SECTION_FETCH_WIDGET_INSTANCE = self self.all_molecules = MoleculeMeta.all_names() self.parent = parent self.wn_check: QCheckBox = None self.numin: QDoubleSpinBox = None self.numax: QDoubleSpinBox = None self.pressure_check: QCheckBox = None self.pressure_min: QDoubleSpinBox = None self.pressure_max: QDoubleSpinBox = None self.temp_check: QCheckBox = None self.temp_min: QDoubleSpinBox = None self.temp_max: QDoubleSpinBox = None self.molecule: QComboBox = None self.cross_section_list: QListWidget = None self.fetch_button: QPushButton = None self.apply_filters: QPushButton = None self.cross_section_meta: CrossSectionMeta = None self.fetching = False uic.loadUi('layouts/cross_section_widget.ui', self) self.pressure_check.toggled.connect( self.gen_toggle_function([self.pressure_max, self.pressure_min])) self.temp_check.toggled.connect( self.gen_toggle_function([self.temp_max, self.temp_min])) self.wn_check.toggled.connect( self.gen_toggle_function([self.numax, self.numin])) self.pressure_check.setChecked(True) self.temp_check.setChecked(True) self.wn_check.setChecked(True) self.temp_check.toggle() self.wn_check.toggle() self.pressure_check.toggle() self.fetch_button.clicked.connect(self.__on_fetch_clicked) self.apply_filters.clicked.connect(self.__on_apply_filters_clicked) self.molecule.addItems( CrossSectionMeta.all_names_sorted_by_hitran_id()) self.molecule.setEditable(True) self.completer: QCompleter = QCompleter(CrossSectionMeta.all_aliases(), self) self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.molecule.setCompleter(self.completer) self.molecule.currentTextChanged.connect( self.__on_molecule_selection_changed) self.__on_molecule_selection_changed(self.molecule.currentText()) def get_selected_xscs(self) -> List[str]: xscs = [] for i in range(self.cross_section_list.count()): item = self.cross_section_list.item(i) if item.checkState() == QtCore.Qt.Checked: xscs.append(str(item.text())) return xscs def __on_apply_filters_clicked(self, _checked: bool): if self.pressure_check.isChecked(): pressure = [self.pressure_min.value(), self.pressure_max.value()] pressure.sort() else: pressure = None if self.wn_check.isChecked(): wn = [self.numin.value(), self.numax.value()] wn.sort() else: wn = None if self.temp_check.isChecked(): temp = [self.temp_min.value(), self.temp_max.value()] temp.sort() else: temp = None xsc_filter = CrossSectionFilter(self.get_selected_molecule_id(), wn, pressure, temp) self.set_cross_section_list_items(xsc_filter.get_cross_sections()) def __on_molecule_selection_changed(self, _current_text: str): mid = self.get_selected_molecule_id() if mid is None: return self.cross_section_meta = CrossSectionMeta(mid) items = self.cross_section_meta.get_all_filenames() self.set_cross_section_list_items(items) if self.fetching: return if len(items) == 0: self.fetch_button.setDisabled(True) else: self.fetch_button.setEnabled(True) def __on_fetch_clicked(self, _checked: bool): xscs = self.get_selected_xscs() if len(xscs) == 0: return args = HapiWorker.echo(xscs=xscs, molecule_name=self.molecule.currentText()) self.fetch_button.setDisabled(True) self.worker = HapiWorker(WorkRequest.DOWNLOAD_XSCS, args, self.__on_fetch_xsc_done) self.worker.start() def __on_fetch_xsc_done(self, res): _result = res.result if _result is None: err_log("Failed to fetch cross sections...") self.parent.populate_table_lists() self.fetching = False self.fetch_button.setEnabled(True) def get_selected_molecule_id(self) -> Optional[int]: selected_molecule_name = self.molecule.currentText() mid = MoleculeMeta(selected_molecule_name) if mid.populated: return mid.id else: return None def set_cross_section_list_items(self, xscs: List[str]): list( map(lambda _: self.cross_section_list.takeItem(0), range(self.cross_section_list.count()))) for xsc in xscs: item = QtWidgets.QListWidgetItem(xsc) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) item.setCheckState(QtCore.Qt.Unchecked) self.cross_section_list.addItem(item)
def run(): """ The main method starts the GUI after asking for an api key if necessary. """ if not check_internet_connection_and_obtain_api_key(): return 0 # Create the data folder if it doesn't exist. if not os.path.exists(Config.data_folder): os.makedirs(Config.data_folder) if Config.online: if len(sys.argv) > 1: if sys.argv[1] in {'--test', '-t'}: import test test.run_tests() return 0 elif sys.argv[1] in ('--download-molecule-images', '-dmi'): import res_gen.image_downloader as id id.download_images() return 0 elif sys.argv[1] in ("-gba", "--generate-broadener-availability"): import res_gen.generate_broadener_availability as gba gba.generate_availability() return 0 if Config.high_dpi: # Enable High DPI display with PyQt5 QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # Fix for mac-based systems... os.environ['no_proxy'] = '*' ## # The following blocks of code verify the hapi API key is in place, and it # is valid. If it is not valid or in place the user will we prompted for # one. This code also checks for a working internet connection, as hapi # needs one to download data. In the future, if there is no internet # connection the GUI should simply disable the features that require it, # and there could be a periodic check for internet connection that will # re-enable them. from metadata.molecule_meta import MoleculeMeta WorkRequest.start_work_process() # Hapi is now started automatically in the work process # start = HapiWorker(WorkRequest.START_HAPI, {}) # start.start() # When a start_hapi request is sent, it starts automatically. _ = MoleculeMeta(0) from metadata.xsc_meta import CrossSectionMeta # If the cache is expired, download a list of the cross section meta file. # This also populates the CrossSectionMeta.molecule_metas field. _ = CrossSectionMeta(0) app = QtWidgets.QApplication(sys.argv) app.setStyle(QStyleFactory.create("Fusion")) window = MainWindow() window.gui.adjustSize() TextReceiver.init(window) _qt_result = app.exec_() TextReceiver.redirect_close() close = HapiWorker(WorkRequest.END_WORK_PROCESS, {}, callback=None) close.safe_exit() WorkRequest.WORKER.process.join() HapiThread.kill_all() return 0