diff --git a/crys3d/hklviewer/HKLviewer.py b/crys3d/hklviewer/HKLviewer.py index d366a85b32..0ca0323af2 100644 --- a/crys3d/hklviewer/HKLviewer.py +++ b/crys3d/hklviewer/HKLviewer.py @@ -35,7 +35,7 @@ except Exception as e: # if invoked by a generic python that doesn't know cctbx modules from . import hklviewer_gui from .helpers import ( MillerArrayTableView, MillerArrayTableForm, MyhorizontalHeader, - MillerArrayTableModel, MPLColourSchemes, MillerTableColumnHeaderDialog ) + MillerArrayTableModel, MPLColourSchemes, MillerTableColumnHeaderDialog, MyQDoubleSpinBox ) class MakeNewDataForm(QDialog): @@ -78,14 +78,18 @@ def __init__(self, parent=None): self.aboutlabel.setWordWrap(True) self.aboutlabel.setTextInteractionFlags(Qt.TextBrowserInteraction); self.aboutlabel.setOpenExternalLinks(True); + self.aboutlabel.setMaximumSize(QSize(16777215, 16777215)) self.writeAboutstr("") self.copyrightstxt = QTextEdit() self.copyrightstxt.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.copyrightstxt.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.copyrightstxt.setReadOnly(True) self.copyrightstxt.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) - self.copyrightstxt.setMinimumSize(QSize(400, 100)) self.copyrightstxt.setMaximumSize(QSize(16777215, 16777215)) + sp = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.copyrightstxt.setMinimumSize(QSize(350, 150)) + self.copyrightstxt.setSizePolicy(sp) + self.aboutlabel.setSizePolicy(sp) self.OKbtn = QPushButton() self.OKbtn.setText("OK") self.OKbtn.clicked.connect(self.onOK) @@ -93,7 +97,8 @@ def __init__(self, parent=None): mainLayout.addWidget(self.copyrightstxt, 1, 0, 1, 3) mainLayout.addWidget(self.OKbtn, 2, 1, 1, 1) self.setLayout(mainLayout) - self.setMinimumSize(QSize(350, 200)) + self.setMinimumSize(QSize(400, 300)) + self.setSizePolicy(sp) self.setFixedSize( self.sizeHint() ) def writeAboutstr(self, versionstr): aboutstr = """

