Source code for pwem.wizards.wizard

# -*- coding: utf-8 -*-
# **************************************************************************
# *
# * Authors:     Jose Gutierrez (jose.gutierrez@cnb.csic.es)
# *              J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# *              
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
"""
This module implement some base classes and utils for wizards
The content of this module is not discovered at runtime by pyworkflow.
Usage of this content is though importing it
"""

import tkinter as tk
import tkinter.ttk as ttk

import pyworkflow as pw
import pyworkflow.object as pwobj
import pyworkflow.wizard as pwizard
import pyworkflow.gui.dialog as dialog
from matplotlib import cm
from pwem import convertPixToLength, splitRange
from pyworkflow.gui.plotter import getHexColorList
from pyworkflow.gui.tree import BoundTree, ListTreeProvider, AttributesTreeProvider
from pyworkflow.gui.widgets import LabelSlider, ExplanationText

import pwem.constants as emcts
import pwem.objects as emobj
from pwem import emlib
from pyworkflow.protocol import IntParam, StringParam, FloatParam, LEVEL_ADVANCED
from pyworkflow.utils import Icon

# Color map wizard constants
HIGHEST_ATTR = 'highest'
LOWEST_ATTR = 'lowest'
INTERVAL_ATTR = 'intervals'
COLORMAP_ATTR = 'colorMap'


# ===============================================================================
#    Wizard EM base class
# ===============================================================================

