Skip to content

Commit

Permalink
Workaround for bug in fontspin control when resizing GUI when changin…
Browse files Browse the repository at this point in the history
…g font size by implementing QDoubleSpinBox.mouseReleaseEvent() as a signal rather than using buggy valueChanged().

Bug fix for scaling radii properly according to data values
  • Loading branch information
Oeffner committed Jul 30, 2022
1 parent b906a8c commit ea59614
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 26 deletions.
28 changes: 20 additions & 8 deletions crys3d/hklviewer/HKLviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -78,22 +78,27 @@ 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)
mainLayout.addWidget(self.aboutlabel, 0, 0, 1, 3)
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 = """<html><head/><body><p>
Expand Down Expand Up @@ -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:")

Expand Down Expand Up @@ -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()


Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
33 changes: 18 additions & 15 deletions crys3d/hklviewer/display2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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


Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand Down
13 changes: 10 additions & 3 deletions crys3d/hklviewer/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -295,14 +301,15 @@ 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:
self.parent.selcolmap = cmaps[event.inaxes.get_subplotspec().rowspan.start]
self.parent.updatelabel()
self.parent.EnactColourMapSelection()


# TODO work out scaling of canvas to match QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
# and
class MPLColourSchemes(QtWidgets.QDialog):
Expand Down
7 changes: 7 additions & 0 deletions crys3d/hklviewer/jsview_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 = []
Expand Down

0 comments on commit ea59614

Please sign in to comment.