# **************************************************************************
# *
# * 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]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)