From b6b8e3bb27e2a75a076155baa921e478fcb71c0d Mon Sep 17 00:00:00 2001 From: Robert Oeffner Date: Tue, 1 Nov 2022 15:03:28 +0100 Subject: [PATCH] Bug fixes: Allow binning unmerged datasets, UI fixes when user enters explicit bin thresholds, rectifying broken I/SigI preset button --- crys3d/hklviewer/HKLviewer.py | 5 +- crys3d/hklviewer/jsview_3d.py | 96 ++++++++++++++++++------------ crys3d/hklviewer/preset_buttons.py | 2 +- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/crys3d/hklviewer/HKLviewer.py b/crys3d/hklviewer/HKLviewer.py index bfe5d0b84a..da5b2f18c2 100644 --- a/crys3d/hklviewer/HKLviewer.py +++ b/crys3d/hklviewer/HKLviewer.py @@ -497,6 +497,7 @@ def __init__(self, thisapp, isembedded=False): #, cctbxpython=None): self.matching_arrays = [] self.bin_infotpls = None self.bin_opacities= None + self.nbins = 1 self.lowerbinvals = [] self.upperbinvals = [] self.html_url = None @@ -984,9 +985,9 @@ def ProcessMessages(self): # needed for determining if expansion checkbox for P1 and friedel are enabled or disabled self.ano_spg_tpls = self.infodict.get("ano_spg_tpls",[]) - if self.infodict.get("current_scene_id", None) is not None: + if self.infodict.get("current_labels", None) is not None: # needed in case we run xtriage on this miller array - self.current_labels = self.array_infotpls[self.infodict.get("current_scene_id")][1][0] + self.current_labels = self.infodict.get("current_labels") if self.infodict.get("colnames_select_lst"): self.colnames_select_lst = self.infodict.get("colnames_select_lst",[]) diff --git a/crys3d/hklviewer/jsview_3d.py b/crys3d/hklviewer/jsview_3d.py index d178146643..77fd973bf9 100644 --- a/crys3d/hklviewer/jsview_3d.py +++ b/crys3d/hklviewer/jsview_3d.py @@ -333,14 +333,15 @@ def update_settings(self, diff_phil, curphilparam) : msg = "" if self.params.viewer.scene_id is not None and \ has_phil_path(diff_phil, - "scene_id", + #"scene_id", "show_missing", "show_only_missing", "show_systematic_absences", "binner_idx", "binlabel", "nbins", - ) and not has_phil_path(diff_phil, "scene_bin_thresholds") : + ) and not has_phil_path(diff_phil, "scene_bin_thresholds"): + self.binvalsboundaries = [] self.binvals, self.nuniqueval = self.calc_bin_thresholds(curphilparam.binning.binner_idx, curphilparam.binning.nbins) self.sceneisdirty = True @@ -900,6 +901,8 @@ def get_scene_id_from_label_or_type(self, datalabel, datatype=None): def get_label_type_from_scene_id(self, sceneid): + if sceneid is None: + return None,None # Find data label and type for a particular sceneid assert sceneid < len(self.hkl_scenes_infos) datalabel = self.hkl_scenes_infos[sceneid][3] @@ -960,12 +963,21 @@ def calc_bin_thresholds(self, binner_idx, nbins): bindata, dummy = self.get_matched_binarray(binner_idx) selection = flex.sort_permutation( bindata ) bindata_sorted = bindata.select(selection) - # get binvals by dividing bindata_sorted with nbins + # Get binvals by dividing bindata_sorted with nbins + # This yields approximately the same number of reflections in each bin binvals = [bindata_sorted[0]] * (nbins+1) # for i,e in enumerate(bindata_sorted): idiv = int( (nbins+1)*float(i)/len(bindata_sorted)) binvals[idiv] = e + # If this didn't yield enough bins with different binvalues, say a multiplicity dataset + # with values between [1;6] but 95% reflections having multiplcity=2 then assign + # binvalues equidistantly between [1;6] even if some bins are empty nuniquevalues = len(set(list(bindata))) + if len(set(binvals)) < nbins: + binincr = (max(bindata) - min(bindata))/nbins + for i in range(nbins): + binvals[i] = i*binincr + min(bindata) + binvals.sort() self.mprint("Bin thresholds are:\n" + str(binvals), verbose=1) return binvals, nuniquevalues @@ -1018,22 +1030,16 @@ def MatchBinArrayToSceneArray(self): binarraydata, dummy = self.get_matched_binarray(self.params.binning.binner_idx) scenearraydata = self.HKLscene_from_dict().data ibinarray = self.bin_labels_type_idxs[self.params.binning.binner_idx][2] - matchindices = miller.match_indices(self.HKLscene_from_dict().indices, - self.HKLscene_from_dict(ibinarray).indices ) - matched_binarray = binarraydata.select( matchindices.pairs().column(1) ) - #valarray.sort(by_value="packed_indices") - #import code, traceback; code.interact(local=locals(), banner="".join( traceback.format_stack(limit=10) ) ) - #missing = scenearraydata.lone_set( valarray ) - # insert NAN values for reflections in self.miller_array not found in binarray - #valarray = display.ExtendMillerArray(valarray, missing.size(), missing.indices() ) - #match_valindices = miller.match_indices(scenearray.indices(), valarray.indices() ) - #match_valarray = valarray.select( match_valindices.pairs().column(1) ) - #match_valarray.sort(by_value="packed_indices") - #match_valarray.set_info(binarraydata.info() ) + if len(set(self.HKLscene_from_dict(ibinarray).indices)) < self.HKLscene_from_dict(ibinarray).indices.size(): + raise Sorry("The indices in the array chosen for binning is not unique") + + matchindices = miller.match_multi_indices(self.HKLscene_from_dict(ibinarray).indices, + self.HKLscene_from_dict().indices ) + matched_binarray = binarraydata.select( matchindices.pairs().column(0) ) # patch the bin array so its sequence matches the scene array patched_binarraydata = [] c = 0 - for b in matchindices.pair_selection(0): + for b in matchindices.pair_selection(1): if b: patched_binarraydata.append(matched_binarray[c]) c +=1 @@ -1258,7 +1264,6 @@ def DrawNGLJavaScript(self, blankscene=False): self.radii2 = [] self.spbufttips = [] - self.binvalsboundaries = [] if not blankscene: if bin_labels_type_idx[0] =="Resolution": self.binvalsboundaries = self.binvals @@ -1268,24 +1273,28 @@ def DrawNGLJavaScript(self, blankscene=False): self.bindata = self.scene.singletonsiness else: # get upper and lower bounds for the dataset used for binning - dummy, self.binvalsboundaries = self.get_matched_binarray(self.params.binning.binner_idx) - # binvals derived from scene_bin_thresholds must be sorted - self.binvals.sort() - # if minimum or maximum of binvals are smaller or bigger than lower or - # upper bounds then use those values instead - vals = self.binvals[:] - if self.binvals[0] > self.binvalsboundaries[0]: - vals[0] = self.binvalsboundaries[0] - if self.binvals[-1] < self.binvalsboundaries[1]: - vals[-1] = self.binvalsboundaries[1] - self.binvalsboundaries = vals - self.binvalsboundaries.sort() self.bindata = self.MatchBinArrayToSceneArray() + if len(self.binvalsboundaries)==0 or len(self.params.binning.scene_bin_thresholds) > 0: + dummy, self.binvalsboundaries = self.get_matched_binarray(self.params.binning.binner_idx) + # binvals derived from scene_bin_thresholds must be sorted + # if minimum or maximum of binvals are smaller or bigger than lower or + # upper bounds then use those values instead + self.binvals.sort() + vals = self.binvals[:] + # ignoring nan values add binvalsboundaries if these are smaller or bigger than values in binvals + nonanbinvals = [e for e in self.binvals if not math.isnan(e)] + if nonanbinvals[0] > self.binvalsboundaries[0]: + vals[0] = self.binvalsboundaries[0] + if nonanbinvals[-1] < self.binvalsboundaries[1]: + vals[-1] = self.binvalsboundaries[-1] + # if nan values are present then sort with nan being the last value + vals = list(set( vals)) # no duplicates + self.binvalsboundaries = sorted(vals, key= lambda e: sys.maxsize if math.isnan(e) else e) self.nbinvalsboundaries = len(self.binvalsboundaries) # avoid resetting opacities of bins unless we change the number of bins if self.oldnbinvalsboundaries != self.nbinvalsboundaries and not self.executing_preset_btn: - self.params.binning.bin_opacity = [ [1.0, e] for e in range(self.nbinvalsboundaries + 1) ] + self.params.binning.bin_opacity = [ [1.0, e] for e in range(self.nbinvalsboundaries ) ] self.oldnbinvalsboundaries = self.nbinvalsboundaries # Un-binnable data are scene data values where there are no matching reflections in the bin data # Put these in a separate bin and be diligent with the book keeping! @@ -1298,7 +1307,7 @@ def DrawNGLJavaScript(self, blankscene=False): def data2bin(d, binvalsboundaries, nbinvalsboundaries): for ibin, binval in enumerate(binvalsboundaries): if math.isnan(d): # NaN values are un-binnable. Tag them for an additional last bin - return nbinvalsboundaries + return nbinvalsboundaries-1 if (ibin+1) == nbinvalsboundaries: return ibin if d > binval and d <= binvalsboundaries[ibin+1]: @@ -1333,8 +1342,13 @@ def getprecision(v1,v2): self.bin_infotpls = [] if self.nuniqueval < self.params.binning.nbins: self.mprint("%d bins was requested but %s data has only %d unique value(s)!" %(self.params.binning.nbins, colstr, self.nuniqueval), 0) - for ibin in range(self.nbinvalsboundaries): + for ibin in range(self.nbinvalsboundaries+1): + mstr ="" nreflsinbin = len(self.radii2[ibin]) + bin2 = float("nan"); bin1= float("nan") # indicates un-binned data + if ibin == self.nbinvalsboundaries: + mstr= "bin[%d] has %d reflections with no %s values (assigned to %2.3f)" %(cntbin, nreflsinbin, \ + colstr, bin1) precision = 3 if ibin < (self.nbinvalsboundaries-1): bin1 = self.binvalsboundaries[ibin] @@ -1357,10 +1371,16 @@ def getprecision(v1,v2): binformatstr = "]%2." + str(precision) + "f; %2." + str(precision) + "f]" mstr= "bin[%d] has %d reflections with %s in " %(cntbin, nreflsinbin, colstr) mstr += binformatstr %(bin1, bin2) - self.bin_infotpls.append( roundoff((nreflsinbin, bin1, bin2 ), precision) ) - self.binstrs.append(mstr) - self.mprint(mstr, verbose=1) - cntbin += 1 + if len(self.bin_infotpls) > 0 \ + and math.isnan(self.bin_infotpls[-1][1]) \ + and math.isnan(self.bin_infotpls[-1][2]) \ + and nreflsinbin == 0: + continue + + self.bin_infotpls.append( roundoff((nreflsinbin, bin1, bin2 ), precision) ) + self.binstrs.append(mstr) + self.mprint(mstr, verbose=1) + cntbin += 1 if self.params.binning.bin_opacity != None: opqlist = self.params.binning.bin_opacity @@ -1407,8 +1427,6 @@ def getprecision(v1,v2): self.RemoveStageObjects() for ibin in range(self.nbinvalsboundaries+1): nreflsinbin = len(self.radii2[ibin]) - if nreflsinbin == 0: - continue if self.debug: self.SetBrowserDebug("true") self.DefineHKL_Axes(str(Hstararrowstart), str(Hstararrowend), @@ -1429,7 +1447,7 @@ def getprecision(v1,v2): self.sceneisdirty = False self.lastscene_id = self.params.viewer.scene_id self.SendInfoToGUI( { "CurrentDatatype": self.get_current_datatype(), - "current_scene_id": self.params.viewer.scene_id } ) + "current_labels": self.get_label_type_from_scene_id( self.params.viewer.scene_id)[0] } ) self.mprint("\nDone rendering reflections ") diff --git a/crys3d/hklviewer/preset_buttons.py b/crys3d/hklviewer/preset_buttons.py index 3e9f355e68..804f1c6e97 100644 --- a/crys3d/hklviewer/preset_buttons.py +++ b/crys3d/hklviewer/preset_buttons.py @@ -76,7 +76,7 @@ ("IoverSigI", "I/SigI >= 2 ( from miller_array.data()/miller_array.sigmas() )", """ - miller_array_operation = "('newarray._data=array1.data()/array1.sigmas()', 'IoverSigI', ['I<