class Histogram(ow_automatic_element.AutomaticElement): name = "Histogram" description = "Display Data Tools: Histogram" icon = "icons/histogram.png" maintainer = "Luca Rebuffi" maintainer_email = "lrebuffi(@at@)anl.gov" priority = 2 category = "Display Data Tools" keywords = ["data", "file", "load", "read"] inputs = [("Input Beam", ShadowBeam, "setBeam")] IMAGE_WIDTH = 878 IMAGE_HEIGHT = 635 want_main_area = 1 plot_canvas = None input_beam = None image_plane = Setting(0) image_plane_new_position = Setting(10.0) image_plane_rel_abs_position = Setting(0) x_column_index = Setting(10) x_range = Setting(0) x_range_min = Setting(0.0) x_range_max = Setting(0.0) weight_column_index = Setting(23) rays = Setting(1) number_of_bins = Setting(100) title = Setting("Energy") autosave = Setting(0) autosave_file_name = Setting("autosave_histogram_plot.hdf5") keep_result = Setting(0) autosave_partial_results = Setting(0) is_conversion_active = Setting(1) cumulated_ticket = None plotted_ticket = None autosave_file = None autosave_prog_id = 0 def __init__(self): super().__init__() button_box = oasysgui.widgetBox(self.controlArea, "", addSpace=False, orientation="horizontal") gui.button(button_box, self, "Refresh", callback=self.plot_results, height=45) gui.button(button_box, self, "Save Current Plot", callback=self.save_results, height=45) gui.separator(self.controlArea, 10) self.tabs_setting = oasysgui.tabWidget(self.controlArea) self.tabs_setting.setFixedWidth(self.CONTROL_AREA_WIDTH - 5) # graph tab tab_set = oasysgui.createTabPage(self.tabs_setting, "Plot Settings") tab_gen = oasysgui.createTabPage(self.tabs_setting, "Histogram Settings") screen_box = oasysgui.widgetBox(tab_set, "Screen Position Settings", addSpace=True, orientation="vertical", height=120) self.image_plane_combo = gui.comboBox( screen_box, self, "image_plane", label="Position of the Image", items=["On Image Plane", "Retraced"], labelWidth=260, callback=self.set_ImagePlane, sendSelectedValue=False, orientation="horizontal") self.image_plane_box = oasysgui.widgetBox(screen_box, "", addSpace=False, orientation="vertical", height=50) self.image_plane_box_empty = oasysgui.widgetBox(screen_box, "", addSpace=False, orientation="vertical", height=50) oasysgui.lineEdit(self.image_plane_box, self, "image_plane_new_position", "Image Plane new Position", labelWidth=220, valueType=float, orientation="horizontal") gui.comboBox(self.image_plane_box, self, "image_plane_rel_abs_position", label="Position Type", labelWidth=250, items=["Absolute", "Relative"], sendSelectedValue=False, orientation="horizontal") self.set_ImagePlane() general_box = oasysgui.widgetBox(tab_set, "General Settings", addSpace=True, orientation="vertical", height=250) self.x_column = gui.comboBox( general_box, self, "x_column_index", label="Column", labelWidth=70, items=[ "1: X", "2: Y", "3: Z", "4: X'", "5: Y'", "6: Z'", "7: E\u03c3 X", "8: E\u03c3 Y", "9: E\u03c3 Z", "10: Ray Flag", "11: Energy", "12: Ray Index", "13: Optical Path", "14: Phase \u03c3", "15: Phase \u03c0", "16: E\u03c0 X", "17: E\u03c0 Y", "18: E\u03c0 Z", "19: Wavelength", "20: R = sqrt(X\u00b2 + Y\u00b2 + Z\u00b2)", "21: Theta (angle from Y axis)", "22: Magnitude = |E\u03c3| + |E\u03c0|", "23: Total Intensity = |E\u03c3|\u00b2 + |E\u03c0|\u00b2", "24: \u03a3 Intensity = |E\u03c3|\u00b2", "25: \u03a0 Intensity = |E\u03c0|\u00b2", "26: |K|", "27: K X", "28: K Y", "29: K Z", "30: S0-stokes = |E\u03c0|\u00b2 + |E\u03c3|\u00b2", "31: S1-stokes = |E\u03c0|\u00b2 - |E\u03c3|\u00b2", "32: S2-stokes = 2|E\u03c3||E\u03c0|cos(Phase \u03c3-Phase \u03c0)", "33: S3-stokes = 2|E\u03c3||E\u03c0|sin(Phase \u03c3-Phase \u03c0)", "34: Power = Intensity * Energy", ], sendSelectedValue=False, orientation="horizontal") gui.comboBox(general_box, self, "x_range", label="X Range", labelWidth=250, items=["<Default>", "Set.."], callback=self.set_XRange, sendSelectedValue=False, orientation="horizontal") self.xrange_box = oasysgui.widgetBox(general_box, "", addSpace=True, orientation="vertical", height=100) self.xrange_box_empty = oasysgui.widgetBox(general_box, "", addSpace=True, orientation="vertical", height=100) oasysgui.lineEdit(self.xrange_box, self, "x_range_min", "X min", labelWidth=220, valueType=float, orientation="horizontal") oasysgui.lineEdit(self.xrange_box, self, "x_range_max", "X max", labelWidth=220, valueType=float, orientation="horizontal") self.set_XRange() self.weight_column = gui.comboBox( general_box, self, "weight_column_index", label="Weight", labelWidth=70, items=[ "0: No Weight", "1: X", "2: Y", "3: Z", "4: X'", "5: Y'", "6: Z'", "7: E\u03c3 X", "8: E\u03c3 Y", "9: E\u03c3 Z", "10: Ray Flag", "11: Energy", "12: Ray Index", "13: Optical Path", "14: Phase \u03c3", "15: Phase \u03c0", "16: E\u03c0 X", "17: E\u03c0 Y", "18: E\u03c0 Z", "19: Wavelength", "20: R = sqrt(X\u00b2 + Y\u00b2 + Z\u00b2)", "21: Theta (angle from Y axis)", "22: Magnitude = |E\u03c3| + |E\u03c0|", "23: Total Intensity = |E\u03c3|\u00b2 + |E\u03c0|\u00b2", "24: \u03a3 Intensity = |E\u03c3|\u00b2", "25: \u03a0 Intensity = |E\u03c0|\u00b2", "26: |K|", "27: K X", "28: K Y", "29: K Z", "30: S0-stokes = |E\u03c0|\u00b2 + |E\u03c3|\u00b2", "31: S1-stokes = |E\u03c0|\u00b2 - |E\u03c3|\u00b2", "32: S2-stokes = 2|E\u03c3||E\u03c0|cos(Phase \u03c3-Phase \u03c0)", "33: S3-stokes = 2|E\u03c3||E\u03c0|sin(Phase \u03c3-Phase \u03c0)", "34: Power = Intensity * Energy", ], sendSelectedValue=False, orientation="horizontal") gui.comboBox(general_box, self, "rays", label="Rays", labelWidth=250, items=["All rays", "Good Only", "Lost Only"], sendSelectedValue=False, orientation="horizontal") autosave_box = oasysgui.widgetBox(tab_gen, "Autosave", addSpace=True, orientation="vertical", height=85) gui.comboBox(autosave_box, self, "autosave", label="Save automatically plot into file", labelWidth=250, items=["No", "Yes"], sendSelectedValue=False, orientation="horizontal", callback=self.set_autosave) self.autosave_box_1 = oasysgui.widgetBox(autosave_box, "", addSpace=False, orientation="horizontal", height=25) self.autosave_box_2 = oasysgui.widgetBox(autosave_box, "", addSpace=False, orientation="horizontal", height=25) self.le_autosave_file_name = oasysgui.lineEdit( self.autosave_box_1, self, "autosave_file_name", "File Name", labelWidth=100, valueType=str, orientation="horizontal") gui.button(self.autosave_box_1, self, "...", callback=self.selectAutosaveFile) incremental_box = oasysgui.widgetBox(tab_gen, "Incremental Result", addSpace=True, orientation="vertical", height=120) gui.comboBox(incremental_box, self, "keep_result", label="Keep Result", labelWidth=250, items=["No", "Yes"], sendSelectedValue=False, orientation="horizontal", callback=self.set_autosave) self.cb_autosave_partial_results = gui.comboBox( incremental_box, self, "autosave_partial_results", label="Save partial plots into file", labelWidth=250, items=["No", "Yes"], sendSelectedValue=False, orientation="horizontal") gui.button(incremental_box, self, "Clear", callback=self.clearResults) histograms_box = oasysgui.widgetBox(tab_gen, "Histograms settings", addSpace=True, orientation="vertical", height=90) oasysgui.lineEdit(histograms_box, self, "number_of_bins", "Number of Bins", labelWidth=250, valueType=int, orientation="horizontal") gui.comboBox(histograms_box, self, "is_conversion_active", label="Is U.M. conversion active", labelWidth=250, items=["No", "Yes"], sendSelectedValue=False, orientation="horizontal") self.set_autosave() self.main_tabs = oasysgui.tabWidget(self.mainArea) plot_tab = oasysgui.createTabPage(self.main_tabs, "Plots") out_tab = oasysgui.createTabPage(self.main_tabs, "Output") self.image_box = gui.widgetBox(plot_tab, "Plot Result", addSpace=True, orientation="vertical") self.image_box.setFixedHeight(self.IMAGE_HEIGHT) self.image_box.setFixedWidth(self.IMAGE_WIDTH) self.shadow_output = oasysgui.textArea(height=580, width=800) out_box = gui.widgetBox(out_tab, "System Output", addSpace=True, orientation="horizontal") out_box.layout().addWidget(self.shadow_output) def clearResults(self, interactive=True): if not interactive: proceed = True else: proceed = ConfirmDialog.confirmed(parent=self) if proceed: self.input_beam = ShadowBeam() self.cumulated_ticket = None self.plotted_ticket = None self.autosave_prog_id = 0 if not self.autosave_file is None: self.autosave_file.close() self.autosave_file = None self.plot_canvas.clear() def set_XRange(self): self.xrange_box.setVisible(self.x_range == 1) self.xrange_box_empty.setVisible(self.x_range == 0) def set_ImagePlane(self): self.image_plane_box.setVisible(self.image_plane == 1) self.image_plane_box_empty.setVisible(self.image_plane == 0) def set_autosave(self): self.autosave_box_1.setVisible(self.autosave == 1) self.autosave_box_2.setVisible(self.autosave == 0) self.cb_autosave_partial_results.setEnabled(self.autosave == 1 and self.keep_result == 1) def selectAutosaveFile(self): self.le_autosave_file_name.setText( oasysgui.selectFileFromDialog( self, self.autosave_file_name, "Select File", file_extension_filter="HDF5 Files (*.hdf5 *.h5 *.hdf)")) def replace_fig(self, beam, var, xrange, title, xtitle, ytitle, xum, flux): if self.plot_canvas is None: self.plot_canvas = ShadowPlot.DetailedHistoWidget( y_scale_factor=1.14) self.image_box.layout().addWidget(self.plot_canvas) try: if self.autosave == 1: if self.autosave_file is None: self.autosave_file = ShadowPlot.HistogramHdf5File( congruence.checkDir(self.autosave_file_name)) elif self.autosave_file.filename != congruence.checkFileName( self.autosave_file_name): self.autosave_file.close() self.autosave_file = ShadowPlot.HistogramHdf5File( congruence.checkDir(self.autosave_file_name)) if self.keep_result == 1: self.cumulated_ticket, last_ticket = self.plot_canvas.plot_histo( beam, var, self.rays, xrange, self.weight_column_index, title, xtitle, ytitle, nbins=self.number_of_bins, xum=xum, conv=self.workspace_units_to_cm, ticket_to_add=self.cumulated_ticket, flux=flux) self.plotted_ticket = self.cumulated_ticket if self.autosave == 1: self.autosave_prog_id += 1 self.autosave_file.write_coordinates(self.cumulated_ticket) dataset_name = self.weight_column.itemText( self.weight_column_index) self.autosave_file.add_histogram(self.cumulated_ticket, dataset_name=dataset_name) if self.autosave_partial_results == 1: if last_ticket is None: self.autosave_file.add_histogram( self.cumulated_ticket, plot_name="Histogram #" + str(self.autosave_prog_id), dataset_name=dataset_name) else: self.autosave_file.add_histogram( last_ticket, plot_name="Histogram #" + str(self.autosave_prog_id), dataset_name=dataset_name) self.autosave_file.flush() else: ticket, _ = self.plot_canvas.plot_histo( beam, var, self.rays, xrange, self.weight_column_index, title, xtitle, ytitle, nbins=self.number_of_bins, xum=xum, conv=self.workspace_units_to_cm, flux=flux) self.cumulated_ticket = None self.plotted_ticket = ticket if self.autosave == 1: self.autosave_prog_id += 1 self.autosave_file.write_coordinates(ticket) self.autosave_file.add_histogram( ticket, dataset_name=self.weight_column.itemText( self.weight_column_index)) self.autosave_file.flush() except Exception as e: if not self.IS_DEVELOP: raise Exception( "Data not plottable: No good rays or bad content") else: raise e def plot_histo(self, var_x, title, xtitle, ytitle, xum): beam_to_plot = self.input_beam._beam flux = self.input_beam.get_flux(nolost=self.rays) if self.image_plane == 1: new_shadow_beam = self.input_beam.duplicate(history=False) dist = 0.0 if self.image_plane_rel_abs_position == 1: # relative dist = self.image_plane_new_position else: # absolute if self.input_beam.historySize() == 0: historyItem = None else: historyItem = self.input_beam.getOEHistory( oe_number=self.input_beam._oe_number) if historyItem is None: image_plane = 0.0 elif self.input_beam._oe_number == 0: image_plane = 0.0 else: if isinstance(historyItem._shadow_oe_end._oe, CompoundOE): image_plane = historyItem._shadow_oe_end._oe.list[ -1].T_IMAGE else: image_plane = historyItem._shadow_oe_end._oe.T_IMAGE dist = self.image_plane_new_position - image_plane self.retrace_beam(new_shadow_beam, dist) beam_to_plot = new_shadow_beam._beam xrange = self.get_range(beam_to_plot, var_x) self.replace_fig(beam_to_plot, var_x, xrange, title, xtitle, ytitle, xum, flux) def get_range(self, beam_to_plot, var_x): if self.x_range == 0: x_max = 0 x_min = 0 x, good_only = beam_to_plot.getshcol((var_x, 10)) x_to_plot = copy.deepcopy(x) go = numpy.where(good_only == 1) lo = numpy.where(good_only != 1) if self.rays == 0: x_max = numpy.array(x_to_plot[0:], float).max() x_min = numpy.array(x_to_plot[0:], float).min() elif self.rays == 1: x_max = numpy.array(x_to_plot[go], float).max() x_min = numpy.array(x_to_plot[go], float).min() elif self.rays == 2: x_max = numpy.array(x_to_plot[lo], float).max() x_min = numpy.array(x_to_plot[lo], float).min() xrange = [x_min, x_max] else: congruence.checkLessThan(self.x_range_min, self.x_range_max, "X range min", "X range max") factor1 = ShadowPlot.get_factor(var_x, self.workspace_units_to_cm) xrange = [self.x_range_min / factor1, self.x_range_max / factor1] return xrange def save_results(self): if not self.plotted_ticket is None: try: file_name, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Save Current Plot", filter="HDF5 Files (*.hdf5 *.h5 *.hdf)") if not file_name is None and not file_name.strip() == "": if not (file_name.endswith("hd5") or file_name.endswith("hdf5") or file_name.endswith("hdf")): file_name += ".hdf5" save_file = ShadowPlot.HistogramHdf5File( congruence.checkDir(file_name)) save_file.write_coordinates(self.plotted_ticket) dataset_name = self.weight_column.itemText( self.weight_column_index) save_file.add_histogram(self.plotted_ticket, dataset_name=dataset_name) save_file.close() except Exception as exception: QtWidgets.QMessageBox.critical(self, "Error", str(exception), QtWidgets.QMessageBox.Ok) if self.IS_DEVELOP: raise exception def plot_results(self): try: plotted = False sys.stdout = EmittingStream(textWritten=self.writeStdOut) if self.trace_shadow: grabber = TTYGrabber() grabber.start() if ShadowCongruence.checkEmptyBeam(self.input_beam): ShadowPlot.set_conversion_active(self.getConversionActive()) self.number_of_bins = congruence.checkPositiveNumber( self.number_of_bins, "Number of Bins") x, auto_title, xum = self.get_titles() self.plot_histo(x, title=self.title, xtitle=auto_title, ytitle="Number of Rays", xum=xum) plotted = True if self.trace_shadow: grabber.stop() for row in grabber.ttyData: self.writeStdOut(row) time.sleep( 0.5 ) # prevents a misterious dead lock in the Orange cycle when refreshing the histogram return plotted except Exception as exception: QtWidgets.QMessageBox.critical(self, "Error", str(exception), QtWidgets.QMessageBox.Ok) if self.IS_DEVELOP: raise exception def get_titles(self): auto_title = self.x_column.currentText().split(":", 2)[1] xum = auto_title + " " self.title = auto_title x = self.x_column_index + 1 if x == 1 or x == 2 or x == 3: if self.getConversionActive(): xum = xum + "[" + u"\u03BC" + "m]" auto_title = auto_title + " [$\mu$m]" else: xum = xum + " [" + self.workspace_units_label + "]" auto_title = auto_title + " [" + self.workspace_units_label + "]" elif x == 4 or x == 5 or x == 6: if self.getConversionActive(): xum = xum + "[" + u"\u03BC" + "rad]" auto_title = auto_title + " [$\mu$rad]" else: xum = xum + " [rad]" auto_title = auto_title + " [rad]" elif x == 11: xum = xum + "[eV]" auto_title = auto_title + " [eV]" elif x == 13: xum = xum + " [" + self.workspace_units_label + "]" auto_title = auto_title + " [" + self.workspace_units_label + "]" elif x == 14: xum = xum + "[rad]" auto_title = auto_title + " [rad]" elif x == 15: xum = xum + "[rad]" auto_title = auto_title + " [rad]" elif x == 19: xum = xum + "[Å]" auto_title = auto_title + " [Å]" elif x == 20: xum = xum + " [" + self.workspace_units_label + "]" auto_title = auto_title + " [" + self.workspace_units_label + "]" elif x == 21: xum = xum + "[rad]" auto_title = auto_title + " [rad]" elif x >= 25 and x <= 28: xum = xum + "[Å-1]" auto_title = auto_title + " [Å-1]" return x, auto_title, xum def setBeam(self, beam): if ShadowCongruence.checkEmptyBeam(beam): if ShadowCongruence.checkGoodBeam(beam): self.input_beam = beam if self.is_automatic_run: self.plot_results() else: QtWidgets.QMessageBox.critical( self, "Error", "Data not displayable: No good rays or bad content", QtWidgets.QMessageBox.Ok) def writeStdOut(self, text): cursor = self.shadow_output.textCursor() cursor.movePosition(QtGui.QTextCursor.End) cursor.insertText(text) self.shadow_output.setTextCursor(cursor) self.shadow_output.ensureCursorVisible() def retrace_beam(self, new_shadow_beam, dist): new_shadow_beam._beam.retrace(dist) def getConversionActive(self): return self.is_conversion_active == 1