Source code for tomo.viewers.views_tkinter_tree

# **************************************************************************
# *
# * Authors:     J.M. De la Rosa Trevin (delarosatrevin@scilifelab.se) [1]
# *
# * [1] SciLifeLab, Stockholm University
# *
# * 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 2 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'
# *
# **************************************************************************

import glob
import threading
from tkinter import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

from pwem.viewers import showj
from pwem.viewers.showj import runJavaIJapp
from pyworkflow.gui import *
from pyworkflow.gui.tree import TreeProvider
from pyworkflow.gui.dialog import ListDialog, ToolbarListDialog, showInfo
import pyworkflow.config as conf
import pyworkflow.viewer as pwviewer
import pyworkflow.utils as pwutils
from pyworkflow.plugin import Domain

import tomo.objects
from ..convert.convert import getMeshVolFileName


[docs]class TiltSeriesTreeProvider(TreeProvider): """ Model class that will retrieve the information from TiltSeries and prepare the columns/rows models required by the TreeDialog GUI. """ COL_TS = 'Tilt series' COL_TI = 'Path' COL_TI_ANGLE = 'Tilt angle' COL_TI_ACQ_ORDER = 'Order' COL_TI_DEFOCUS_U = 'Defocus U (A)' COL_TI_DOSE = "Accum. dose" ORDER_DICT = {COL_TI_ANGLE: '_tiltAngle', COL_TI_DEFOCUS_U: '_ctfModel._defocusU', COL_TI_DOSE: '_acquisition._accumDose'} def __init__(self, protocol, tiltSeries): self.protocol = protocol self.tiltseries = tiltSeries self._hasCtf = tiltSeries.getFirstItem().getFirstItem().hasCTF() TreeProvider.__init__(self, sortingColumnName=self.COL_TS) self.selectedDict = {} self.mapper = protocol.mapper self.maxNum = 200
[docs] def getObjects(self): # Retrieve all objects of type className objects = [] orderBy = self.ORDER_DICT.get(self.getSortingColumnName(), 'id') direction = 'ASC' if self.isSortingAscending() else 'DESC' for ts in self.tiltseries: tsObj = ts.clone(ignoreAttrs=['_mapperPath']) tsObj._allowsSelection = True tsObj._parentObject = None objects.append(tsObj) for ti in ts.iterItems(orderBy=orderBy, direction=direction): tiObj = ti.clone() tiObj._allowsSelection = False tiObj._parentObject = tsObj objects.append(tiObj) return objects
def _sortObjects(self, objects): pass
[docs] def objectKey(self, pobj): pass
[docs] def getColumns(self): cols = [ (self.COL_TS, 100), (self.COL_TI_ACQ_ORDER, 100), (self.COL_TI_ANGLE, 100), (self.COL_TI_DOSE, 100), (self.COL_TI, 400), ] if self._hasCtf: cols.insert(3, (self.COL_TI_DEFOCUS_U, 80)) return cols
[docs] def isSelected(self, obj): """ Check if an object is selected or not. """ return False
@staticmethod def _getParentObject(pobj, default=None): return getattr(pobj, '_parentObject', default)
[docs] def getObjectInfo(self, obj): objId = obj.getObjId() tsId = obj.getTsId() if isinstance(obj, tomo.objects.TiltSeriesBase): key = objId text = tsId values = ['', '', '', str(obj)] opened = True else: # TiltImageBase key = '%s.%s' % (tsId, objId) text = objId dose = obj.getAcquisition().getAccumDose() if hasattr(obj.getAcquisition(), '_accumDose') else None adqOrder = obj.getAcquisitionOrder() if hasattr(obj, '_acqOrder') else None values = [str("%d" % adqOrder) if adqOrder is not None else "", str("%0.2f" % obj.getTiltAngle()), round(dose, 2) if dose is not None else "", "%d@%s" % (obj.getLocation()[0] or 1, obj.getLocation()[1])] if self._hasCtf: values.insert(2, "%d" % obj.getCTF().getDefocusU()) opened = False return { 'key': key, 'text': text, 'values': tuple(values), 'open': opened, 'selected': False, 'parent': obj._parentObject }
[docs] def getObjectActions(self, obj): actions = [] if isinstance(obj, tomo.objects.TiltSeries): viewers = Domain.findViewers(obj.getClassName(), pwviewer.DESKTOP_TKINTER) for viewerClass in viewers: def createViewer(viewerClass, obj): proj = self.protocol.getProject() item = self.tiltseries[obj.getObjId()] # to load mapper return lambda: viewerClass(project=proj).visualize(item) actions.append(('Open with %s' % viewerClass.__name__, createViewer(viewerClass, obj))) return actions
[docs]class TiltSeriesDialogView(pwviewer.View): """ This class implements a view using Tkinter ListDialog and the TiltSeriesTreeProvider. """ def __init__(self, parent, protocol, tiltSeries, **kwargs): """ Params: parent: Tkinter parent widget From kwargs: message: message tooltip to show when browsing. selected: the item that should be selected. validateSelectionCallback: a callback function to validate selected items. allowSelect: if set to False, the 'Select' button will not be shown. allowsEmptySelection: if set to True, it will not validate that at least one element was selected. """ self._tkParent = parent self._protocol = protocol self._provider = TiltSeriesTreeProvider(protocol, tiltSeries)
[docs] def show(self): dlg = ListDialog(self._tkParent, 'TiltSeries display', self._provider, allowSelect=False, cancelButton=True)
[docs]class TomogramsTreeProvider(TreeProvider): """ Populate Tree from SetOfTomograms. """ def __init__(self, tomoList, path, mode): TreeProvider.__init__(self) self.tomoList = tomoList self._path = path self._mode = mode
[docs] def getColumns(self): return [('Tomogram', 300), ("# coords", 100), ('status', 150)]
[docs] def getObjectInfo(self, tomo): if self._mode == 'txt': tomogramName = os.path.basename(tomo.getFileName()) tomogramName = os.path.splitext(tomogramName)[0] filePath = os.path.join(self._path, tomogramName + ".txt") elif self._mode == 'json': tomogramName = os.path.basename(tomo.getFileName()) tomogramName = os.path.splitext(tomogramName)[0] outFile = '*%s_info.json' % pwutils.removeBaseExt(tomogramName.split("__")[0]) pattern = os.path.join(self._path, outFile) files = glob.glob(pattern) filePath = '' if files: filePath = files[0] if not os.path.isfile(filePath): return {'key': tomogramName, 'parent': None, 'text': tomogramName, 'values': (tomo.count, "TODO"), 'tags': ("pending")} else: return {'key': tomogramName, 'parent': None, 'text': tomogramName, 'values': (tomo.count, "DONE"), 'tags': ("done")}
[docs] def getObjectPreview(self, obj): return (None, None)
[docs] def getObjectActions(self, obj): return []
def _getObjectList(self): """Retrieve the object list""" return self.tomoList
[docs] def getObjects(self): objList = self._getObjectList() return objList
[docs] def configureTags(self, tree): tree.tag_configure("pending", foreground="red") tree.tag_configure("done", foreground="green")
[docs]class MeshesTreeProvider(TreeProvider): """Populate tree from SetOfMeshes""" def __init__(self, meshList): TreeProvider.__init__(self) self._parentDict = {} self.meshList = meshList self.tomoList = [] self.tomo_names = set() id = 1 for mesh in self.meshList: tomo = mesh.getVolume() if tomo.getFileName() not in self.tomo_names: tomo.setObjId(id) self.tomoList.append(tomo) self.tomo_names.add(tomo.getFileName()) id += 1 self.tomo_names = sorted(list(self.tomo_names), reverse=False) self.tomoList.sort(key=lambda x: x.getFileName(), reverse=False)
[docs] def getColumns(self): return [('Tomogram', 300), ('Number of Meshes', 150)]
[docs] def getObjectInfo(self, obj): if isinstance(obj, tomo.objects.MeshPoint): meshName = 'Mesh %d' % obj.getObjId() tomoName = pwutils.removeBaseExt(obj.getVolume().getFileName()) return {'key': tomoName + '-' + str(obj.getObjId()), 'parent': self._parentDict.get(obj.getObjId(), None), 'text': meshName, 'values': ('')} elif isinstance(obj, tomo.objects.Tomogram): tomoName = pwutils.removeBaseExt(obj.getFileName()) numMeshes = 0 for mesh in self.meshList: if mesh.getVolume().getFileName() == obj.getFileName(): numMeshes += 1 return {'key': tomoName, 'parent': None, 'text': tomoName, 'values': (numMeshes)}
[docs] def getObjectActions(self, mesh): return []
def _getObjectList(self): """Retrieve the object list""" return self.tomoList
[docs] def getObjects(self): objList = self._getObjectList() self._parentDict = {} childs = [] for obj in self.meshList: childs += self._getChilds(obj) objList += childs return objList
def _getChilds(self, obj): childs = [] childs.append(obj) for idx in range(len(self.tomo_names)): if obj.getVolume().getFileName() == self.tomo_names[idx]: self._parentDict[obj.getObjId()] = self.tomoList[idx] return childs
[docs]class TomogramsDialog(ToolbarListDialog): """ This class extend from ListDialog to allow calling an ImageJ subprocess from a list of Tomograms. """ def __init__(self, parent, viewer, **kwargs): self.path = kwargs.get("path", None) self.provider = kwargs.get("provider", None) if viewer: ToolbarListDialog.__init__(self, parent, "Tomogram List", allowsEmptySelection=False, itemDoubleClick=self.doubleClickViewer, allowSelect=False, **kwargs) else: ToolbarListDialog.__init__(self, parent, "Tomogram List", allowsEmptySelection=False, itemDoubleClick=self.doubleClickOnTomogram, allowSelect=False, **kwargs)
[docs] def refresh_gui_viewer(self): if self.proc.is_alive(): self.after(1000, self.refresh_gui_viewer) else: meshFile = os.path.join(self.path, pwutils.removeBaseExt(self.tomo.getFileName()) + '.txt') self.tomo.count = np.loadtxt(meshFile, delimiter=',').shape[0] pwutils.cleanPath(self.macroPath) self.tree.update()
[docs] def refresh_gui(self): self.tree.update() if self.proc.is_alive(): self.after(1000, self.refresh_gui) else: meshFile = os.path.join(self.path, pwutils.removeBaseExt(self.tomo.getFileName()) + '.txt') self.tomo.count = np.loadtxt(meshFile, delimiter=',').shape[0] pwutils.cleanPath(self.macroPath) self.tree.update()
[docs] def doubleClickOnTomogram(self, e=None): self.tomo = e self.proc = threading.Thread(target=self.lanchIJForTomogram, args=(self.path, self.tomo,)) self.proc.start() self.after(1000, self.refresh_gui)
[docs] def doubleClickViewer(self, e=None): self.tomo = e self.proc = threading.Thread(target=self.lanchIJForViewing, args=(self.path, self.tomo,)) self.proc.start() self.after(1000, self.refresh_gui_viewer)
[docs] def lanchIJForTomogram(self, path, tomogram): self.macroPath = os.path.join(self.path, "AutoSave_ROI.ijm") tomogramFile = tomogram.getFileName() tomogramName = os.path.basename(tomogramFile) macro = r"""path = "%s"; file = "%s" // --------- Initialize Roi Manager --------- roiManager("Draw"); setTool("polygon"); newClass = "Yes"; outPath = path + file + ".txt"; // --------- Load SetOfMeshes --------- if (File.exists(outPath)){ group = 0; groups = loadMeshFile(outPath); numMeshes = roiManager("count"); emptyOutFile(outPath); aux = editMeshes(groups, numMeshes, outPath); group = group + aux + 1; } else{ emptyOutFile(outPath); group = 1; } // --------- Draw new Meshes and save them --------- while (newClass == "Yes") { roiManager("Reset"); //group = classDialog(); waitForRoi(); saveMeshes(group, outPath); newClass = newClassDialog(); group = group + 1; } // --------- Close ImageJ --------- run("Quit"); // --------- Functions Definition --------- function classDialog(){ Dialog.create("Class Selection"); Dialog.addMessage("Determine the group of the labels to be drawn"); Dialog.addNumber("Class Group", 1); Dialog.show(); return floor(Dialog.getNumber()); } function newClassDialog(){ choices = newArray("Yes", "No"); Dialog.create("Create new class?"); Dialog.addChoice("Choice", choices); Dialog.show(); return Dialog.getChoice(); } function waitForRoi(){ waitForUser("Draw the desired ROIs\n\nThen click Ok"); wait(50); while(roiManager("count")==0){ waitForUser("Draw the desired ROIs\n\nThen click Ok"); wait(50); } } function emptyOutFile(outPath){ fid = File.open(outPath); File.close(fid); } function saveMeshes(class, outPath){ string = ""; meshes = roiManager("count"); for (i=0; i<meshes; i++){ roiManager("select", i); Stack.getPosition(channel, slice, frame); getSelectionCoordinates(xpoints, ypoints); for (j = 0; j < xpoints.length; j++) { string = string + "" + xpoints[j] + "," + ypoints[j] + "," + slice + "," + class + "\n"; } } lastJump = lastIndexOf(string, "\n"); File.append(substring(string, 0, lastJump), outPath); } function loadMeshFile(meshPath){ c = ""; c = c + toHex(255*random); c = c + toHex(255*random); c = c + toHex(255*random); contents = split(File.openAsString(meshPath), "\n"); xpoints = newArray(); ypoints = newArray(); groups = newArray(); for (idx=0; idx < contents.length; idx++){ values = split(contents[idx], ","); if (idx+1 < contents.length){ valuesNext = split(contents[idx+1], ","); } else{ valuesNext = newArray(-1,-1,-1,-1); } xpoints = Array.concat(xpoints, values[0]); ypoints = Array.concat(ypoints, values[1]); if (values[2] != valuesNext[2]){ xpoints = Array.concat(xpoints, xpoints[0]); ypoints = Array.concat(ypoints, ypoints[0]); groups = Array.concat(groups, values[3]); Stack.setSlice(values[2]); makeSelection("polyline", xpoints, ypoints); Roi.setName("Class " + values[3]); Roi.setStrokeWidth(5); roiManager("add"); count = roiManager("count"); roiManager("select", count-1); roiManager("Set Color", c); if (values[3] != valuesNext[3]){ c = ""; c = c + toHex(255*random); c = c + toHex(255*random); c = c + toHex(255*random); } xpoints = newArray(); ypoints = newArray(); } } return groups; } function editMeshes(classVect, numMeshes, outPath){ waitForUser("Edit the input ROIs if needed\n\nThen click Ok"); string = ""; for (i=0; i<numMeshes; i++){ roiManager("select", i); Stack.getPosition(channel, slice, frame); getSelectionCoordinates(xpoints, ypoints); for (j = 0; j < xpoints.length; j++) { string = string + "" + xpoints[j] + "," + ypoints[j] + "," + slice + "," + classVect[i] + "\n"; groupEnd = classVect[i]; } } lastJump = lastIndexOf(string, "\n"); File.append(substring(string, 0, lastJump), outPath); return groupEnd; } """ % (os.path.join(path, ''), os.path.splitext(tomogramName)[0]) macroFid = open(self.macroPath, 'w') macroFid.write(macro) macroFid.close() args = "-i %s -macro %s" % (tomogramFile, self.macroPath) viewParams = {showj.ZOOM: 50} for key, value in viewParams.items(): args = "%s --%s %s" % (args, key, value) app = "xmipp.ij.commons.XmippImageJ" runJavaIJapp(4, app, args).wait()
[docs] def lanchIJForViewing(self, path, tomogram): self.macroPath = os.path.join(self.path, "View_ROI.ijm") tomogramFile = tomogram.getFileName() tomogramName = os.path.basename(tomogramFile) meshFile = getMeshVolFileName(self.tomo.getObjId()) macro = r"""path = "%s"; file = "%s" meshFile = "%s" outPath = path + meshFile; // --------- Load SetOfMeshes --------- if (File.exists(outPath)){ loadMeshFile(outPath); } // --------- Functions Definition --------- function loadMeshFile(meshPath){ c = ""; c = c + toHex(255*random); c = c + toHex(255*random); c = c + toHex(255*random); contents = split(File.openAsString(meshPath), "\n"); xpoints = newArray(); ypoints = newArray(); groups = newArray(); for (idx=0; idx < contents.length; idx++){ values = split(contents[idx], ","); if (idx+1 < contents.length){ valuesNext = split(contents[idx+1], ","); } else{ valuesNext = newArray(-1,-1,-1,-1); } xpoints = Array.concat(xpoints, values[0]); ypoints = Array.concat(ypoints, values[1]); if (values[2] != valuesNext[2]){ xpoints = Array.concat(xpoints, xpoints[0]); ypoints = Array.concat(ypoints, ypoints[0]); groups = Array.concat(groups, values[3]); Stack.setSlice(values[2]); makeSelection("polyline", xpoints, ypoints); Roi.setName("Class " + values[3]); Roi.setStrokeWidth(5); roiManager("add"); count = roiManager("count"); roiManager("select", count-1); roiManager("Set Color", c); if (values[3] != valuesNext[3]){ c = ""; c = c + toHex(255*random); c = c + toHex(255*random); c = c + toHex(255*random); } xpoints = newArray(); ypoints = newArray(); } } return groups; } """ % (os.path.join(path, ''), os.path.splitext(tomogramName)[0], meshFile) macroFid = open(self.macroPath, 'w') macroFid.write(macro) macroFid.close() args = "-i %s -macro %s" % (tomogramFile, self.macroPath) viewParams = {showj.ZOOM: 50} for key, value in viewParams.items(): args = "%s --%s %s" % (args, key, value) app = "xmipp.ij.commons.XmippImageJ" runJavaIJapp(4, app, args).wait()
[docs]class CTFSerieStates: UNCHECKED = 'unchecked' CHECKED = 'checked' ODD = 'odd' EVEN = 'even' FAILED = 'Failed' OK = 'Ok'
[docs]class CtfEstimationTreeProvider(TreeProvider, ttk.Treeview): """ Model class that will retrieve the information from SetOfCTFTomoSeries and prepare the columns/rows models required by the TreeDialog GUI. """ COL_CTF_SERIE = 'Tilt Series' COL_TILT_ANG = 'Tilt Angle' CRITERIA_1 = 'Status' COL_CTF_EST_DEFOCUS_U = 'DefocusU (A)' COL_CTF_EST_DEFOCUS_V = 'DefocusV (A)' COL_CTF_EST_AST = 'Astigmatism (A)' COL_CTF_EST_RES = 'Resolution (A)' COL_CTF_EST_FIT = 'CC value' COL_CTF_EST_PHASE = 'Phase shift (deg)' ORDER_DICT = {COL_CTF_EST_DEFOCUS_U: '_defocusU', COL_CTF_EST_DEFOCUS_V: '_defocusV', COL_CTF_EST_AST: '_defocusRatio', COL_CTF_EST_RES: '_resolution', COL_CTF_EST_PHASE: '_phaseShift', COL_CTF_EST_FIT: '_fitQuality'} def __init__(self, master, protocol, outputSetOfCTFTomoSeries, **kw): ttk.Treeview.__init__(self, master, **kw) self.protocol = protocol self.ctfSeries = outputSetOfCTFTomoSeries self._hasPhaseShift = outputSetOfCTFTomoSeries.getFirstItem().getFirstItem().hasPhaseShift() TreeProvider.__init__(self, sortingColumnName=self.COL_CTF_SERIE) self.selectedDict = {} self.mapper = protocol.mapper self.maxNum = 200 self._checkedItems = 0
[docs] def getObjects(self): objects = [] orderBy = self.ORDER_DICT.get(self.getSortingColumnName(), 'id') direction = 'ASC' if self.isSortingAscending() else 'DESC' for ctfSerie in self.ctfSeries: ctfEstObj = ctfSerie.clone() ctfEstObj._allowsSelection = True ctfEstObj._parentObject = None objects.append(ctfEstObj) for item in ctfSerie.iterItems(orderBy=orderBy, direction=direction): ctfEstItem = item.clone() ctfEstItem._allowsSelection = False ctfEstItem._parentObject = ctfEstObj objects.append(ctfEstItem) return objects
[docs] def getCTFSeries(self): return self.ctfSeries
def _sortObjects(self, objects): # TODO pass
[docs] def objectKey(self, pobj): pass
[docs] def getColumns(self): cols = [ (self.COL_CTF_SERIE, 100), (self.COL_TILT_ANG, 100), (self.CRITERIA_1, 60), (self.COL_CTF_EST_DEFOCUS_U, 100), (self.COL_CTF_EST_DEFOCUS_V, 100), (self.COL_CTF_EST_AST, 150), (self.COL_CTF_EST_RES, 100), (self.COL_CTF_EST_FIT, 100) ] if self._hasPhaseShift: cols.insert(5, (self.COL_CTF_EST_PHASE, 150)) return cols
[docs] def isSelected(self, obj): """ Check if an object is selected or not. """ return False
@staticmethod def _getParentObject(pobj, default=None): return getattr(pobj, '_parentObject', default)
[docs] def getObjectInfo(self, obj): if isinstance(obj, tomo.objects.CTFTomoSeries): key = obj.getTsId() text = obj.getTsId() # TODO: show avg defocus for TomoSeries values = ['', CTFSerieStates.OK if obj.getIsDefocusUDeviationInRange() else CTFSerieStates.FAILED] opened = False selected = obj.isEnabled() else: # CTFTomo key = "%s.%s" % (obj._parentObject.getTsId(), str(obj.getObjId())) text = obj.getIndex() ts = obj._parentObject.getTiltSeries() tiltAngle = ts[int(text)].getTiltAngle() ast = obj.getDefocusU() - obj.getDefocusV() phSh = obj.getPhaseShift() if obj.hasPhaseShift() else 0 values = [str("%0.2f" % tiltAngle), CTFSerieStates.OK if obj.getIsDefocusUDeviationInRange() else CTFSerieStates.FAILED, str("%d" % obj.getDefocusU()), str("%d" % obj.getDefocusV()), str("%d" % ast), str("%0.1f" % obj.getResolution()) if obj.getResolution() else "-", str("%0.3f" % obj.getFitQuality()) if obj.getFitQuality() else "-"] if self._hasPhaseShift: values.insert(4, str("%0.2f" % phSh)) opened = False selected = False item = { 'key': key, 'text': text, 'values': tuple(values), 'open': opened, 'selected': selected, 'parent': obj._parentObject } if isinstance(obj, tomo.objects.CTFTomoSeries): tags = CTFSerieStates.UNCHECKED if not (obj.getIsDefocusUDeviationInRange() and obj.getIsDefocusVDeviationInRange()): obj.setEnabled(True) tags = CTFSerieStates.CHECKED self._checkedItems += 1 if obj.getObjId() % 2 == 0: item['tags'] = (tags, CTFSerieStates.ODD) else: item['tags'] = (tags, CTFSerieStates.EVEN) else: if obj.getObjId() % 2 == 0: item['tags'] = (CTFSerieStates.ODD,) else: item['tags'] = (CTFSerieStates.EVEN,) return item
[docs]class CTFEstimationTree(BoundTree): def __init__(self, master, provider, **opts): BoundTree.__init__(self, master, provider, **opts) self.selectedItem = None self._checkedItems = provider._checkedItems
[docs] def check_item(self, item): """ check the box of item and change the state of the boxes of item's ancestors accordingly """ tags = CTFSerieStates.EVEN if CTFSerieStates.ODD in self.item(item, 'tags'): tags = CTFSerieStates.ODD if CTFSerieStates.UNCHECKED in self.item(item, 'tags'): self.item(item, tags=(CTFSerieStates.CHECKED, tags,)) self._checkedItems += 1 self.getSelectedObj().setEnabled(False) self.item(item)['selected'] = True else: self.item(item, tags=(CTFSerieStates.UNCHECKED, tags,)) self.getSelectedObj().setEnabled(True) self._checkedItems -= 1 self.item(item)['selected'] = False
def _onClick(self, event=None): self._unpostMenu() x, y, widget = event.x, event.y, event.widget elem = widget.identify("element", x, y) self.selectedItem = self.identify_row(y) self.focus(self.selectedItem) if "image" in elem: # click on the checkbox self.check_item(self.selectedItem)
[docs] def getSelectedItem(self): return self.selectedItem
[docs] def getSelectedObj(self): obj = None if self.selectedItem: obj = self._objDict[self.getFirst()] return obj
[docs]class CtfEstimationListDialog(ListDialog): def __init__(self, parent, title, provider, protocol, inputTS, **kwargs): self._project = protocol.getProject() self._protocol = protocol self._inputSetOfTiltSeries = inputTS self._checkedItems = provider._checkedItems # the funcs below should be implemented in viewers self._show1DPLot = kwargs.pop('plot1Dfunc', None) self._show2DPLot = kwargs.pop('plot2Dfunc', None) ListDialog.__init__(self, parent, title, provider, allowSelect=False, cancelButton=True, **kwargs)
[docs] def body(self, bodyFrame): bodyFrame.config() self._col = 1 self._fillCTFEstimationGUI(bodyFrame)
def _addButton(self, frame, text, image, command, sticky='news', state=tk.NORMAL): btn = tk.Label(frame, text=text, image=self.getImage(image), compound=tk.LEFT, cursor='hand2', state=state) btn.bind('<Button-1>', command) btn.grid(row=0, column=self._col, sticky=sticky, padx=(0, 5), pady=5) self._col += 1 return btn def _fillCTFEstimationGUI(self, bodyFrame): # Create a top panel to put the search box and buttons topPanel = tk.Frame(bodyFrame) topPanel.grid(row=0, column=0, padx=0, pady=0, sticky='news') self._createTopPanel(topPanel) # Create a bottom panel to put the tree and the plotter bottomPanel = tk.Frame(bodyFrame) bottomPanel.grid(row=1, column=0, padx=0, pady=0, sticky='news') self._createBottomPanel(bottomPanel) def _createTopPanel(self, topPanel): self._createFilterBox(topPanel) topRigthPanel = tk.Frame(topPanel) topRigthPanel.grid(row=0, column=1, padx=0, pady=0, sticky='news') self._createSubsetButton(topRigthPanel) if self._show1DPLot is not None: self._createShowFit(topRigthPanel) self._createViewerHelp(topRigthPanel) def _createSubsetButton(self, topRigthPanel): state = tk.NORMAL if self._checkedItems or self._checkedItems == len(self.provider.getCTFSeries()): state = tk.DISABLED self.generateSubsetButton = self._addButton(topRigthPanel, 'Generate subsets', pwutils.Icon.PROCESSING, self._actionCreateSets, sticky='ne', state=state) def _createShowFit(self, topRigthPanel): self.createShowFitButton = self._addButton(topRigthPanel, '1D fit', pwutils.Icon.ACTION_RESULTS, self._show1DFit, state=tk.DISABLED, sticky='ne') def _createViewerHelp(self, topRigthPanel): self._addButton(topRigthPanel, pwutils.Message.LABEL_HELP, pwutils.Icon.ACTION_HELP, self._showHelp, sticky='ne') def _show1DFit(self, event=None): itemSelected = self.tree.getSelectedItem() obj = self.tree.getSelectedObj() if self.tree.parent(itemSelected): # child item if obj is not None: for ctfSerie in self.provider.getCTFSeries(): if ctfSerie.getTsId() in itemSelected: # TODO: sort ctfSerie by id ctfId = int(itemSelected.split('.')[-1]) plot = self._show1DPLot(ctfSerie, ctfId) plot.show() break def _actionCreateSets(self, event=None): if self.generateSubsetButton['state'] == tk.NORMAL: protocol = self.provider.protocol ctfSeries = self.provider.getCTFSeries() suffix = self._getSuffix(protocol) goodCTFName = 'goodCtf%s' % suffix badCTFName = 'badCtf%s' % suffix outputSetOfgoodCTFTomoSeries = ctfSeries.createCopy(protocol._getPath(), prefix=goodCTFName, copyInfo=True) outputSetOfbadCTFTomoSeries = ctfSeries.createCopy(protocol._getPath(), prefix=badCTFName, copyInfo=True) for ctfSerie in ctfSeries: ctfSerieClon = ctfSerie.clone() if CTFSerieStates.UNCHECKED in self.tree.item(ctfSerie.getTsId(), 'tags'): # Adding the ctfSerie to the good set of ctfTomoSeries outputSetOfgoodCTFTomoSeries.append(ctfSerieClon) outputSetOfgoodCTFTomoSeries.setSetOfTiltSeries(self._inputSetOfTiltSeries) else: # Adding the ctfSerie to the bad set of ctfTomoSeries outputSetOfbadCTFTomoSeries.append(ctfSerieClon) outputSetOfbadCTFTomoSeries.setSetOfTiltSeries(self._inputSetOfTiltSeries) for item in ctfSerie.iterItems(): ctfEstItem = item.clone() ctfSerieClon.append(ctfEstItem) outputgoodCTFSetName = 'goodSetOfCTFTomoSeries%s' % suffix outputbadCTFSetName = 'badSetOfCTFTomoSeries%s' % suffix if len(outputSetOfgoodCTFTomoSeries) > 0: protocol._defineOutputs(**{outputgoodCTFSetName: outputSetOfgoodCTFTomoSeries}) if len(outputSetOfbadCTFTomoSeries) > 0: protocol._defineOutputs(**{outputbadCTFSetName: outputSetOfbadCTFTomoSeries}) protocol._store() self.cancel() def _showHelp(self, event=None): showInfo('CTFTomoSeries viewer help', 'This viewer calculates the standard deviation with respect ' 'to the mean of the defocusU and defocusV values. If the ' 'values of the images are not in the 20% range from the average ' 'they are marked as *Failed* and therefore the CTFTomoSerie is ' 'marked as *Failed* as well.\n\n' 'On the other hand, the viewer allows you to create two ' 'subsets of CTFTomoSeries which are classified as good ' 'and bad respectively.\n\n' 'Note: The series that are checked are the ones that ' 'represent the bad CTFTomoSeries', self.parent) def _getSuffix(self, protocol): """ Return the number of the last output in order to complete the new output with a suffix """ maxCounter = -1 pattern = 'goodSetOfCTFTomoSeries' for attrName, _ in protocol.iterOutputAttributes(): suffix = attrName.replace(pattern, '') try: counter = int(suffix) except: counter = 1 # when there is no number, assume 1 maxCounter = max(counter, maxCounter) return str(maxCounter + 1) if maxCounter > 0 else '' def _createBottomPanel(self, bottomPanel): self._createCTFEstimationGUI(bottomPanel) self.initial_focus = self.tree def _createCTFEstimationGUI(self, bottomPanel): # Create a division Paned pw = tk.PanedWindow(bottomPanel, orient=tk.HORIZONTAL) # Create a left panel to put the tree bottomleftPanel = tk.Frame(pw) bottomleftPanel.grid(row=0, column=0, padx=0, pady=0, sticky='news') self._createTree(bottomleftPanel) pw.add(bottomleftPanel) # Create a right panel to put the plotter self.bottomRightPanel = ttk.Frame(pw) self.bottomRightPanel.grid(row=0, column=1, padx=0, pady=0, sticky='news') self._createPloter(self.bottomRightPanel) pw.add(self.bottomRightPanel) pw.pack(fill=BOTH, expand=True) # This method is used to show sash pw.configure(sashrelief=RAISED) def _createTree(self, parent): gui.configureWeigths(parent) self.tree = CTFEstimationTree(parent, self.provider, selectmode=self._selectmode) item = self.tree.identify_row(0) self.tree.selection_set(item) self.tree.focus(item) self.tree.selectedItem = item self.im_checked = gui.getImage(Icon.CHECKED) self.im_unchecked = gui.getImage(Icon.UNCHECKED) self.tree.tag_configure(CTFSerieStates.UNCHECKED, image=self.im_unchecked) self.tree.tag_configure(CTFSerieStates.CHECKED, image=self.im_checked) self.tree.tag_configure(CTFSerieStates.EVEN, background='#F2F2F2', foreground='black') self.tree.tag_configure(CTFSerieStates.ODD, background='#E6E6E6', foreground='black') self.tree.bind("<Button-1>", self._createPloter, True)
[docs] def plotterChildItem(self, itemSelected): plotterPanel = tk.Frame(self.bottomRightPanel) if self._show2DPLot is None: self.plotterParentItem(self.tree.parent(itemSelected)) else: for ctfSerie in self.provider.getCTFSeries(): if ctfSerie.getTsId() in itemSelected: ctfId = int(itemSelected.split('.')[-1]) # TODO: sort ctfSerie by id fig = self._show2DPLot(ctfSerie, ctfId) if fig is None: return canvas = FigureCanvasTkAgg(fig, master=plotterPanel) canvas.draw() canvas.get_tk_widget().pack(fill=BOTH, expand=0) break plotterPanel.grid(row=0, column=1, sticky='news')
[docs] def plotterParentItem(self, itemSelected): plotterPanel = tk.Frame(self.bottomRightPanel) angList = [] defocusUList = [] defocusVList = [] phShList = [] resList = [] for ctfSerie in self.provider.getCTFSeries(): ts = ctfSerie.getTiltSeries() if ctfSerie.getTsId() == itemSelected: for item in ctfSerie.iterItems(orderBy='id'): index = int(item.getIndex()) angList.append(int(ts[index].getTiltAngle())) defocusUList.append(item.getDefocusU()) defocusVList.append(item.getDefocusV()) phShList.append( item.getPhaseShift() if item.hasPhaseShift() else 0) resList.append(item.getResolution()) fig = Figure(figsize=(7, 7), dpi=100) defocusPlot = fig.add_subplot(111) defocusPlot.grid() defocusPlot.set_title(itemSelected) defocusPlot.set_xlabel('Tilt angle') defocusPlot.set_ylabel('DefocusU', color='tab:red') defocusPlot.plot(angList, defocusUList, marker='.', color='tab:red', label='DefocusU (A)') defocusPlot.set_ylabel('DefocusV', color='tab:blue') defocusPlot.plot(angList, defocusVList, marker='.', color='tab:blue', label='DefocusV (A)') if item.hasPhaseShift(): phShPlot = defocusPlot.twinx() phShPlot.set_ylim(0, 180) phShPlot.set_ylabel('Phase shift', color='tab:green') phShPlot.plot(angList, phShList, marker='.', color='tab:green', label='Phase shift (deg)') else: # no phase shift, plot resolution instead resPlot = defocusPlot.twinx() resPlot.set_ylim(0, 30) resPlot.set_ylabel('Resolution', color='tab:green') resPlot.plot(angList, resList, marker='.', color='tab:green', label='Resolution (A)') fig.legend() canvas = FigureCanvasTkAgg(fig, master=plotterPanel) canvas.draw() canvas.get_tk_widget().pack(fill=BOTH, expand=0) plotterPanel.grid(row=0, column=1, sticky='news') break
def _createPloter(self, event): itemSelected = self.tree.getSelectedItem() obj = self.tree.getSelectedObj() self._checkedItems = self.tree._checkedItems if self._checkedItems and self._checkedItems != len(self.provider.getCTFSeries()): self.generateSubsetButton['state'] = tk.NORMAL else: self.generateSubsetButton['state'] = tk.DISABLED if obj is not None: if self.tree.parent(itemSelected): # child item if self._show1DPLot is not None: self.createShowFitButton['state'] = tk.NORMAL self.plotterChildItem(itemSelected) else: # parent item if self._show1DPLot is not None: self.createShowFitButton['state'] = tk.DISABLED self.plotterParentItem(itemSelected)