[docs]class EmWizard(pwizard.Wizard): def _getMics(self, objs): return [mic.clone() for mic in objs] def _getParticles(self, objs, num=100): particles = [] for i, par in enumerate(objs): # Cloning the particle particle = par.clone() if i == num: # Only load up to NUM particles break particles.append(particle) return particles def _getVols(self, objs): vols = [] if isinstance(objs, emobj.Volume): vols.append(objs) else: vols = [vol.clone() for vol in objs] return vols def _getListProvider(self, objs): """ This should be implemented to return the list of object to be displayed in the tree. """ provider = None if objs.hasValue(): # If objs is a PointerList currently it can only be formed of SetOfVolumes and Volume # (for protocol align_volume). Should this change review this part if isinstance(objs, pwobj.PointerList): vols_total = [] for pointer in objs: obj = pointer.get() vols = self._getVols(obj) vols_total.extend(vols) provider = ListTreeProvider(vols_total) else: objs = objs.get() if isinstance(objs, emobj.SetOfMicrographs): mics = self._getMics(objs) provider = ListTreeProvider(mics) if isinstance(objs, emobj.SetOfParticles): particles = self._getParticles(objs) provider = ListTreeProvider(particles) if isinstance(objs, emobj.SetOfVolumes) or isinstance(objs, emobj.Volume): vols = self._getVols(objs) provider = ListTreeProvider(vols) return provider return None def _getInputProtocol(self, targets, protocol): label = [] value = [] for k, v in targets: if k.__name__ == protocol.getClassName(): label = v for val in v: value.append(protocol.getAttributeValue(val)) if len(label) > 1: return label, value else: return label[0], value[0]
# =============================================================================== # Wizards base classes # ===============================================================================
[docs]class VariableWizard(pwizard.Wizard): """Wizard base class object where input and output paramNames can be modified and added, so one wizard can be used in several protocols with parameters of different names""" #Variables _targets, _inputs and _outputs must be created in sons # _targets, _inputs, _outputs = [], {}, {}
[docs] def addTarget(self, protocol, targets, inputs, outputs): '''Add a target to a wizard and the input and output parameters are stored in a dictionary with (protocol, targetParamName) as key. Targets must be added one by one.''' self._targets += [(protocol, targets)] self._inputs.update({(protocol, targets[0]): inputs}) self._outputs.update({(protocol, targets[0]): outputs})
[docs] def getInputOutput(self, form): '''Retrieving input and output paramNames corresponding to the protocol and target of the wizard clicked''' outParam = '' for target in self._targets: if form.wizParamName == target[1][0] and form.protocol.__class__ == target[0]: prot, target = (target[0], target[1][0]) inParams, outParam = self._inputs[(prot, target)], self._outputs[(prot, target)] inParams = self.filterPresentInputs(inParams, form.protocol) return inParams, outParam
[docs] def filterPresentInputs(self, inputParams, protocol): '''Filter the inputs to allow flexibility: 1) InputParams can be lists: the first not None parameter of the list will be chosen. 2) InputParams can be dict: key -> EnumParam (or IntParam) which functions as index value -> list containing paramNames. The hey index element will be chosen ''' fInpParams = [] for inPar in inputParams: if type(inPar) == list: present = '' for option in inPar: if getattr(protocol, option).get(): present = option break fInpParams.append(present) elif type(inPar) == dict: idxParam = list(inPar.keys())[0] optionParams = inPar[idxParam] idx = getattr(protocol, idxParam).get() fInpParams.append(optionParams[idx]) else: fInpParams.append(inPar) return fInpParams
[docs]class DownsampleWizard(EmWizard):
[docs] def show(self, form, value, label, units=emcts.UNIT_PIXEL): protocol = form.protocol provider = self._getProvider(protocol) if provider is not None: args = {'unit': units, 'downsample': value } d = DownsampleDialog(form.root, provider, **args) if d.resultYes(): form.setVar(label, d.getDownsample()) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs] @classmethod def getView(self): return "wiz_downsampling"
[docs]class CtfWizard(EmWizard):
[docs] def show(self, form, value, label, units=emcts.UNIT_PIXEL): protocol = form.protocol # form.setParamFromVar('inputMicrographs') # update selected input micrographs provider = self._getProvider(protocol) if provider is not None: args = {'unit': units, 'lf': value[0], 'hf': value[1] } d = CtfDialog(form.root, provider, **args) if d.resultYes(): form.setVar(label[0], d.getLowFreq()) form.setVar(label[1], d.getHighFreq()) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs] @classmethod def getView(self): return "wiz_ctf"
[docs]class MaskRadiusWizard(EmWizard):
[docs] def show(self, form, value, label, units=emcts.UNIT_PIXEL): protocol = form.protocol provider = self._getProvider(protocol) if provider is not None: d = MaskPreviewDialog(form.root, provider, maskRadius=value, unit=units) if d.resultYes(): if units == emcts.UNIT_ANGSTROM: value = round(d.getRadiusAngstroms(d.radiusSlider)) # Must be an integer else: value = int(d.getRadius(d.radiusSlider)) self.setVar(form, label, value) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs] def setVar(self, form, label, value): form.setVar(label, value)
[docs]class MaskRadiiWizard(EmWizard):
[docs] def show(self, form, value, label, units=emcts.UNIT_PIXEL): protocol = form.protocol provider = self._getProvider(protocol) if provider is not None: d = MaskRadiiPreviewDialog(form.root, provider, innerRadius=value[0], outerRadius=value[1], unit=units) if d.resultYes(): if units == emcts.UNIT_ANGSTROM: form.setVar(label[0], d.getRadiusAngstroms(d.radiusSliderIn)) form.setVar(label[1], d.getRadiusAngstroms(d.radiusSliderOut)) else: form.setVar(label[0], int(d.getRadius(d.radiusSliderIn))) form.setVar(label[1], int(d.getRadius(d.radiusSliderOut))) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs] @classmethod def getView(self): return "wiz_volume_mask_radii"
[docs]class ParticleMaskRadiusWizard(MaskRadiusWizard): pass
[docs]class VolumeMaskRadiusWizard(MaskRadiusWizard): pass
[docs]class ParticlesMaskRadiiWizard(MaskRadiiWizard): pass
[docs]class VolumeMaskRadiiWizard(MaskRadiiWizard): pass
[docs]class FilterWizard(EmWizard):
[docs] def show(self, form, value, label, mode, unit=emcts.UNIT_PIXEL, **args): protocol = form.protocol provider = self._getProvider(protocol) if provider is not None: self.mode = mode args.update({'mode': self.mode, 'unit': unit}) if self.mode == emcts.FILTER_LOW_PASS: args['showLowFreq'] = False args['highFreq'] = value[1] args['freqDecay'] = value[2] elif self.mode == emcts.FILTER_HIGH_PASS: args['showHighFreq'] = False args['lowFreq'] = value[0] args['freqDecay'] = value[2] elif self.mode == emcts.FILTER_BAND_PASS: args['lowFreq'] = value[0] args['highFreq'] = value[1] args['freqDecay'] = value[2] else: raise Exception("Unknown mode '%s'" % self.mode) d = BandPassFilterDialog(form.root, provider, **args) if d.resultYes(): def setFormValue(flag, value, index): if args.get(flag, True): if unit == emcts.UNIT_ANGSTROM: value = d.samplingRate / value form.setVar(label[index], value) setFormValue('showLowFreq', d.getLowFreq(), 0) setFormValue('showHighFreq', d.getHighFreq(), 1) setFormValue('showDecay', d.getFreqDecay(), 2) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs]class FilterParticlesWizard(FilterWizard): pass
[docs]class FilterVolumesWizard(FilterWizard): pass
[docs]class GaussianWizard(EmWizard):
[docs] def show(self, form, value, label, units=emcts.UNIT_PIXEL_FOURIER): protocol = form.protocol provider = self._getProvider(protocol) if provider is not None: args = {'freqSigma': value, 'unit': units } d = GaussianFilterDialog(form.root, provider, **args) if d.resultYes(): form.setVar(label, d.getFreqSigma()) else: dialog.showWarning("Empty input", "Select elements first", form.root)
[docs]class GaussianParticlesWizard(GaussianWizard): pass
[docs]class GaussianVolumesWizard(GaussianWizard): pass
[docs]class PDBVolumeWizard(EmWizard):
[docs] def show(self, form): if form.protocol.inputPDB.hasValue(): pdb = form.protocol.inputPDB.get() print("pdb ", pdb._volume, str(pdb._volume)) if pdb._volume: print("Setting ", str(pdb.getVolume())) ptr = pwobj.Pointer() ptr.copy(form.protocol.inputPDB) ptr.setExtended(ptr.getExtended() + "._volume") # ptr.set(pdb.getVolume()) form.setVar('inputVol', ptr)
[docs]class ColorScaleWizardBase(EmWizard): """ Base wizard to edit color scale parameters Usage: 1.- define new class inheriting this one 2.- declare _targets = ColorScaleWizardBase.defineTargets(youviewer) in a class scope, right after class definition. 3.- call ColorScaleWizardBase.defineColorScaleParams(group) in your viewer._defineParams 4.- use attributes in your plotting method """
[docs] @classmethod def defineTargets(cls, *viewersClass): """ :return targets list per each viewer class passed""" targets = [] for viewer in viewersClass: targets.append((viewer, [HIGHEST_ATTR, LOWEST_ATTR, INTERVAL_ATTR, COLORMAP_ATTR])) return targets
[docs] def show(self, form): viewer = form.protocol d = ColorScaleDialog(form.root, viewer.lowest.get(), viewer.highest.get(), viewer.intervals.get(), viewer.colorMap.get()) # If accepted if d.resultYes(): form.setVar(LOWEST_ATTR, d.getLowest()) form.setVar(HIGHEST_ATTR, d.getHighest()) form.setVar(INTERVAL_ATTR, d.getIntervals()) form.setVar(COLORMAP_ATTR, d.getColorPalette())
[docs] @staticmethod def defineColorScaleParams(form, defaultHighest=10, defaultLowest=0, defaultIntervals=11, defaultColorMap="jet"): line = form.addLine("Color scale options:", help="Options to define the color scale limits, intervals (advanced) and color set. Useful when you have outliers ruining your " "visualization/plot.") line.addParam(HIGHEST_ATTR, FloatParam, default=defaultHighest, label="Highest", help="Highest value for the scale") line.addParam(LOWEST_ATTR, FloatParam, default=defaultLowest, label="Lowest", help="lowest value of the scale.") line.addParam(INTERVAL_ATTR, IntParam, default=defaultIntervals, label="Intervals", help="Number of labels of the scale", expertLevel=LEVEL_ADVANCED) line.addParam(COLORMAP_ATTR, StringParam, default=defaultColorMap, label="Color set", help="Combination of color for the scale")
# =============================================================================== # Dialogs used by wizards # ===============================================================================
[docs]class PreviewDialog(dialog.Dialog): """ This will be the base class for several wizards. The layout of this wizard will be: 1. Left panel(Items) that contains a list of items to preview 2. Right-top panel (Preview) where some preview of the items will be displayed 3. Right-bottom panel (Controls) where some controls can change the preview """ def __init__(self, parent, provider, **args): """ Params: parent: parent windows of the dialog. provider: the TreeProvider to populate items tree. """ # Set the attributes in **args for k, v in args.items(): setattr(self, k, v) self.provider = provider self.firstItem = provider.getObjects()[0] self.isInnerRad = False self.isMakingBigger = False self.step = 1 self.expText = [] buttons = [('Select', dialog.RESULT_YES), ('Cancel', dialog.RESULT_CANCEL)] dialog.Dialog.__init__(self, parent, "Wizard", buttons=buttons, default='Select', **args)
[docs] def body(self, bodyFrame): bodyFrame.config() bodyFrame.columnconfigure(0, weight=1) bodyFrame.columnconfigure(1, weight=1) bodyFrame.columnconfigure(2, weight=1) # Create explanation label self.expText = ExplanationText(bodyFrame) self.expText.text.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky='news') # Create items frame itemsFrame = tk.Frame(bodyFrame, bg=pw.TK_GRAY_DEFAULT) itemsFrame.grid(row=1, column=0, padx=5, sticky='news') itemsFrame.columnconfigure(0, weight=1) itemsFrame.rowconfigure(0, weight=1) itemsTree = BoundTree(itemsFrame, self.provider) itemsTree.grid(row=0, column=0, padx=5, pady=5, sticky='news') itemsTree.itemClick = self._itemSelected # Create preview frame previewFrame = tk.Frame(bodyFrame, bg=pw.TK_GRAY_DEFAULT) previewFrame.grid(row=1, column=1, padx=5, pady=5) self._beforePreview() self._createPreview(previewFrame) # Create controls frame controlsFrame = tk.Frame(bodyFrame) controlsFrame.grid(row=2, column=0, columnspan=2, padx=5, pady=5, sticky='sew') controlsFrame.columnconfigure(0, weight=1) controlsFrame.rowconfigure(2, weight=1) self._createControls(controlsFrame) self._itemSelected(self.firstItem) itemsTree.selectChildByIndex(0) # Select the first item
def _beforePreview(self): """ Called just before setting the preview. This is the place to set data values such as: labels, constants... """ pass def _createPreview(self, frame): """ Should be implemented by subclasses to create the items preview. """ pass def _createControls(self, frame): """ Create controls to be used. """ pass def _itemSelected(self, obj): """ This will be call when an item in the tree is selected. """ pass
[docs]class ImagePreviewDialog(PreviewDialog): def _beforePreview(self): self.dim = 256 self.previewLabel = '' def _createPreview(self, frame): """ Should be implemented by subclasses to create the items preview. """ from pyworkflow.gui.matplotlib_image import ImagePreview self.preview = ImagePreview(frame, self.dim, label=self.previewLabel) self.preview.grid(row=0, column=0) def _itemSelected(self, obj): index = obj.getIndex() filename = emlib.image.ImageHandler.fixXmippVolumeFileName(obj) if index: filename = "%03d@%s" % (index, filename) self.image = emlib.image.ImageHandler()._img try: self.image.readPreview(filename, self.dim) if filename.endswith('.psd'): self.image.convertPSD() self.Z = self.image.getData() except Exception as e: from pyworkflow.gui.matplotlib_image import getPngData self.Z = getPngData(pw.getResourcesPath(Icon.NO_IMAGE_128)) dialog.showError("Input particles", "Error reading image <%s>" % filename, self) self.preview.updateData(self.Z)
[docs]class DownsampleDialog(ImagePreviewDialog): def _beforePreview(self): self.expText.updateExpText(emcts.FREQ_BANDPASS_WIZ_MSG, width=75) ImagePreviewDialog._beforePreview(self) self.lastObj = None self.rightPreviewLabel = "PSD" self.message = "Computing PSD..." self.previewLabel = "Micrograph" self.rightImage = emlib.image.ImageHandler()._img def _createPreview(self, frame): """ Should be implemented by subclasses to create the items preview. """ leftFrame = tk.Frame(frame) leftFrame.grid(row=0, column=0) rightFrame = tk.Frame(frame) rightFrame.grid(row=0, column=1) ImagePreviewDialog._createPreview(self, leftFrame) self.rightPreview = self._createRightPreview(rightFrame) self.rightPreview.grid(row=0, column=0) def _createRightPreview(self, rightFrame): from pyworkflow.gui.matplotlib_image import ImagePreview return ImagePreview(rightFrame, self.dim, label=self.rightPreviewLabel) def _createControls(self, frame): self.downVar = tk.StringVar() self.downVar.set(getattr(self, 'downsample', 1)) downFrame = tk.Frame(frame) downFrame.grid(row=0, column=0, pady=5, sticky='new') downLabel = tk.Label(downFrame, text='Downsample') downLabel.grid(row=0, column=0, sticky='ew') downEntry = tk.Entry(downFrame, width=10, textvariable=self.downVar) downEntry.grid(row=0, column=1, padx=5, sticky='ew') downButton = tk.Button(downFrame, text='Preview', command=self._doPreview) downButton.grid(row=1, column=1, padx=5, pady=5, sticky='ew')
[docs] def getDownsample(self): return float(self.downVar.get())
[docs] def manageMaskVals(self): # To be coded by each child, if necessary pass
def _itemSelected(self, obj): self.lastObj = obj ImagePreviewDialog._itemSelected(self, obj) dialog.FlashMessage(self, self.message, func=self._computeRightPreview) self.rightPreview.updateData(self.rightImage.getData()) self.manageMaskVals() def _doPreview(self, e=None): if self.lastObj is None: dialog.showError("Empty selection", "Select an item first before preview", self) else: self._itemSelected(self.lastObj) def _computeRightPreview(self): """ This function should compute the right preview using the self.lastObj that was selected """ emlib.fastEstimateEnhancedPSD(self.rightImage, self.lastObj.getFileName(), self.getDownsample(), self.dim, 2)
[docs]class CtfDialog(DownsampleDialog): def _createRightPreview(self, rightFrame): self.isInnerRad = True listeners = {"<Button-4>": self.makeBigger, "<Button-5>": self.makeSmaller, "<Up>": self.upKeyPress, "<Down>": self.downKeyPress} from pyworkflow.gui.matplotlib_image import PsdPreview return PsdPreview(rightFrame, self.dim, self.lf, self.hf, label=self.rightPreviewLabel, listenersDict=listeners)
[docs] def downKeyPress(self, event): self.isInnerRad = False self.highlightOuterSlider()
[docs] def upKeyPress(self, event): self.isInnerRad = True self.highlightInnerSlider()
[docs] def highlightOuterSlider(self): self.hfSlider.highlightLabel() self.lfSlider.removeHighlightFromLabel()
[docs] def highlightInnerSlider(self): self.lfSlider.highlightLabel() self.hfSlider.removeHighlightFromLabel()
[docs] def makeBigger(self, event): self.isMakingBigger = True if self.isInnerRad: self.lf = self.lf + self.step else: new_val = self.hf + self.step if new_val <= 0.5: # Don't make the mask bigger unless the is equal or lower than the max self.hf = new_val self.manageMaskVals()
[docs] def makeSmaller(self, event): self.isMakingBigger = False if self.isInnerRad: new_val = self.lf - self.step if new_val >= 0: self.lf = new_val else: self.hf = self.hf - self.step self.manageMaskVals()
[docs] def manageMaskVals(self): if self.isMakingBigger: if self.isInnerRad and self.lf >= self.hf: # Inner ring can't be bigger than outer ring # Subtract one step to go back to the nearest lower value self.lf = self.hf - self.step else: if not self.isInnerRad and self.hf <= self.lf: # Outer ring can't be smaller than inner # ring # Add one step to go back to the nearest higher value self.hf = self.lf + self.step # Set the ring sliders in case it comes from the mouse wheel self.setFreq(self.lfSlider, self.lf) self.setFreq(self.hfSlider, self.hf) # Show values self.showValues(self.hfVar, self.hfSlider) self.showValues(self.lfVar, self.lfSlider) # Update mask self.rightPreview.updateFreq(self.lf, self.hf)
def _showInAngstroms(self): return getattr(self, 'showInAngstroms', False) def _createControls(self, frame): frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) self.step = 0.01 self.freqFrame = ttk.LabelFrame(frame, text="Frequencies") self.freqFrame.columnconfigure(0, weight=3) self.freqFrame.grid(row=0, column=2, sticky='news') self.lfSlider = LabelSlider(self.freqFrame, 'Low freq', value=self.lf, from_=0.01, to=0.5, length=178, labelWidth=13, step=self.step, showvalue=0, callback=lambda a, b, c: self.updateSliderInnerRadius(), ) self.lfSlider.grid(row=0, column=0, padx=5, pady=5, sticky='news') self.lfSlider.highlightLabel() self.hfSlider = LabelSlider(self.freqFrame, 'High freq', value=self.lf, from_=0.01, to=0.5, length=178, labelWidth=13, step=self.step, showvalue=0, callback=lambda a, b, c: self.updateSliderOuterRadius(), ) self.hfSlider.grid(row=1, column=0, padx=5, pady=5, sticky='news') # Pack and configure low freq slider self.lfVar = tk.StringVar() self.lfLabel = tk.Label(self.freqFrame, textvariable=self.lfVar, width=26) self.lfLabel.grid(row=0, column=1, sticky='nse', padx=5, pady=5) # Pack and configure high freq slider self.hfVar = tk.StringVar() self.hfLabel = tk.Label(self.freqFrame, textvariable=self.hfVar, width=26) self.hfLabel.grid(row=1, column=1, sticky='nse', padx=5, pady=5) # Update both mask and sliders with the initial values self.manageMaskVals()
[docs] def getDownsample(self): return 1.0 # Micrograph previously downsample, not taken into account here
[docs] def updateFreqRing(self): self.rightPreview.updateFreq(self.getLowFreq(), self.getHighFreq()) self.showValues(self.lfVar, self.lfSlider) self.showValues(self.hfVar, self.hfSlider)
[docs] def updateSliderOuterRadius(self): new_val = self.getFreq(self.hfSlider) # Carry out the action only if the slider is clicked & dragged (prevent from minimal variations when the mouse # is on the slider but not clicking on it) if abs(new_val - self.hf) >= self.step: self.highlightOuterSlider() self.isInnerRad = False self.isMakingBigger = False # Check if the user is making the ring bigger if new_val > self.hf: self.isMakingBigger = True self.hf = new_val self.manageMaskVals()
[docs] def updateSliderInnerRadius(self): new_val = self.getFreq(self.lfSlider) # Highlight only if the slider is clicked & dragged (prevent from minimal variations when the mouse is on the # slider but not clicking on it) if abs(new_val - self.lf) >= self.step: self.highlightInnerSlider() self.isInnerRad = True self.isMakingBigger = False # Check if the user is making the ring bigger if new_val > self.lf: self.isMakingBigger = True self.lf = new_val self.manageMaskVals()
[docs] def showValues(self, var2set, labSlider): """ Show the values selected for the inner and outer radius. If the units are angstroms (sampling_rate = 1, it will show only one value to avoid redundancies """ sr = self.firstItem.getSamplingRate() freqVal = max(self.getFreq(labSlider), 0.0001) # avoid division by zero if sr == 1: var2set.set('{:2.2f} {}'.format(labSlider.slider.get(), emcts.UNIT_PIXEL)) else: var2set.set('{:2.2f} rad/Å | {:5.1f} Å'.format(labSlider.slider.get(), self.getDownsample() * sr / freqVal))
[docs] def getLowFreq(self): return self.lfSlider.get()
[docs] def getHighFreq(self): return self.hfSlider.get()
[docs] @staticmethod def getFreq(freqSlider): return freqSlider.get()
[docs] @staticmethod def setFreq(freqSlider, val): freqSlider.slider.set(val)
[docs]class CtfDownsampleDialog(CtfDialog): def _createControls(self, frame): DownsampleDialog._createControls(self, frame) CtfDialog._createControls(self, frame)
[docs] def getDownsample(self): return float(self.downVar.get())
[docs]class BandPassFilterDialog(DownsampleDialog): def _beforePreview(self): ImagePreviewDialog._beforePreview(self) self.lastObj = None self.rightPreviewLabel = "Filtered" self.message = "Computing filtered image..." self.previewLabel = "Image" self.rightImage = emlib.image.ImageHandler()._img def _createControls(self, frame): self.freqFrame = ttk.LabelFrame(frame, text="Frequencies (%s)" % self.unit, padding="5 5 5 5") self.freqFrame.grid(row=0, column=0) self.showLowFreq = getattr(self, 'showLowFreq', True) self.showHighFreq = getattr(self, 'showHighFreq', True) self.showDecay = getattr(self, 'showDecay', True) if (not self.showLowFreq) or (not self.showHighFreq): label_high = 'Freq' label_low = 'Freq' else: label_high = 'High freq' label_low = 'Low freq' self.samplingRate = 1.0 self.sliTo = 0.5 self.sliFrom = 0.01 if self.unit == emcts.UNIT_ANGSTROM: self.samplingRate = self.firstItem.getSamplingRate() self.itemDim, _, _ = self.firstItem.getDim() self.sliFrom = 2. * self.samplingRate self.sliTo = 2. * self.itemDim * self.samplingRate self.step = self.sliTo / 1000 if self.showLowFreq: self.lfSlider = self.addFreqSlider(label_low, self.lowFreq, col=0) if self.showHighFreq: self.hfSlider = self.addFreqSlider(label_high, self.highFreq, col=1) if self.showDecay: self.freqDecaySlider = self.addFreqSlider('Decay', self.freqDecay, col=2)
[docs] def addFreqSlider(self, label, value, col): fromValue = self.sliFrom toValue = self.sliTo if self.unit == emcts.UNIT_ANGSTROM: fromValue = self.sliTo toValue = self.sliFrom slider = LabelSlider(self.freqFrame, label, from_=fromValue, to=toValue, step=self.step, value=value, callback=lambda a, b, c: self.updateFilteredImage()) slider.grid(row=0, column=col, padx=5, pady=5) return slider
[docs] def updateFilteredImage(self): self._computeRightPreview() self.rightPreview.updateData(self.rightImage.getData())
def _computeRightPreview(self): """ This function should compute the right preview using the self.lastObj that was selected """ emlib.bandPassFilter(self.rightImage, emlib.image.ImageHandler.locationToXmipp(self.lastObj), self.getLowFreq(), self.getHighFreq(), self.getFreqDecay(), self.dim)
[docs] def getLowFreq(self): if self.showLowFreq: if self.unit == emcts.UNIT_ANGSTROM: return self.samplingRate / self.lfSlider.get() else: return self.lfSlider.get() return 0.01
[docs] def getHighFreq(self): if self.showHighFreq: if self.unit == emcts.UNIT_ANGSTROM: return self.samplingRate / self.hfSlider.get() else: return self.hfSlider.get() return 1.0
[docs] def getFreqDecay(self): if self.showDecay: if self.unit == emcts.UNIT_ANGSTROM: return self.samplingRate / self.freqDecaySlider.get() else: return self.freqDecaySlider.get() return 0.01
[docs]class GaussianFilterDialog(BandPassFilterDialog): def _createControls(self, frame): self.freqVar = tk.StringVar() self.freqVar.set(getattr(self, 'freqSigma', 1)) freqFrame = tk.Frame(frame) freqFrame.grid(row=0, column=0, sticky='nw') downEntry = tk.Entry(freqFrame, width=10, textvariable=self.freqVar) downEntry.grid(row=0, column=1, padx=5, pady=5) downButton = tk.Button(freqFrame, text='Preview', command=self._doPreview) downButton.grid(row=0, column=2, padx=5, pady=5)
[docs] def getFreqSigma(self): return float(self.freqVar.get())
def _computeRightPreview(self): """ This function should compute the right preview using the self.lastObj that was selected """ emlib.gaussianFilter(self.rightImage, emlib.image.ImageHandler.locationToXmipp(self.lastObj), self.getFreqSigma(), self.dim)
[docs]class MaskPreviewDialog(ImagePreviewDialog): def _beforePreview(self): self.dim = 256 self.unit = getattr(self, 'unit', emcts.UNIT_PIXEL) self.samplingRate = self.firstItem.getSamplingRate() self.dim_par = self.firstItem.getDim()[0] self.ratio = self.dim / float(self.dim_par) self.previewLabel = 'Central slice' def _createPreview(self, frame): """ Should be implemented by subclasses to create the items preview. """ # Insert the corresponding explanation text self.expText.updateExpText(emcts.CIRCLE_MASK_WIZ_MSG) from pyworkflow.gui.matplotlib_image import MaskPreview if self.maskRadius == -1: self.iniRadius = self.dim_par / 2 else: self.iniRadius = self.maskRadius if self.unit == emcts.UNIT_ANGSTROM: self.iniRadius = self.iniRadius / self.samplingRate listeners = {"<Button-4>": self.makeBigger, "<Button-5>": self.makeSmaller} self.preview = MaskPreview(frame, self.dim, label=self.previewLabel, listenersDict=listeners) self.preview.grid(row=1, column=0)
[docs] def makeBigger(self, event): new_val = self.iniRadius + 1 if new_val <= self.dim_par / 2: # Don't make the mask bigger unless the is equal or lower than the max self.iniRadius = self.iniRadius + 1 self.manageMaskVals()
[docs] def makeSmaller(self, event): new_val = self.iniRadius - 1 if new_val >= 1: # Don't make the mask smaller unless the radius is equal or greater than 1 self.iniRadius = self.iniRadius - 1 self.manageMaskVals()
[docs] def manageMaskVals(self): # Set the ring slider in case it comes from the mouse wheel self.setRadius(self.radiusSlider, self.iniRadius) # Show values self.showValues(self.hfVar, self.radiusSlider) # Update mask self.preview.updateMask(self.iniRadius * self.ratio)
def _createControls(self, frame): self.addRadiusBox(frame) self.hfVar = tk.StringVar() self.hfLabel = tk.Label(frame, textvariable=self.hfVar, width=20) self.hfLabel.grid(row=0, column=1, sticky='NSE', padx=5, pady=5) self.manageMaskVals()
[docs] def addRadiusBox(self, parent): self.radiusSlider = LabelSlider(parent, 'Mask radius', from_=1, to=int(self.dim_par / 2), value=self.iniRadius, step=1, length=150, showvalue=0, callback=lambda a, b, c: self.updateSliderRadius()) self.radiusSlider.grid(row=0, column=0, sticky='NEWS')
[docs] def updateSliderRadius(self): new_val = self.getRadius(self.radiusSlider) # Carry out the action only if the slider is clicked & dragged (prevent from minimal variations when the mouse # is on the slider but not clicking on it) if abs(new_val - self.iniRadius) >= 1: self.iniRadius = new_val self.manageMaskVals()
[docs] def showValues(self, var2set, radiusSlider): """ Show the values selected for the inner and outer radius. If the units are angstroms (sampling_rate = 1, it will show only one value to avoid redundancies """ pixVal = self.getRadius(radiusSlider) if self.samplingRate == 1: var2set.set('{:6.1f} {}'.format(pixVal, emcts.UNIT_PIXEL)) else: var2set.set('{:5.0f} pix | {:6.1f} Å'.format(pixVal, self.getRadiusAngstroms(radiusSlider), ))
[docs] @staticmethod def getRadius(radiusSlider): return radiusSlider.get()
[docs] @staticmethod def setRadius(radiusSlider, val): radiusSlider.slider.set(val)
[docs] def getRadiusAngstroms(self, radiusSlider): return convertPixToLength(self.samplingRate, radiusSlider.get())
[docs]class MaskRadiiPreviewDialog(MaskPreviewDialog): def _createPreview(self, frame): """ Should be implemented by subclasses to create the items preview. """ # Insert the corresponding explanation text self.expText.updateExpText(emcts.RING_MASK_WIZ_MSG) from pyworkflow.gui.matplotlib_image import MaskPreview if self.innerRadius is None: self.innerRadius = 1 if self.outerRadius is None or self.outerRadius == -1 or self.outerRadius > self.dim_par / 2: self.outerRadius = int(self.dim_par / 2) if self.unit == emcts.UNIT_ANGSTROM: self.innerRadius = self.innerRadius / self.samplingRate self.outerRadius = self.innerRadius / self.samplingRate listeners = {"<Button-4>": self.makeBigger, "<Button-5>": self.makeSmaller, "<Up>": self.upKeyPress, "<Down>": self.downKeyPress} self.preview = MaskPreview(frame, self.dim, label=self.previewLabel, listenersDict=listeners) self.preview.grid(row=1, column=0)
[docs] def upKeyPress(self, event): self.isInnerRad = False self.highlightOuterSlider()
[docs] def downKeyPress(self, event): self.isInnerRad = True self.highlightInnerSlider()
[docs] def highlightOuterSlider(self): self.radiusSliderOut.highlightLabel() self.radiusSliderIn.removeHighlightFromLabel()
[docs] def highlightInnerSlider(self): self.radiusSliderIn.highlightLabel() self.radiusSliderOut.removeHighlightFromLabel()
[docs] def makeBigger(self, event): self.isMakingBigger = True if self.isInnerRad: self.innerRadius = self.innerRadius + self.step else: new_val = self.outerRadius + self.step if new_val <= int(self.dim_par / 2): # Don't make the mask bigger unless the is equal or lower than the max self.outerRadius = new_val self.manageMaskVals()
[docs] def makeSmaller(self, event): self.isMakingBigger = False if self.isInnerRad: new_val = self.innerRadius - self.step if new_val >= 0: self.innerRadius = new_val else: self.outerRadius = self.outerRadius - self.step self.manageMaskVals()
[docs] def manageMaskVals(self): if self.isMakingBigger: if self.isInnerRad and self.innerRadius >= self.outerRadius: # Inner ring can't be bigger than outer ring # Subtract one step to go back to the nearest lower value self.innerRadius = self.outerRadius - self.step else: if not self.isInnerRad and self.outerRadius <= self.innerRadius: # Outer ring can't be smaller than inner # ring # Add one step to go back to the nearest higher value self.outerRadius = self.innerRadius + self.step # Set the ring sliders in case it comes from the mouse wheel self.setRadius(self.radiusSliderIn, self.innerRadius) self.setRadius(self.radiusSliderOut, self.outerRadius) # Show values self.showValues(self.orVar, self.radiusSliderOut) self.showValues(self.irVar, self.radiusSliderIn) # Update mask self.preview.updateMask(self.outerRadius * self.ratio, self.innerRadius * self.ratio)
def _createControls(self, frame): self.step = 1 to = int(self.dim_par / 2) self.radiusSliderOut = LabelSlider(frame, 'Outer radius', from_=1, to=to, value=self.outerRadius, step=self.step, callback=lambda a, b, c: self.updateSliderOuterRadius(), length=150, showvalue=0) self.radiusSliderOut.highlightLabel() self.orVar = tk.StringVar() self.orLabel = tk.Label(frame, textvariable=self.orVar, width=20) self.radiusSliderIn = LabelSlider(frame, 'Inner radius', from_=1, to=to, value=self.innerRadius, step=self.step, callback=lambda a, b, c: self.updateSliderInnerRadius(), length=150, showvalue=0) self.irVar = tk.StringVar() self.irLabel = tk.Label(frame, textvariable=self.irVar, width=20) # Pack and configure outer radius slider self.radiusSliderOut.grid(row=0, column=0, sticky='NEWS') self.radiusSliderOut.columnconfigure(0, weight=1) self.orLabel.grid(row=0, column=1, sticky='NSE', padx=5, pady=5) self.orLabel.columnconfigure(0, weight=1) # Pack and configure inner radius slider self.radiusSliderIn.grid(row=1, column=0, sticky='NEWS') self.radiusSliderIn.columnconfigure(0, weight=1) self.irLabel.grid(row=1, column=1, sticky='NSE', padx=5, pady=5) self.irLabel.columnconfigure(0, weight=1) # Update both mask and sliders with the initial values self.manageMaskVals()
[docs] def updateSliderOuterRadius(self): new_val = self.getRadius(self.radiusSliderOut) # Carry out the action only if the slider is clicked & dragged (prevent from minimal variations when the mouse # is on the slider but not clicking on it) if abs(new_val - self.outerRadius) >= self.step: self.highlightOuterSlider() self.isInnerRad = False self.isMakingBigger = False # Check if the user is making the ring bigger if new_val > self.outerRadius: self.isMakingBigger = True self.outerRadius = new_val self.manageMaskVals()
[docs] def updateSliderInnerRadius(self): new_val = self.getRadius(self.radiusSliderIn) # Highlight only if the slider is clicked & dragged (prevent from minimal variations when the mouse is on the # slider but not clicking on it) if abs(new_val - self.innerRadius) >= self.step: self.highlightInnerSlider() self.isInnerRad = True self.isMakingBigger = False # Check if the user is making the ring bigger if new_val > self.innerRadius: self.isMakingBigger = True self.innerRadius = new_val self.manageMaskVals()
[docs]class ColorScaleDialog(dialog.Dialog): """ This will assist users to choose the color scale and range for local resolution viewers """ def __init__(self, parentWindow, lowest, highest, intervals, colorPalette): """ :param highest: highest resolution value for the scale :param lowest: lowest resolution value for the scale :param intervals: number of labels for the scale :param colorPalette: color palette to use """ self.highest = tk.DoubleVar(value=highest) self.lowest = tk.DoubleVar(value=lowest) self.intervals = tk.IntVar(value=intervals) self.colorPalette = tk.StringVar(value=colorPalette) self.info = tk.StringVar() # GUI attributes initialization self.palette = None self.params = None dialog.Dialog.__init__(self, parentWindow, "Color scale wizard", default="None") # Getters
[docs] def getIntervals(self): return int(self.intervals.get())
[docs] def getHighest(self): return float(self.highest.get())
[docs] def getLowest(self): return float(self.lowest.get())
[docs] def getColorPalette(self): return self.colorPalette.get()
### ----- GUI methods ------ ###
[docs] def body(self, master): """ Draws the main frame of the dialog""" body = tk.Frame(self) body.grid(row=0, column=0, sticky="news") body.grid_columnconfigure(1, weight=10) # GUI attributes self.palette = tk.Frame(body) self.palette.grid(row=0, column=0, sticky='nes', padx=5, pady=5) # Params self.params = tk.Frame(body) self.params.grid(row=0, column=1, sticky='news', padx=5, pady=5) self.params.bind("<Key>", self._keyPressedOnParams) self._drawPalette() self._fillParams()
def _drawLabel(self, color, value, count): label = tk.Label(self.palette, text=value) label.config(bg=color) label.grid(row=count, column=0, sticky='news') def _fillParams(self): # Add highest scale value highValueLabel = tk.Label(self.params, text="Highest:") highValueLabel.grid(row=0, column=0, sticky="e") self._addEntry(0, 1, self.highest) # Add lowest value lowValueLabel = tk.Label(self.params, text="Lowest:") lowValueLabel.grid(row=1, column=0, sticky="e") self._addEntry(1, 1, self.lowest) intervalsLabel = tk.Label(self.params, text="Intervals:") intervalsLabel.grid(row=2, column=0, sticky="e") self._addEntry(2, 1, self.intervals) # Palettes paletteLabel = tk.Label(self.params, text="Color set:") paletteLabel.grid(row=3, column=0, sticky="e") availablePalettes = self.getAvailablePalettes() opt = ttk.Combobox(self.params, textvariable=self.colorPalette, values=availablePalettes) opt.grid(row=3, column=1, sticky="news") self.colorPalette.trace("w", self._paletteChanged) # Label to store information: number of color of the color map, e.g. infoLabel = tk.Label(self.params, textvariable=self.info, text="") infoLabel.grid(row=4, column=0, columnspan=2, sticky="news") def _addEntry(self, row, column, var): newEntry = tk.Entry(self.params, textvariable=var) newEntry.grid(row=row, column=column, sticky="news") newEntry.lift() # Bind events newEntry.bind("<Return>", self._paramChanged) newEntry.bind("<FocusIn>", self._selectAllText) newEntry.bind("<Button-1>", self._selectAllText) newEntry.bind("<FocusOut>", self._paramChanged) def _paletteChanged(self, *args): self._drawPalette() info = "%s has %s colors." % (self.getColorPalette(), cm.get_cmap(self.getColorPalette()).N) self.info.set(info) def _drawPalette(self): """ Draws the palette using current values: palette, highest, lowest and intervals""" # clean first for widget in self.palette.winfo_children(): widget.destroy() # get the colors colorList = getHexColorList(self.getIntervals(), colorName=self.getColorPalette()) # Get the label values labelValues = splitRange(self.getLowest(), self.getHighest(), self.getIntervals()) # Draw it count = 0 for color, value in zip(colorList, labelValues): self._drawLabel(color, value, count) count = count + 1 ### Events handling def _paramChanged(self, event): # Handles change in palette Option self._drawPalette() def _keyPressedOnParams(self, event): # handles keypress on params frame print("PASA key press") self.colorPalette.set("jet")
[docs] def getAvailablePalettes(self): """ Returns a list of all available palettes""" return list(cm.cmap_d.keys())
def _selectAllText(self, event): """ Select all the text of the widget that triggered the event""" # select text event.widget.select_range(0, 'end') # move cursor to the end event.widget.icursor('end')
# stop propagation # return 'break'
[docs]class FormulaDialog(dialog.Dialog): """ This will assist users to create a formula based on class attibutes. """ def __init__(self, parentWindow, set, formula=""): """ :param set: set with the items to use in the formulae :param formula: initial formulate to load """ self.set = set self.item = set.getFirstItem() self.formula = tk.StringVar(value=formula) self.formula.trace("w", self.evaluateFormula) self.formulaTxt = None # To be instantiated later. self.info = tk.StringVar() dialog.Dialog.__init__(self, parentWindow, "Formula wizard", default="None") # Getters
[docs] def getFormula(self): return self.formula.get()
[docs] def getItem(self): return self.item
### ----- GUI methods ------ ### def _createTree(self, parent): provider = AttributesTreeProvider(self.item) self.tree = BoundTree(parent, provider) self.tree.itemDoubleClick = self.addAttributeToFormula self.tree.grid(row=1, column=0)
[docs] def addAttributeToFormula(self, event): attr = self.tree.getSelectedObjects()[0] self._insertText("item." + attr.attrName , self.formulaTxt)
[docs] def body(self, master): """ Draws the main frame of the dialog""" body = tk.Frame(self) body.grid(row=0, column=0, sticky="news") body.grid_columnconfigure(1, weight=10) # GUI attributes self.attributes = tk.Frame(body) self.attributes.grid(row=0, column=0, sticky='nes', padx=5, pady=5) # Formula self.formulaFrame = tk.Frame(body) self.formulaFrame.grid(row=0, column=1, sticky='news', padx=5, pady=5) #self.params.bind("<Key>", self._keyPressedOnParams) self._fillParams()
def _fillParams(self): # Add the formula text formulaLbl = tk.Label(self.formulaFrame, text="Formula:") formulaLbl.grid(row=0, column=0, sticky="w") self.formulaTxt = self._addEntry(1, 0, self.formula, width=100) self._createTree(self.attributes) # Label to show formula result or error infoLabel = tk.Label(self.formulaFrame, textvariable=self.info) infoLabel.grid(row=2, column=0, columnspan=2, sticky="news") def _addEntry(self, row, column, var, width=None): newEntry = tk.Entry(self.formulaFrame, textvariable=var, width=width) newEntry.grid(row=row, column=column, sticky="news") newEntry.lift() # Bind events newEntry.bind("<Return>", self._paramChanged) newEntry.bind("<FocusOut>", self._paramChanged) return newEntry
[docs] def evaluateFormula(self, name='', index='', mode=''): try: item = self.item result = eval(self.formula.get()) self.info.set(result) except Exception as e: self.info.set(str(e))
### Events handling def _paramChanged(self, event): # Handles change in palette Option self.evaluateFormula() def _selectAllText(self, event): """ Select all the text of the widget that triggered the event""" # select text event.widget.select_range(0, 'end') # move cursor to the end event.widget.icursor('end') # stop propagation # return 'break' def _insertText(self, text, widget): """ Insert the passed text in the entry at current cursor location""" # Get inserting position pos = widget.index(tk.INSERT) # Insert he text widget.insert(pos, text)
[docs]def insertText (target, textToInsert, position): """ Inserts a text into another at a position :param target: text to do the insertion on :param textToInsert: text to be inserted :param position: position where to insert the new text""" return target[:position] + textToInsert + target[position:]