# **************************************************************************
# *
# * Authors: Grigory Sharov (gsharov@mrc-lmb.cam.ac.uk)
# *
# * MRC Laboratory of Molecular Biology (MRC-LMB)
# *
# * 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'
# *
# **************************************************************************
from pyworkflow.protocol.params import (FloatParam, EnumParam,
BooleanParam)
from pyworkflow.constants import PROD
import pyworkflow.utils as pwutils
from pwem.objects.data import CTFModel, SetOfParticles
from pwem.protocols import ProtProcessParticles
from .. import Plugin
from ..constants import *
from ..convert import writeSetOfParticles, iterLstFile, jsonToCtfModel
[docs]class EmanProtCTFAuto(ProtProcessParticles):
"""
This protocol wraps *e2ctf_auto.py* EMAN2 program.
It automates the CTF fitting and structure factor
generation process.
"""
_label = 'ctf auto'
_devStatus = PROD
def __init__(self, **kwargs):
ProtProcessParticles.__init__(self, **kwargs)
def _createFilenameTemplates(self):
""" Centralize the names of the files. """
myDict = {
'partSet': self._getExtraPath('sets/all.lst'),
'partSetFlipInvar': self._getExtraPath('sets/all__ctf_flip_invar.lst'),
'partSetFlipFullRes': self._getExtraPath('sets/all__ctf_flip_fullres.lst'),
'partSetFlipLp5': self._getExtraPath('sets/all__ctf_flip_lp5.lst'),
'partSetFlipLp7': self._getExtraPath('sets/all__ctf_flip_lp7.lst'),
'partSetFlipLp12': self._getExtraPath('sets/all__ctf_flip_lp12.lst'),
'partSetFlipLp20': self._getExtraPath('sets/all__ctf_flip_lp20.lst'),
}
self._updateFilenamesDict(myDict)
# --------------------------- DEFINE param functions ----------------------
def _defineProcessParams(self, form):
form.addParam('type', EnumParam,
choices=['hires', 'midres', 'lores'],
label='type', default=HIRES,
display=EnumParam.DISPLAY_COMBO,
help='Performs CTF processing targeting '
'different resolution:\n'
'*hires* - 2-6 Angstrom\n*midres* - 7-15 Angstrom\n'
'*lores* - 15-30 Angstrom')
line = form.addLine('Defocus search range (microns)',
help='Select _minimum_ and _maximum_ values for '
'defocus search range (in microns). Underfocus'
' is represented by a positive number.')
line.addParam('minDefocus', FloatParam, default=0.6,
label='Min')
line.addParam('maxDefocus', FloatParam, default=4.,
label='Max')
form.addParam('fromScratch', BooleanParam, default=False,
label='Fit from scratch',
help='Force refitting of CTF from scratch, ignoring any '
'previous fits.')
form.addParam('astig', BooleanParam, default=False,
label='Estimate astigmatism',
help='Includes astigmatism in automatic fitting.')
form.addParam('phaseEst', BooleanParam, default=False,
label='Estimate phase shift',
help='Include phase/amplitude contrast in CTF '
'estimation. For use with hole-less phase plates.')
form.addSection(label='Advanced')
form.addParam('extrapad', BooleanParam, default=False,
label='Extra padding',
help='If particles were boxed more tightly than EMAN '
'requires, this will add some extra padding.')
form.addParam('invarType', EnumParam,
choices=['auto', 'bispec', 'harmonic'],
label='Invariant type', default=INVAR_AUTO,
display=EnumParam.DISPLAY_COMBO,
help='Which type of invariants to generate')
form.addParam('highDensity', BooleanParam, default=False,
label='High density ',
help='If particles are very close together, this will '
'interfere with SSNR estimation. '
'If set uses an alternative strategy, '
'but may over-estimate SSNR.')
form.addParam('invert', BooleanParam, default=False,
label='Invert contrast',
help='Invert the contrast of the particles in output '
'files (default false)')
form.addParam('constBfact', FloatParam, default=-1.0,
label='Constant B-factor',
help='Set B-factor to a fixed value, negative value '
'enables autofitting.')
form.addParallelSection(threads=1, mpi=0)
# --------------------------- INSERT steps functions ----------------------
def _insertAllSteps(self):
self._createFilenameTemplates()
self._insertFunctionStep('convertImagesStep')
args = self._prepareParams()
self._insertFunctionStep('runCTFStep', args)
self._insertFunctionStep('createOutputStep')
# --------------------------- STEPS functions -----------------------------
[docs] def convertImagesStep(self):
partSet = self._getInputParticles()
partAlign = partSet.getAlignment()
storePath = self._getExtraPath("particles")
pwutils.makePath(storePath)
writeSetOfParticles(partSet, storePath, alignType=partAlign)
[docs] def runCTFStep(self, args):
""" Run the EMAN e2ctf_auto.py program. """
program = Plugin.getProgram('e2ctf_auto.py')
self.runJob(program, args, cwd=self._getExtraPath(),
numberOfThreads=1)
[docs] def createOutputStep(self):
inputSet = self._getInputParticles()
outputSets = self._getOutputSets()
outputs = {}
for key, fn in outputSets.items():
outputSet = self._createSetOfParticles(suffix='_%s' % key)
outputSet.copyInfo(inputSet)
outputSet.setIsPhaseFlipped(True)
outputSet.setHasCTF(True)
outputSet.copyItems(inputSet,
updateItemCallback=self._updateCTF,
itemDataIterator=iterLstFile(self._getFileName(fn)))
newPix = self._getNewPixSize(outputSet.getDimensions()[0])
outputSet.setSamplingRate(newPix)
summary = self.getSummary(key)
outputSet.setObjComment(summary)
if key == 'FL':
outputName = 'outputParticles_flip_fullRes'
elif key == 'bispec':
outputName = 'outputParticles_flip_invar'
else:
outputName = 'outputParticles_flip_lp%s' % key
outputs[outputName] = outputSet
self._defineOutputs(**outputs)
for _, out in self.iterOutputAttributes(SetOfParticles):
self._defineSourceRelation(inputSet, out)
# --------------------------- INFO functions ------------------------------
def _validate(self):
errors = []
partSet = self._getInputParticles()
if partSet.isPhaseFlipped():
errors.append('Input particles are already phase-flipped. '
'Please provide original raw particle images.')
if not self.fromScratch and not partSet.hasCTF():
errors.append('Input particles have no CTF information, '
'please select _Fit from scratch_ option.')
if partSet.getAcquisition() is None:
errors.append('Acquisition information missing for input '
'particles, you cannot estimate CTF!')
return errors
def _summary(self):
summary = []
if self.hasAttribute('outputParticles_flip_invar'):
summary.append('CTF estimation on particles completed, '
'produced filtered particles and bispectra.')
else:
summary.append('Output is not ready yet.')
return summary
[docs] def getSummary(self, key):
if key == 'FL':
return 'Phase flipped, full resolution'
elif key == 'bispec':
return 'Bispectra footprints computed from high pass filtered normalized particles'
else:
return "Phase flipped, low-pass filtered to %d A" % int(key)
# --------------------------- UTILS functions -----------------------------
def _prepareParams(self):
partSet = self._getInputParticles()
acq = partSet.getAcquisition()
args = "--%s" % self.getEnumText('type')
args += " --voltage %3d" % acq.getVoltage()
args += " --cs %0.3f" % acq.getSphericalAberration()
args += " --ac %0.2f" % (100 * acq.getAmplitudeContrast())
args += " --apix %0.3f" % partSet.getSamplingRate()
if self.fromScratch:
args += " --fromscratch"
else:
args += " --curdefocusfix"
if self.astig:
args += " --astigmatism"
if self.phaseEst:
args += " --phaseplate"
if self.extrapad:
args += " --extrapad"
if self.highDensity:
args += " --highdensity"
if self.invert:
args += " --invert"
args += " --invartype %s" % self.getEnumText('invarType')
args += " --constbfactor %0.2f --defocusmin %0.2f --defocusmax %0.2f" % (
self.constBfact.get(),
self.minDefocus.get(),
self.maxDefocus.get())
args += " --threads %d" % self.numberOfThreads.get()
args += " --minqual 0"
return args
def _getInputParticles(self):
return self.inputParticles.get()
def _updateCTF(self, item, row):
fileName = self._getExtraPath(row[1])
item.setLocation(row[0], fileName)
if not item.hasCTF():
item.setCTF(CTFModel())
jsonToCtfModel(fileName, item.getCTF())
def _getOutputSets(self):
protType = self.getEnumText('type')
outputs = {}
if protType == 'hires':
outputs.update({'FL': 'partSetFlipFullRes',
'12': 'partSetFlipLp12',
'5': 'partSetFlipLp5'})
elif protType == 'midres':
outputs.update({'FL': 'partSetFlipFullRes',
'20': 'partSetFlipLp20',
'7': 'partSetFlipLp7'})
else: # lores
outputs.update({'20': 'partSetFlipLp20',
'12': 'partSetFlipLp12'})
outputs['bispec'] = 'partSetFlipInvar'
return outputs
def _getNewPixSize(self, newBox):
# calculates new pix size for binned particles
inputParts = self.inputParticles.get()
oldDimX = inputParts.getDimensions()[0]
oldPixSize = inputParts.getSamplingRate()
newPixSize = float(oldDimX) / newBox * oldPixSize
return newPixSize