def __init__(self, parent_window: Window.Window, ui_widget: Declarative.UIDescription, ui_handler): super().__init__(parent_window.ui, app=parent_window.app, parent_window=parent_window, window_style="popup") from nion.ui import Declarative # avoid circular reference def request_close(): # this may be called in response to the user clicking a button to close. # make sure that the button is not destructed as a side effect of closing # the window by queueing the close. and it is not possible to use event loop # here because the event loop limitations: not able to run both parent and child # event loops simultaneously. parent_window.queue_task(self.request_close) # make and attach closer for the handler; put handler into container closer self.__closer = Declarative.Closer() if ui_handler and hasattr(ui_handler, "close"): ui_handler._closer = Declarative.Closer() self.__closer.push_closeable(ui_handler) finishes: typing.List[typing.Callable[[], None]] = list() self.widget = Declarative.construct(parent_window.ui, self, ui_widget, ui_handler, finishes) self.attach_widget(self.widget) for finish in finishes: finish() if ui_handler and hasattr(ui_handler, "init_handler"): ui_handler.init_handler() if ui_handler and hasattr(ui_handler, "init_popup"): ui_handler.init_popup(request_close) self._create_menus() self.__ui_handler = ui_handler
def show_file_param_dialog(self, file_ext: str = None, params_callback: callable = None): if self.__file_param_dialog_closed_event.is_set(): document_controller = self.__api.application.document_controllers[ 0]._document_controller ui_handler = OpenFileDialogUI().get_ui_handler( api_broker=PlugInManager.APIBroker(), event_loop=document_controller.event_loop, file_ext=file_ext, title='File') def dialog_closed(): self.__file_param_dialog_closed_event.set() ui_handler.on_closed = dialog_closed ui_handler.params_callback = params_callback finishes = list() dialog = Declarative.construct(document_controller.ui, document_controller, ui_handler.ui_view, ui_handler, finishes) for finish in finishes: finish() ui_handler._event_loop = document_controller.event_loop if callable(getattr(ui_handler, 'init_handler', None)): ui_handler.init_handler() dialog.show() ui_handler.request_close = dialog.request_close self.__file_param_dialog_closed_event.clear() self.__show_file_param_dialog_finished_event.set()
def calibrate_spectrum(api: API_1_0.API, window: API_1_0.DocumentWindow): class UIHandler: def __init__(self, data_item: API_1_0.DataItem, src_data_item: API_1_0.DataItem, offset_graphic: API_1_0.Graphic, second_graphic: API_1_0.Graphic, units='eV'): self.ev_converter = Converter.PhysicalValueToStringConverter(units) self.property_changed_event = Event.Event() self.__data_item = data_item self.__src_data_item = src_data_item self.__offset_graphic = offset_graphic self.__second_graphic = second_graphic self.__offset_energy = 0 self.__graphic_updating = False self.__second_point = data_item.display_xdata.dimensional_calibrations[0].convert_to_calibrated_value(second_graphic.position * len(data_item.display_xdata.data)) self.__offset_changed_listener = offset_graphic._graphic.property_changed_event.listen(self.__offset_graphic_changed) self.__second_changed_listener = second_graphic._graphic.property_changed_event.listen(self.__second_graphic_changed) def close(self): self.__offset_changed_listener.close() self.__offset_changed_listener = None self.__second_changed_listener.close() self.__second_changed_listener = None self.__data_item = None self.__src_data_item = None self.__second_graphic = None self.__offset_graphic = None @property def offset_energy(self): return self.__offset_energy @offset_energy.setter def offset_energy(self, offset_energy): self.__offset_energy = offset_energy self.property_changed_event.fire("offset_energy") self.__update_calibration(keep_scale=True) @property def second_point(self): return self.__second_point @second_point.setter def second_point(self, energy): self.__second_point = energy self.property_changed_event.fire("second_point") self.__update_calibration() @contextlib.contextmanager def __lock_graphic_updates(self): self.__graphic_updating = True try: yield self.__graphic_updating finally: self.__graphic_updating = False def __update_calibration(self, keep_scale=False): dimensional_calibrations = copy.deepcopy(self.__data_item.display_xdata.dimensional_calibrations) energy_calibration = dimensional_calibrations[0] if keep_scale: scale = energy_calibration.scale else: scale = (self.__second_point - self.__offset_energy) / ((self.__second_graphic.position - self.__offset_graphic.position) * len(self.__data_item.display_xdata.data)) offset = self.__offset_energy - self.__offset_graphic.position * len(self.__data_item.display_xdata.data) * scale energy_calibration.scale = scale energy_calibration.offset = offset dimensional_calibrations = list(self.__src_data_item.xdata.dimensional_calibrations) dimensional_calibrations[-1] = energy_calibration self.__src_data_item.set_dimensional_calibrations(dimensional_calibrations) offset_graphic_position = (self.offset_energy - offset) / scale / len(self.__data_item.display_xdata.data) second_graphic_position = (self.second_point - offset) / scale / len(self.__data_item.display_xdata.data) with self.__lock_graphic_updates(): self.__offset_graphic.position = min(max(0, offset_graphic_position), 0.99) self.__second_graphic.position = min(max(0, second_graphic_position), 0.99) def __offset_graphic_changed(self, property_name): if not self.__graphic_updating: self.__update_calibration(keep_scale=True) def __second_graphic_changed(self, property_name): if not self.__graphic_updating: self.__update_calibration() ui = Declarative.DeclarativeUI() row_1 = ui.create_row(ui.create_label(text="Move the graphics in the spectrum and/or change the numbers\nin the fields below to change the calibration.\n" "The offset graphic will be positioned on the ZLP if possible."), margin=5, spacing=5) row_2 = ui.create_row(ui.create_label(text="Offset Point energy"), ui.create_line_edit(text="@binding(offset_energy, converter=ev_converter)"), ui.create_stretch(), margin=5, spacing=5) row_3 = ui.create_row(ui.create_label(text="Scale Point energy"), ui.create_line_edit(text="@binding(second_point, converter=ev_converter)"), ui.create_stretch(), margin=5, spacing=5) column = ui.create_column(row_1, row_2, row_3, ui.create_stretch(), margin=5, spacing=5) data_item: API_1_0.DataItem = window.target_data_item class DummyHandler: ... if data_item is None or data_item.xdata is None or not data_item.display_xdata.is_data_1d: window.show_modeless_dialog(ui.create_modeless_dialog(ui.create_label(text=("This tool cannot be used for the selected type of data.\n" "To use it you have to select a data item containing 1-D data or a sequence of 1-D data.")), title="Calibrate Spectrum", margin=10), handler=DummyHandler) return if data_item._data_item.is_live: window.show_modeless_dialog(ui.create_modeless_dialog(ui.create_label(text=("This tool cannot be used on live data.\n" "To use it you have to select a data item containing 1-D data or a sequence of 1-D data.")), title="Calibrate Spectrum", margin=10), handler=DummyHandler) return # This is the data item we will update the calibrations on. If the selected data item is the result of a pick # computation we will update the source SI. Otherwise we just update the spectrum itself. src_data_item = data_item for computation in api.library._document_model.computations: if computation.processing_id in {"pick-point", "pick-mask-average", "pick-mask-sum"}: if computation.get_output("target") == data_item._data_item: input_ = computation.get_input("src") # If input_ is a "DataSource" we need to get the actual data item if hasattr(input_, "data_item"): input_ = input_.data_item src_data_item = api._new_api_object(input_) mx_pos = numpy.nan try: mx_pos = ZLP_Analysis.estimate_zlp_amplitude_position_width_fit_spline(data_item.display_xdata.data)[1] except TypeError: pass # fallback to com if fit failed if mx_pos is numpy.nan: mx_pos = ZLP_Analysis.estimate_zlp_amplitude_position_width_com(data_item.display_xdata.data)[1] # fallback to simple max if everything else failed if mx_pos is numpy.nan: mx_pos = float(numpy.argmax(data_item.display_xdata.data)) # We need to move the detected maximum by half a pixel because we want to center the calibration and the graphic # on the pixel center but the maximum is calculated for the left edge. mx_pos += 0.5 dimensional_calibrations = list(data_item.display_xdata.dimensional_calibrations) energy_calibration = dimensional_calibrations[0] energy_calibration.offset = -mx_pos * energy_calibration.scale dimensional_calibrations = list(src_data_item.xdata.dimensional_calibrations) dimensional_calibrations[-1] = energy_calibration src_data_item.set_dimensional_calibrations(dimensional_calibrations) offset_graphic = data_item.add_channel_region(mx_pos / len(data_item.display_xdata.data)) offset_graphic.label = "Offset Point" offset_graphic._graphic.color = "#CE00AC" second_graphic = data_item.add_channel_region((offset_graphic.position + 1.0) * 0.5) second_graphic.label = "Scale Point" second_graphic._graphic.color = "#CE00AC" handler = UIHandler(data_item, src_data_item, offset_graphic, second_graphic, units=energy_calibration.units) dialog = typing.cast(Dialog.ActionDialog, Declarative.construct(window._document_controller.ui, window._document_controller, ui.create_modeless_dialog(column, title="Calibrate Spectrum"), handler)) def wc(w): data_item.remove_region(offset_graphic) data_item.remove_region(second_graphic) getattr(handler, "configuration_dialog_close_listener").close() delattr(handler, "configuration_dialog_close_listener") # use set handler to pass type checking. setattr(handler, "configuration_dialog_close_listener", dialog._window_close_event.listen(wc)) dialog.show() # Return the dialog which is useful for testing return dialog