# **************************************************************************
# *
# * Authors: Federico P. de Isidro Gomez (fp.deisidro@cnb.csic.es) [1]
# *
# * [1] Centro Nacional de Biotecnologia, CSIC, Spain
# *
# * 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 os
from pyworkflow import BETA
from pyworkflow.object import Set
import pyworkflow.protocol.params as params
import pyworkflow.utils.path as path
import tomo.objects as tomoObj
from pwem.emlib.image import ImageHandler
from imod import Plugin
from imod.protocols.protocol_base import ProtImodBase
[docs]class ProtImodTSNormalization(ProtImodBase):
"""
Normalize input tilt-series and change its storing formatting.
More info:
https://bio3d.colorado.edu/imod/doc/man/newstack.html
"""
_label = 'Tilt-series normalization'
_devStatus = BETA
# -------------------------- DEFINE param functions -----------------------
def _defineParams(self, form):
form.addSection('Input')
form.addParam('inputSetOfTiltSeries',
params.PointerParam,
pointerClass='SetOfTiltSeries',
important=True,
label='Input set of tilt-series')
form.addParam('binning',
params.FloatParam,
default=1.0,
label='Binning',
important=True,
help='Binning to be applied to the normalized tilt-series in IMOD convention. Images will be '
'binned by the given factor. Must be an integer bigger than 1')
form.addParam('floatDensities',
params.EnumParam,
choices=['default', '1', '2', '3', '4'],
default=0,
label='Adjust densities mode',
important=True,
display=params.EnumParam.DISPLAY_HLIST,
help='Adjust densities of sections individually:\n'
'-Default: no adjustment performed\n'
'-Mode 1: sections fill the data range\n'
'-Mode 2: sections scaled to common mean and standard deviation.\n'
'-Mode 3: sections shifted to a common mean without scaling\n'
'-Mode 4: sections shifted to a common mean and then rescale the resulting minimum and '
'maximum densities to the Min and Max values specified')
form.addParam('modeToOutput',
params.EnumParam,
choices=['default', '4-bit', 'byte', 'signed 16-bit', 'unsigned 16-bit', '32-bit float'],
default=0,
label='Storage data type',
important=True,
display=params.EnumParam.DISPLAY_HLIST,
help='Apply one density scaling to all sections to map current min and max to the given Min and '
'Max. The storage mode of the output file. The default is the mode of the first input file, '
'except for a 4-bit input file, where the default is to output as bytes')
form.addParam('scaleRangeToggle',
params.EnumParam,
choices=['Yes', 'No'],
condition="floatDensities==0 or floatDensities==1 or floatDensities==3",
default=1,
label='Set scaling range values',
important=True,
display=params.EnumParam.DISPLAY_HLIST,
help='This option will rescale the densities of all sections by the same '
'factors so that the original minimum and maximum density will be mapped '
'to the Min and Max values that are entered')
form.addParam('scaleRangeMax',
params.FloatParam,
condition="(floatDensities==0 or floatDensities==1 or floatDensities==3) and scaleRangeToggle==0",
default=255,
label='Max.',
help='Maximum value for the rescaling')
form.addParam('scaleRangeMin',
params.FloatParam,
condition="(floatDensities==0 or floatDensities==1 or floatDensities==3) and scaleRangeToggle==0",
default=0,
label='Min.',
help='Minimum value for the rescaling')
groupMeanSd = form.addGroup('Mean and SD',
condition='floatDensities==2',
help='Scale all images to the given mean and standard deviation. This option '
'implies -float 2 and is incompatible with all other scaling options. If no '
'values are set, mean=0 and SD=1 by default')
groupMeanSd.addParam('meanSdToggle',
params.EnumParam,
choices=['Yes', 'No'],
default=1,
label='Set mean and SD',
important=True,
display=params.EnumParam.DISPLAY_HLIST,
help='Set mean and SD values')
groupMeanSd.addParam('scaleMean',
params.FloatParam,
default=0,
label='Mean',
help='Mean value for the rescaling')
groupMeanSd.addParam('scaleSd',
params.FloatParam,
default=1,
label='SD',
help='Standard deviation value for the rescaling')
groupScale = form.addGroup('Scaling values',
condition='floatDensities==4')
groupScale.addParam('scaleMax',
params.FloatParam,
default=255,
label='Max.',
help='Maximum value for the rescaling')
groupScale.addParam('scaleMin',
params.FloatParam,
default=0,
label='Min.',
help='Minimum value for the rescaling')
# -------------------------- INSERT steps functions ---------------------
def _insertAllSteps(self):
for ts in self.inputSetOfTiltSeries.get():
self._insertFunctionStep(self.convertInputStep, ts.getObjId(), False)
self._insertFunctionStep(self.generateOutputStackStep, ts.getObjId())
self._insertFunctionStep(self.closeOutputSetsStep)
# --------------------------- STEPS functions ----------------------------
[docs] def generateOutputStackStep(self, tsObjId):
self.getOutputSetOfTiltSeries(self.inputSetOfTiltSeries.get(), self.binning.get())
ts = self.inputSetOfTiltSeries.get()[tsObjId]
tsId = ts.getTsId()
extraPrefix = self._getExtraPath(tsId)
tmpPrefix = self._getTmpPath(tsId)
paramsNewstack = {
'input': os.path.join(tmpPrefix, ts.getFirstItem().parseFileName()),
'output': os.path.join(extraPrefix, ts.getFirstItem().parseFileName()),
'bin': int(self.binning.get()),
'imagebinned': 1.0,
}
argsNewstack = "-input %(input)s " \
"-output %(output)s " \
"-bin %(bin)d " \
"-imagebinned %(imagebinned)s "
if self.floatDensities.get() != 0:
argsNewstack += " -FloatDensities " + str(self.floatDensities.get())
if self.floatDensities.get() == 2:
if self.meanSdToggle.get() == 0:
argsNewstack += " -MeanAndStandardDeviation " + str(self.scaleMean.get()) + "," + \
str(self.scaleSd.get())
elif self.floatDensities.get() == 4:
argsNewstack += " -ScaleMinAndMax " + str(self.scaleMax.get()) + "," + str(self.scaleMin.get())
else:
if self.scaleRangeToggle.get() == 0:
argsNewstack += " -ScaleMinAndMax " + str(self.scaleRangeMax.get()) + "," + \
str(self.scaleRangeMin.get())
if self.getModeToOutput() is not None:
argsNewstack += " -ModeToOutput " + str(self.getModeToOutput())
Plugin.runImod(self, 'newstack', argsNewstack % paramsNewstack)
newTs = tomoObj.TiltSeries(tsId=tsId)
newTs.copyInfo(ts)
self.outputSetOfTiltSeries.append(newTs)
if self.binning > 1:
newTs.setSamplingRate(ts.getSamplingRate() * int(self.binning.get()))
for index, tiltImage in enumerate(ts):
newTi = tomoObj.TiltImage()
newTi.copyInfo(tiltImage, copyId=True, copyTM=True)
if tiltImage.hasTransform():
newTi = self.updateTM(newTi)
newTi.setAcquisition(tiltImage.getAcquisition())
newTi.setLocation(index + 1, (os.path.join(extraPrefix, tiltImage.parseFileName())))
if self.binning > 1:
newTi.setSamplingRate(tiltImage.getSamplingRate() * int(self.binning.get()))
newTs.append(newTi)
ih = ImageHandler()
x, y, z, _ = ih.getDimensions(newTs.getFirstItem().getFileName())
newTs.setDim((x, y, z))
newTs.write(properties=False)
self.outputSetOfTiltSeries.update(newTs)
self.outputSetOfTiltSeries.updateDim()
self.outputSetOfTiltSeries.write()
self._store()
[docs] def closeOutputSetsStep(self):
self.outputSetOfTiltSeries.setStreamState(Set.STREAM_CLOSED)
self.outputSetOfTiltSeries.write()
self._store()
# --------------------------- UTILS functions ----------------------------
[docs] def getModeToOutput(self):
parseParamsOutputMode = {
0: None,
1: 101,
2: 0,
3: 1,
4: 6,
5: 2
}
return parseParamsOutputMode[self.modeToOutput.get()]
[docs] def updateTM(self, newTi):
transform = newTi.getTransform()
matrix = transform.getMatrix()
matrix[0][2] = matrix[0][2] / self.binning.get()
matrix[1][2] = matrix[1][2] / self.binning.get()
transform.setMatrix(matrix)
newTi.setTransform(transform)
return newTi
# --------------------------- INFO functions ----------------------------
def _summary(self):
summary = []
if hasattr(self, 'outputSetOfTiltSeries'):
summary.append("Input Tilt-Series: %d.\nInterpolations applied: %d.\n"
% (self.inputSetOfTiltSeries.get().getSize(),
self.outputSetOfTiltSeries.getSize()))
else:
summary.append("Output classes not ready yet.")
return summary
def _methods(self):
methods = []
if hasattr(self, 'outputSetOfTiltSeries'):
methods.append("%d tilt-series have been normalized using the IMOD newstack program.\n"
% (self.outputSetOfTiltSeries.getSize()))
else:
methods.append("Output classes not ready yet.")
return methods