@@ -318,13 +323,17 @@ def __init__(self, thisapp, isembedded=False): #, cctbxpython=None): self.mousesensitxtbox = QLineEdit('') self.mousesensitxtbox.setReadOnly(True) - self.fontspinBox = QDoubleSpinBox() + self.fontspinBox = MyQDoubleSpinBox() self.fontspinBox.setSingleStep(1) self.fontspinBox.setRange(4, 50) self.font = QFont() self.font.setFamily(self.font.defaultFamily()) self.fontspinBox.setValue(self.font.pointSize()) - self.fontspinBox.valueChanged.connect(self.onFontsizeChanged) + self.fontspinBox.editingFinished.connect(self.onFontsizeChanged) + # valueChanged signal is buggy and gets triggered twice when onFontsizeChanged takes too long + # This may cause font step=2 rather than step=1 + # Workaround is to use onMouseRelease invoked by MyQDoubleSpinBox.mouseReleaseEvent + self.fontspinBox.onMouseRelease = self.onFontsizeChanged self.Fontsize_labeltxt = QLabel() self.Fontsize_labeltxt.setText("Font size:") @@ -678,7 +687,7 @@ def onSaveReflectionFile(self): def SettingsDialog(self): self.settingsform.show() # don't know why valueChanged.connect() method only takes effect from here on - self.fontspinBox.valueChanged.connect(self.onFontsizeChanged) + #self.fontspinBox.valueChanged.connect(self.onFontsizeChanged) self.settingsform.activateWindow() @@ -1297,7 +1306,8 @@ def onShowTooltips(self, val): self.ttip_click_invoke = "hover" - def onFontsizeChanged(self, val): + def onFontsizeChanged(self): + val = self.fontspinBox.value() font = self.app.font() font.setPointSize(val); self.fontsize = val @@ -1311,6 +1321,7 @@ def onFontsizeChanged(self, val): self.textAlerts.setFont(font) self.SpaceGrpUCellText.setFont(font) + def onBrowserFontsizeChanged(self, val): self.browserfontsize = val self.send_message("NGL.fontsize = %d" %val) @@ -2436,8 +2447,9 @@ def UsePersistedQsettings(self): self.onTextbufferSizeChanged(int(self.textinfosize)) self.bufsizespinBox.setValue(int(self.textinfosize)) if self.fontsize is not None: - self.onFontsizeChanged(int(self.fontsize)) + #self.onFontsizeChanged(int(self.fontsize)) self.fontspinBox.setValue(int(self.fontsize)) + self.onFontsizeChanged() if self.browserfontsize is not None: self.onBrowserFontsizeChanged(int(self.browserfontsize)) self.browserfontspinBox.setValue(int(self.browserfontsize)) diff --git a/crys3d/hklviewer/display2.py b/crys3d/hklviewer/display2.py index 399dc555ad..fe01cadc7d 100644 --- a/crys3d/hklviewer/display2.py +++ b/crys3d/hklviewer/display2.py @@ -55,7 +55,7 @@ def generate_systematic_absences(array, return absence_array -def nth_power_scale(dataarray, nth_power): +def nth_power_scale(dataarray, nth_power, is_sigmas=False): """ set nth_power to a number for dampening or enhancing the difference between the smallest and the largest values. @@ -65,14 +65,18 @@ def nth_power_scale(dataarray, nth_power): If nth_power=NaN then an automatic value is computed that maps the smallest values to 0.1 of the largest values """ - absdat = flex.abs(dataarray).as_double() - absdat2 = graphics_utils.NoNansArray(absdat) # much faster than flex.double([e for e in absdat if not math.isnan(e)]) - maxdat = flex.max(absdat2) - mindat = max(1e-10*maxdat, flex.min(absdat2) ) + maxdat = flex.max(dataarray) + mindat = flex.min(dataarray) + offset = mindat - 0.001 # avoid log(0) + offsetmin = mindat - offset + offsetmax = maxdat - offset + offsetarr = dataarray - offset # only autoscale for sensible values of maxdat and mindat if math.isnan(nth_power) and maxdat > mindat : # amounts to automatic scale - nth_power = math.log(0.2)/(math.log(mindat) - math.log(maxdat)) - datascaled = flex.pow(absdat, nth_power) + nth_power = math.log(10)/(math.log(offsetmax) - math.log(offsetmin)) + if is_sigmas: + nth_power *= -1.0 # want small sigmas to have larger radii and large sigmas to have smaller radii + datascaled = flex.pow(offsetarr, nth_power) return datascaled, nth_power @@ -352,25 +356,26 @@ def generate_view_data(self): # assuming last part of the labels indicates the phase label as in ["FCALC","PHICALC"] self.colourlabel = "Phase of " + self.miller_array.info().label_string() elif (settings.sigma_color_radius) and sigmas is not None: - data_for_colors = sigmas.as_double() + data_for_colors = 1.0/sigmas.as_double() self.colourlabel = "Sigma of " + self.miller_array.info().label_string() else : data_for_colors = flex.abs(data.deep_copy()) + uc = self.work_array.unit_cell() self.min_dist = min(uc.reciprocal_space_vector((1,1,1))) * self.renderscale min_radius = 0.05 * self.min_dist max_radius = 0.5 * self.min_dist if (settings.sigma_color_radius) and sigmas is not None: - data_for_radii, self.nth_power_scale_radii = nth_power_scale(flex.abs(sigmas.as_double().deep_copy()), - settings.nth_power_scale_radii) + data_for_radii, self.nth_power_scale_radii = nth_power_scale(sigmas.as_double().deep_copy(), + settings.nth_power_scale_radii, True) else : - data_for_radii, self.nth_power_scale_radii = nth_power_scale(flex.abs(data.deep_copy()), + data_for_radii, self.nth_power_scale_radii = nth_power_scale(data.deep_copy(), settings.nth_power_scale_radii) if (settings.slice_mode): data = data.select(self.slice_selection) # Computing rgb colours of each reflection is slow so make a small array # of precomputed colours to use as a lookup table for each reflection - if isinstance(data, flex.complex_double): + if isinstance(data, flex.complex_double): # map coefficients are coloured by phase values COL = MplColorHelper(settings.color_scheme, 0, 360) rgbcolarray = [ COL.get_rgb(d)[0:3] for d in range(360) ] if self.isUsingFOMs(): @@ -409,8 +414,7 @@ def generate_view_data(self): colors = colors.select(self.slice_selection) data_for_radii = data_for_radii.select(self.slice_selection) if len(data_for_radii): - #dat2 = flex.abs(flex.double([e for e in data_for_radii if not math.isnan(e)])) - dat2 = flex.abs(flex.double( graphics_utils.NoNansArray( data_for_radii, 0.1 ) )) + dat2 = flex.double( graphics_utils.NoNansArray( data_for_radii, 0.1 ) ) # don't divide by 0 if dealing with selection of Rfree array where all values happen to be zero scale = max_radius/(flex.max(dat2) + 0.001) radii = data_for_radii * (self.settings.scale * scale) @@ -424,7 +428,6 @@ def generate_view_data(self): self.colors = colors if isinstance(data, flex.complex_double): self.foms = foms_for_colours - #print(min_dist, min_radius, max_radius, flex.min(data_for_radii), flex.max(data_for_radii), scale) def isUsingFOMs(self): diff --git a/crys3d/hklviewer/helpers.py b/crys3d/hklviewer/helpers.py index 04ea21d95d..2be74ebc68 100644 --- a/crys3d/hklviewer/helpers.py +++ b/crys3d/hklviewer/helpers.py @@ -11,7 +11,7 @@ from .qt import QCursor, QKeySequence from .qt import ( QAbstractItemView, QCheckBox, QTableWidget, QAction, QDoubleSpinBox, QMenu, QTableView, QDialog, QSpinBox, QLabel, QComboBox, QGridLayout, QGroupBox, - QScrollArea, QVBoxLayout, QHeaderView, QTableWidgetItem + QScrollArea, QVBoxLayout, QHeaderView, QTableWidgetItem, QSizePolicy ) import math, csv from io import StringIO @@ -24,10 +24,16 @@ def stepBy(self, steps): QDoubleSpinBox.stepBy(self, steps) if hasattr(self, "onStepBy"): self.onStepBy() + def mouseReleaseEvent(self, event): + # used by NGL_HKLViewer.fontspinBox since changing fonts and resizing windows is slow and + # valueChanged() will then mistakenly be fired twice when clicking once on the spin control + QDoubleSpinBox.mouseReleaseEvent(self, event) + if hasattr(self, "onMouseRelease"): + self.onMouseRelease() class MyhorizontalHeader(QHeaderView): -# Assigned to HeaderDataTableWidget (HKLViewer.NGL_HKLViewer.millertable) but in +# Assigned to HeaderDataTableWidget (NGL_HKLViewer.millertable) but in # NGL_HKLViewer.createFileInfoBox() as to avoid a very long chain of parents when # accessing select_millertable_column_dlg() # Display the labels of the columns in the millertable @@ -295,7 +301,7 @@ def __init__(self, parent=None, width=5, height=4, dpi=dpi): # total size of canvas self.fig.set_size_inches(7,40) # total size of canvas super(MplCanvas, self).__init__(self.fig) - cid = self.fig.canvas.mpl_connect('button_press_event', self.on_press) + self.cid1 = self.fig.canvas.mpl_connect('button_press_event', self.on_press) def on_press(self, event): if event.inaxes is not None: @@ -303,6 +309,7 @@ def on_press(self, event): self.parent.updatelabel() self.parent.EnactColourMapSelection() + # TODO work out scaling of canvas to match QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # and class MPLColourSchemes(QtWidgets.QDialog): diff --git a/crys3d/hklviewer/jsview_3d.py b/crys3d/hklviewer/jsview_3d.py index 02bd4a79c4..8b760fcf70 100644 --- a/crys3d/hklviewer/jsview_3d.py +++ b/crys3d/hklviewer/jsview_3d.py @@ -342,9 +342,11 @@ def update_settings(self, diff_phil, curphilparam) : if has_phil_path(diff_phil, "color_scheme"): self.add_colour_scheme_to_dict() + self.sceneisdirty = True if has_phil_path(diff_phil, "color_powscale"): self.add_colour_powscale_to_dict() + self.sceneisdirty = True if has_phil_path(diff_phil, "nth_power_scale_radii"): self.add_nth_power_scale_radii_to_dict() @@ -1363,6 +1365,11 @@ def getprecision(v1,v2): self.calc_rotation_axes() nvaluelabels = int(ln/self.params.viewer.ncolourlabels ) colourgradstrs = [] + if self.params.hkls.sigma_color_radius: + lst = list(colourscalararray) + lst.reverse() # flip labels on chart when showing sigmas + colourscalararray = flex.double(lst ) + # if displaying phases from map coefficients together with fom values then for g,colourgradarray in enumerate(colourgradarrays): self.colourgradientvalues = []