Source code for pwem.viewers.views

# **************************************************************************
# *
# * 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 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, see <https://www.gnu.org/licenses/>.
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
import csv
import os
import re

from pwem.objects import SetOfClasses2D
from pyworkflow.config import Config as pwConfig
from pyworkflow.gui import getDefaultFont, openTextFileEditor

import tkinter as tk
import tkinter.ttk as ttk

import pyworkflow.viewer as pwviewer

from pwem import emlib

from .showj import (getJvmMaxMemory, MODE, VISIBLE, ZOOM, ORDER, RENDER,
                    SORT_BY, runJavaIJapp, MODE_MD, LABELS,
                    launchSupervisedPickerGUI)


[docs]class DataView(pwviewer.View): """ Wrapper the arguments to showj (either web or desktop). Also useful to visualize images that are not objects, e.g.: dark or gain images""" def __init__(self, path, viewParams={}, **kwargs): pwviewer.View.__init__(self) self._memory = getJvmMaxMemory() self._loadPath(path) self._env = kwargs.get('env', {}) self._viewParams = viewParams
[docs] def setMemory(self, memory): self._memory = memory
[docs] def getViewParams(self): """ Give access to the viewParams dict. """ return self._viewParams
def _loadPath(self, path): self._tableName = None # If path is a tuple, we will convert to the filename format # as expected by Showj if isinstance(path, tuple): self._path = emlib.image.ImageHandler.locationToXmipp(path) # Check if there is a table name with @ in path # in that case split table name and path # table names can never starts with a number # this is considering an image inside an stack elif isinstance(path, str): if '@' in path and path[0] not in '0123456789': self._tableName, self._path = path.split('@') else: self._path = path else: raise Exception("Invalid input path, " "should be 'string' or 'tuple'")
[docs] def show(self): runJavaIJapp(self._memory, 'xmipp.viewer.scipion.ScipionViewer', self.getShowJParams(), env=self._env)
[docs] def getShowJParams(self): tableName = '%s@' % self._tableName if self._tableName else '' params = '-i "%s%s"' % (tableName, self._path) for key, value in self._viewParams.items(): params = "%s --%s %s" % (params, key, value) if pwConfig.debugOn(): params += " --debug" return params
[docs] def getShowJWebParams(self): parameters = { MODE, # FOR MODE TABLE OR GALLERY VISIBLE, ZOOM, ORDER, RENDER, SORT_BY } params = {} for key, value in self._viewParams.items(): if key in parameters: if key == 'mode' and value == 'metadata': value = 'table' params[key] = value return params
[docs] def getPath(self): return self._path
[docs] def getTableName(self): return self._tableName
[docs]class ObjectView(DataView): """ Wrapper to DataView but for displaying Scipion objects. """ def __init__(self, project, inputid, path, other='', viewParams={}, **kwargs): DataView.__init__(self, path, viewParams, **kwargs) self.type = type self.port = project.port self.inputid = inputid self.other = other
[docs] def getShowJParams(self): # Add the scipion parameters over the normal showj params return '%s --scipion %s %s %s' % (DataView.getShowJParams(self), self.port, self.inputid, self.other)
[docs] def show(self): runJavaIJapp(self._memory, 'xmipp.viewer.scipion.ScipionViewer', self.getShowJParams(), env=self._env)
[docs]class MicrographsView(ObjectView): """ Customized ObjectView for SetOfCTF objects . """ # All extra labels that we want to show if present in the CTF results RENDER_LABELS = ['thumbnail._filename', 'psdCorr._filename', 'plotGlobal._filename'] EXTRA_LABELS = ['_filename'] def __init__(self, project, micSet, other='', **kwargs): first = micSet.getFirstItem() def existingLabels(labelList): return ' '.join([l for l in labelList if first.hasAttributeExt(l)]) renderLabels = existingLabels(self.RENDER_LABELS) extraLabels = existingLabels(self.EXTRA_LABELS) labels = 'id enabled %s %s' % (renderLabels, extraLabels) viewParams = {MODE: MODE_MD, ORDER: labels, VISIBLE: labels, ZOOM: 50 } if renderLabels: viewParams[RENDER] = renderLabels inputId = micSet.getObjId() or micSet.getFileName() ObjectView.__init__(self, project, inputId, micSet.getFileName(), other, viewParams, **kwargs)
[docs]class CtfView(ObjectView): """ Customized ObjectView for SetOfCTF objects . """ # All extra labels that we want to show if present in the CTF results PSD_LABELS = ['_micObj.thumbnail._filename', '_psdFile', '_xmipp_enhanced_psd', '_xmipp_ctfmodel_quadrant', '_xmipp_ctfmodel_halfplane', '_micObj.plotGlobal._filename' ] EXTRA_LABELS = ['_ctftilt_tiltAxis', '_ctftilt_tiltAngle', '_xmipp_ctfCritFirstZero', '_xmipp_ctfCritCorr13', '_xmipp_ctfCritIceness', '_xmipp_ctfCritFitting', '_xmipp_ctfCritNonAstigmaticValidty', '_xmipp_ctfCritCtfMargin', '_xmipp_ctfCritMaxFreq', '_xmipp_ctfCritPsdCorr90', '_xmipp_ctfVPPphaseshift' ] def __init__(self, project, ctfSet, other='', **kwargs): first = ctfSet.getFirstItem() def existingLabels(labelList): return ' '.join([l for l in labelList if first.hasAttributeExt(l)]) psdLabels = existingLabels(self.PSD_LABELS) extraLabels = existingLabels(self.EXTRA_LABELS) labels = 'id enabled %s _defocusU _defocusV ' % psdLabels labels += '_defocusAngle _defocusRatio ' labels += '_phaseShift _resolution _fitQuality %s ' % extraLabels labels += ' _micObj._filename' viewParams = {MODE: MODE_MD, ORDER: labels, VISIBLE: labels, ZOOM: 50 } if psdLabels: viewParams[RENDER] = psdLabels if ctfSet.isStreamOpen(): viewParams['dont_recalc_ctf'] = '' inputId = ctfSet.getObjId() or ctfSet.getFileName() ObjectView.__init__(self, project, inputId, ctfSet.getFileName(), other, viewParams, **kwargs)
[docs]class ClassesView(ObjectView): """ Customized ObjectView for SetOfClasses. """ def __init__(self, project, inputid, path, other='', viewParams={}, **kwargs): from pwem.viewers.viewers_data import RegistryViewerConfig config = RegistryViewerConfig.getConfig(SetOfClasses2D) defaultViewParams = config.copy() defaultViewParams.update(viewParams) ObjectView.__init__(self, project, inputid, path, other, defaultViewParams, **kwargs)
[docs]class Classes3DView(ClassesView): """ Customized ObjectView for SetOfClasses. """ def __init__(self, project, inputid, path, other='', viewParams={}, **kwargs): defaultViewParams = {ZOOM: '99', MODE: 'metadata'} defaultViewParams.update(viewParams) ClassesView.__init__(self, project, inputid, path, other, defaultViewParams, **kwargs)
[docs]class CoordinatesObjectView(DataView): """ Wrapper to View but for displaying Scipion objects. """ MODE_AUTOMATIC = 'Automatic' def __init__(self, project, path, outputdir, protocol, pickerProps=None, inTmpFolder=False, **kwargs): DataView.__init__(self, path, **kwargs) self.project = project self.outputdir = outputdir self.protocol = protocol self.pickerProps = pickerProps self.inTmpFolder = inTmpFolder self.mode = kwargs.get('mode', None)
[docs] def show(self): return launchSupervisedPickerGUI(self._path, self.outputdir, self.protocol, mode=self.mode, pickerProps=self.pickerProps, inTmpFolder=self.inTmpFolder)
[docs]class ImageView(pwviewer.View): """ Customized ObjectView for SetOfClasses. """ def __init__(self, imagePath, **kwargs): pwviewer.View.__init__(self) self._imagePath = os.path.abspath(imagePath)
[docs] def getImagePath(self): return self._imagePath
[docs]class TableView(pwviewer.View): """ show table, pass values as: headerList = ['name', 'surname'] dataList = [ ('John', 'Smith') , ('Larry', 'Black') , ('Walter', 'White') , ('Fred', 'Becker') ]. msg = message to be shown at the table top title= window title height: Specifies the number of rows which should be visible width: minimum width in pixels fontSize= font size padding: cell extra width --------------------- Alternative way to create a table using showj views = [] labels = '_1 _2' emSet = EMSet(filename="/tmp/kk.sqlite") emObject = EMObject() emObject._1 = String('first parameter') emObject._2 = Float(12.) emSet.append(emObject) emObject = EMObject() emObject._1 = String('second parameter') emObject._2 = Float(22.) emSet.append(emObject) emSet.write() views.append(ObjectView(self._project, self.protocol.strId(), "/tmp/kk.sqlite", viewParams = {MODE: MODE_MD, ORDER: labels, VISIBLE: labels})) return views """ def __init__(self, headerList, dataList, mesg=None, title=None, height=10, width=400, padding=10, outFileName=None): """Get new widget that has as parent the top level window and set title. tables can be dump to a csv file. The name of the file is outFileName """ self.title = title self.headerList = headerList self.dataList = dataList self.outFileName = outFileName win = tk.Toplevel() if title: win.wm_title(title) # frame to place all other widgets frame = tk.Frame(win) font = getDefaultFont() font.metrics() fontheight = font.metrics()['linespace'] style = ttk.Style() style.configure('Calendar.Treeview', font=font, rowheight=fontheight) # add a "save data" button saveDataButton = ttk.Button(master=win, text="Save Data", command=self.saveData) # create treeview to store multi list with data tree = ttk.Treeview(columns=headerList, show="headings", master=win, style='Calendar.Treeview', height=height ) # define scrollbars to be added if len(dataList) > height: ysb = ttk.Scrollbar(orient=tk.VERTICAL, command=tree.yview, master=win) # xsb = ttk.Scrollbar(orient=tk.HORIZONTAL, # command= tree.xview, master=win) # add them to three view tree.configure(yscroll=ysb.set) # , xscroll=xsb.set) # create rows and columns counterRow = 1 colWidths = [] # list with maximum width per column # create headers for col in headerList: tree.heading(col, text=col.title()) # save neede width for this cell (w, h) = (font.measure(col.title()), font.metrics("linespace")) colWidths.append(w) # insert other rows # tag rows as odd or even so they may have different background colors for item in dataList: if counterRow % 2: tree.insert('', 'end', values=item, tags=('evenrow',)) else: tree.insert('', 'end', values=item, tags=('oddrow',)) counterRow += 1 counterCol = 0 for i in item: (w, h) = (font.measure(i), font.metrics("linespace")) if colWidths[counterCol] < w: colWidths[counterCol] = w counterCol += 1 # if width less than sum of column widths expand them sumColWid = sum(colWidths) + 20 if sumColWid < width: sumColWid = width factor = int(width / sumColWid) + 1 colWidths = [i * factor for i in colWidths] for col, colWidth in zip(headerList, colWidths): tree.column(col, width=colWidth + padding) # color by rows tree.tag_configure('evenrow', background='white') tree.tag_configure('oddrow', background='light grey') # message placed at the window top msg = ttk.Label(wraplength=sumColWid, justify="left", anchor="n", padding=(10, 2, 10, 6), text=mesg, master=win, font=font) # set mg in grid 0,0 msg.grid(row=0, column=0) # set button in grid 1 0 saveDataButton.grid(row=1, column=0) # set ysg in grid 2 0 tree.grid(row=2, column=0) # set ysg in grid 2 1 # but only if number of elements is larger than height if len(dataList) > height: ysb.grid(row=2, column=1, sticky='ns')
[docs] def saveData(self): """ Save data in the treeView Widget to a file. If not provided the outfile is in the Logs directory the name is the title of the table""" def slugify(value): """ Create a valid file name from the table title. Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens. """ value = re.sub(r'[^\w\s-]', '', value).strip().lower() value = re.sub(r'[-\s]+', '-', value) return value if self.outFileName is None: outFileName = slugify(self.title) + ".csv" outFileName = os.path.join(os.getcwd(), 'Logs', outFileName) print("Data saved to file: ", outFileName) with open(outFileName, "wt") as f: f.write("# TITLE = %s\n" % self.title) spamWriter = csv.writer(f) spamWriter.writerow(self.headerList) for data in self.dataList: spamWriter.writerow(data) f.close() # call the application used to handle csv files. openTextFileEditor(outFileName)