Repository: I2PC/scipion-em-xmipp (language: python)

Issue:
FSO protocol viewer updated adn bfactor viewer

deepPostProcessing fails to create output starting from two half maps
Detailed below appears the error in protocol_postProcessing_deepPostProcessing in createOutputStep
 self._defineTransformRelation(self.inputHalf1, volume)


Neural net inference
01259:   WARNING: This is xmipp_base implementation for script
01260:   deepemhancer  -i Runs/008142_XmippProtDeepVolPostProc/tmp/inputHalf1.mrc  -i2 Runs/008142_XmippProtDeepVolPostProc/tmp/inputHalf2.mrc  -o Runs/008142_XmippProtDeepVolPostProc/extra/deepPostProcess.mrc  --deepLearningModelPath /home/roberto/scipion3/software/em/xmipp/models/deepEMhancer/production_checkpoints/deepEMhancer_tightTarget.hd5  --samplingRate 1.430000 --cleaningStrengh -1.000000 -g 0 -b 8 
01261:   deepemhancer  -i Runs/008142_XmippProtDeepVolPostProc/tmp/inputHalf1.mrc  -i2 Runs/008142_XmippProtDeepVolPostProc/tmp/inputHalf2.mrc  -o Runs/008142_XmippProtDeepVolPostProc/extra/deepPostProcess.mrc  --deepLearningModelPath /home/roberto/scipion3/software/em/xmipp/models/deepEMhancer/production_checkpoints/deepEMhancer_tightTarget.hd5  --samplingRate 1.430000 --cleaningStrengh -1.000000 -g 0 -b 8 
01262:   FINISHED: deepVolPostProStep, step 2, time 2020-12-02 14:21:12.612273



ERROR:   


01263:   STARTED: createOutputStep, step 3, time 2020-12-02 14:21:13.122197
01264:   Traceback (most recent call last):
01265:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pyworkflow/protocol/protocol.py", line 187, in run
01266:       self._run()
01267:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pyworkflow/protocol/protocol.py", line 238, in _run
01268:       resultFiles = self._runFunc()
01269:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pyworkflow/protocol/protocol.py", line 234, in _runFunc
01270:       return self._func(*self._args)
01271:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/xmipp3/protocols/protocol_postProcessing_deepPostProcessing.py", line 265, in createOutputStep
01272:       self._defineTransformRelation(self.inputHalf1, volume)
01273:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pwem/protocols/protocol.py", line 154, in _defineTransformRelation
01274:       self._defineRelation(pwobj.RELATION_TRANSFORM, srcObj, dstObj)
01275:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pyworkflow/protocol/protocol.py", line 1276, in _defineRelation
01276:       parentExt, childExt)
01277:     File "/home/roberto/scipion3/.scipion3/lib/python3.7/site-packages/pyworkflow/mapper/sqlite.py", line 433, in insertRelation
01278:       if not o.hasObjId():
01279:   AttributeError: 'NoneType' object has no attribute 'hasObjId'
01280:   Protocol failed: 'NoneType' object has no attribute 'hasObjId'
01281:   FAILED: createOutputStep, step 3, time 2020-12-02 14:21:13.179255


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..c3c313d3f
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,20 @@
+name: Build
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    types: [opened, synchronize, reopened]
+jobs:
+  sonarcloud:
+    name: SonarCloud
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
+      - name: SonarCloud Scan
+        uses: SonarSource/sonarcloud-github-action@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any
+          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 21f9ddbb0..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-language: python
-os: linux
-dist: bionic
-python: 3.8
-git:
-    depth: false # to get blame information for SonarCloud
-addons:
-  sonarcloud:
-# See https://docs.travis-ci.com/user/sonarcloud/
-    organization: "i2pc"
-    token: $SONAR_TOKEN
-
-script:
- - sonar-scanner
diff --git a/CHANGES.txt b/CHANGES.txt
index 62fe3002d..67c69cb1b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,14 @@
+## Release 3.21.06 -
+
+- CUDA-11 support
+- New protocol: Deep align
+- ChimeraX support
+- Improvements of streaming process
+- Several performance optimizations
+- Build time optimization
+- Multiple bug fixes
+- Improved documentation
+
 
 ## Release 3.20.07 -
 
diff --git a/README.md b/README.md
index c0168af0a..573543fb4 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,11 @@
 Plugin to use Xmipp programs within the Scipion framework.
 
 You can install it either, using the Scipion software following 
-[this](https://scipion-em.github.io/docs/docs/scipion-modes/install-from-sources.html#step-4-installing-xmipp3-and-other-em-plugins) (**production users**) 
-or cloning it on your own and link it to Scipion following 
-[this](https://scipion-em.github.io/docs/docs/scipion-modes/install-plugins-command-line.html#devel-mode). The latter is only for those who intended to develop the Xmipp plugin **but not the Xmipp software**.
+the [Scipion's installation guide](https://scipion-em.github.io/docs/docs/scipion-modes/install-from-sources.html#installing-scipion-v3-0)
+(**strongly recommended for non-developers**) or cloning it on your own and 
+[linking it to Scipion](https://scipion-em.github.io/docs/docs/scipion-modes/install-plugins-command-line.html#devel-mode).
+The latter is only for those who intended to develop the Xmipp plugin but **not the whole Xmipp software**.
 
-For **developers**, we strongly recommend to follow the guide on [how to install xmipp-bundle](https://github.com/i2pc/xmipp#getting-started) in order to get a complet Xmipp development ecosystem.
+For **developers**, we strongly recommend to follow the guide on 
+[how to install xmipp-bundle](https://github.com/i2pc/xmipp#xmipp-as-a-standalone-bundle-for-developers) 
+in order to get a complet Xmipp's development ecosystem.
diff --git a/requirements.txt b/requirements.txt
index 5b2105a91..f48363252 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ joblib
 peppercorn
 plotter
 scons
+PyWavelets
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 9f453fd4f..9a10569c9 100644
--- a/setup.py
+++ b/setup.py
@@ -46,7 +46,7 @@
     # For a discussion on single-sourcing the version across setup.py and the
     # project code, see
     # https://packaging.python.org/en/latest/single_source_version.html
-    version='20.07.04',  # Required
+    version='21.06.1',  # Required
 
     # This is a one-line description or tagline of what your project does. This
     # corresponds to the "Summary" metadata field:
diff --git a/sonar-project.properties b/sonar-project.properties
old mode 100755
new mode 100644
index 0226dfd78..45b7ed30c
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,11 +1,13 @@
 sonar.projectKey=ScipionEmXmipp
-sonar.projectName=Scipion Em Xmipp
-sonar.projectVersion=3.0
+sonar.organization=i2pc
 
-#sonar.log.level=DEBUG
+# This is the name and version displayed in the SonarCloud UI.
+sonar.projectName=Scipion-em-Xmipp
+sonar.projectVersion=3.0
 
-sonar.sources=./
-# don't use .gitignore for auto exclusion, as it doesn't work properly and exclude almost everything
-sonar.scm.exclusions.disabled=true 
-sonar.exclusions=**/tests/**
+# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
+sonar.sources=.
+sonar.exlusions=**/legacy/**
 
+# Encoding of the source code. Default is default system encoding
+#sonar.sourceEncoding=UTF-8
diff --git a/xmipp3/__init__.py b/xmipp3/__init__.py
index 8e8233ff7..d983fc59d 100644
--- a/xmipp3/__init__.py
+++ b/xmipp3/__init__.py
@@ -39,7 +39,7 @@
 
 _logo = "xmipp_logo.png"
 _references = ['delaRosaTrevin2013', 'Sorzano2013']
-_currentVersion = '3.20.07'
+_currentVersion = '3.21.06.1'
 
 
 class Plugin(pwem.Plugin):
@@ -144,7 +144,7 @@ def defineBinaries(cls, env):
 
         ## Linking bindings (removing installationToken)
         bindingsAndLibsCmd = ("find {bindingsSrc} -maxdepth 1 -mindepth 1 "
-                              "! -name __pycache__ -exec ln -srfn {{}} {bindingsDst} \; && "
+                              r"! -name __pycache__ -exec ln -srfn {{}} {bindingsDst} \; && "
                               "ln -srfn {coreLib} {libsDst} && "
                               "touch {bindingsToken} && "
                               "rm {installedToken} 2> /dev/null")
@@ -157,7 +157,7 @@ def defineBinaries(cls, env):
         ## Allowing xmippDev if devel mode detected
         # plugin  = scipion-em-xmipp  <--  xmipp3    <--     __init__.py
         pluginDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-        # bundle  = xmipp-bundle  <-  src  <-  scipion-em-xmipp
+        # bundle  = xmipp-bundle  <-  src   <-  scipion-em-xmipp
         bundleDir = os.path.dirname(os.path.dirname(pluginDir))
 
         isPypiDev = os.path.isfile(os.path.join(pluginDir, 'setup.py'))
@@ -241,7 +241,7 @@ def installDeepLearningToolkit(plugin, env):
                      'xmippLibToken': 'xmippLibToken',
                      'libXmipp': plugin.getHome('lib/libXmipp.so'),
                      'preMsgsStr': ' ; '.join(preMsgs),
-                     'afterMsgs': "\n > ".join(cudaMsgs)}
+                     'afterMsgs': ", > ".join(cudaMsgs)}
 
     installDLvars.update({'modelsTarget': "%s_%s_%s_%s"
                                           % (installDLvars['modelsPrefix'],
diff --git a/xmipp3/base.py b/xmipp3/base.py
index 2315c534e..5ff38514c 100644
--- a/xmipp3/base.py
+++ b/xmipp3/base.py
@@ -450,11 +450,13 @@ def getSymmetry(self,dihedral):
 
     def runCoarseSearch(self, fnVol, dihedral, heightFraction, z0, zF, zStep,
                         rot0, rotF, rotStep, Nthr, fnOut, cylinderInnerRadius,
-                        cylinderOuterRadius, height, Ts):
+                        cylinderOuterRadius, height, Ts, Cn="c1"):
         args = ("-i %s --sym %s --heightFraction %f -z %f %f %f "
                 "--rotHelical %f %f %f --thr %d -o %s --sampling %f"
                 %(fnVol, self.getSymmetry(dihedral), heightFraction, z0, zF, zStep,
                   rot0, rotF, rotStep, Nthr, fnOut, Ts))
+        if Cn!="c1":
+            args += " --sym2 %s"%Cn
         if cylinderOuterRadius > 0 and cylinderInnerRadius < 0:
             args += " --mask cylinder %d %d"%(-cylinderOuterRadius, -height)
         elif cylinderOuterRadius > 0 and cylinderInnerRadius > 0:
@@ -463,7 +465,7 @@ def runCoarseSearch(self, fnVol, dihedral, heightFraction, z0, zF, zStep,
 
     def runFineSearch(self, fnVol, dihedral, fnCoarse, fnFine, heightFraction,
                       z0, zF, rot0, rotF, cylinderInnerRadius, cylinderOuterRadius,
-                      height, Ts):
+                      height, Ts, Cn="c1"):
         md=emlib.MetaData(fnCoarse)
         objId=md.firstObject()
         rotInit=md.getValue(emlib.MDL_ANGLE_ROT,objId)
@@ -472,6 +474,8 @@ def runFineSearch(self, fnVol, dihedral, fnCoarse, fnFine, heightFraction,
               "-z %f %f 1 --rotHelical %f %f 1 --sampling %f"
               %(fnVol, self.getSymmetry(dihedral), heightFraction, zInit, rotInit,
                 fnFine, z0, zF, rot0, rotF, Ts))
+        if Cn!="c1":
+            args += " --sym2 %s"%Cn
         if cylinderOuterRadius>0 and cylinderInnerRadius<0:
             args+=" --mask cylinder %d %d"%(-cylinderOuterRadius,-height)
         elif cylinderOuterRadius>0 and cylinderInnerRadius>0:
@@ -479,7 +483,7 @@ def runFineSearch(self, fnVol, dihedral, fnCoarse, fnFine, heightFraction,
         self.runJob('xmipp_volume_find_symmetry',args, numberOfMpi=1)
 
     def runSymmetrize(self, fnVol, dihedral, fnParams, fnOut, heightFraction,
-                      cylinderInnerRadius, cylinderOuterRadius, height, Ts):
+                      cylinderInnerRadius, cylinderOuterRadius, height, Ts, Cn="c1"):
         md=emlib.MetaData(fnParams)
         objId=md.firstObject()
         rot0=md.getValue(emlib.MDL_ANGLE_ROT,objId)
@@ -487,6 +491,8 @@ def runSymmetrize(self, fnVol, dihedral, fnParams, fnOut, heightFraction,
         args=("-i %s --sym %s --helixParams %f %f --heightFraction %f -o %s "
               "--sampling %f --dont_wrap"
               % (fnVol,self.getSymmetry(dihedral),z0,rot0,heightFraction,fnOut,Ts))
+        if Cn!="c1":
+            args += " --sym2 %s"%Cn
         self.runJob('xmipp_transform_symmetrize',args,numberOfMpi=1)
         doMask=False
         if cylinderOuterRadius>0 and cylinderInnerRadius<0:
diff --git a/xmipp3/bibtex.py b/xmipp3/bibtex.py
index 9b5366551..af51b9672 100644
--- a/xmipp3/bibtex.py
+++ b/xmipp3/bibtex.py
@@ -161,7 +161,7 @@
 }
 
 @article{PascualMontano2001,
-title = "A Novel Neural Network Technique for Analysis and Classification of \{EM\} Single-Particle Images",
+title = "A Novel Neural Network Technique for Analysis and Classification of \\{EM\\} Single-Particle Images",
 journal = "JSB",
 volume = "133",
 number = "2 - 3",
@@ -318,7 +318,7 @@
 
 @Article{Sorzano2007a,
   Title                    = {Fast, robust and accurate determination of transmission electron microscopy contrast transfer function},
-  Author                   = {Sorzano, C. O. S. and Jonic, S. and N\'u\~nez-Ram\'irez, R. and Boisset, N. and Carazo, J. M.},
+  Author                   = {Sorzano, C. O. S. and Jonic, S. and N\'u\\~nez-Ram\'irez, R. and Boisset, N. and Carazo, J. M.},
   Journal                  = {J. Structural Biology},
   Year                     = {2007},
   Pages                    = {249--262},
@@ -581,6 +581,15 @@
 DOI = {http://doi.org/10.3390/electronics9061040}
 }
 
-
+@Article{Vilas2021,
+AUTHOR = {Vilas, JL and Tagare, HD},
+TITLE = {A simple and intuitive measurement of directional resolution anisotropy of cryoEM maps},
+JOURNAL = {XXXX},
+VOLUME = {X},
+YEAR = {2021},
+NUMBER = {X},
+ARTICLE-NUMBER = {xxx},
+URL = {https://github.com/I2PC/xmipp/wiki/FSO---Fourier-Shell-Occupancy},
+}
 """
 
diff --git a/xmipp3/convert/io_coordinates.py b/xmipp3/convert/io_coordinates.py
index 95e18a51b..1eb323292 100644
--- a/xmipp3/convert/io_coordinates.py
+++ b/xmipp3/convert/io_coordinates.py
@@ -31,17 +31,17 @@ def readSetOfCoordsFromPosFnames(posDir, setOfInputCoords, sqliteOutName,
 
 def writeCoordsListToPosFname(mic_fname, list_x_y, outputRoot):
     s = """# XMIPP_STAR_1 *
-    #
-    data_header
-    loop_
-     _pickingMicrographState
-    Auto
-    data_particles
-    loop_
-     _xcoor
-     _ycoor
-    """
-    baseName = os.path.basename(mic_fname).split(".")[0]
+#
+data_header
+loop_
+_pickingMicrographState
+Auto
+data_particles
+loop_
+_xcoor
+_ycoor
+"""
+    baseName, _ = os.path.splitext(os.path.basename(mic_fname))
     print("%d %s %s"%(len(list_x_y), mic_fname,
                       os.path.join(outputRoot, baseName+".pos")))
 
diff --git a/xmipp3/protocols/protocol_subtract_projection.py b/xmipp3/legacy/protocol_subtract_projection.py
similarity index 100%
rename from xmipp3/protocols/protocol_subtract_projection.py
rename to xmipp3/legacy/protocol_subtract_projection.py
diff --git a/xmipp3/protocols.conf b/xmipp3/protocols.conf
index e04ce66b2..fd944e360 100644
--- a/xmipp3/protocols.conf
+++ b/xmipp3/protocols.conf
@@ -46,7 +46,8 @@ Protocols SPA = [
 		]},
 		{"tag": "protocol_group", "text": "Filter", "openItem": "False", "children": [
 			{"tag": "protocol", "value": "XmippProtFilterParticles",   "text": "default"},
-			{"tag": "protocol", "value": "XmippProtDenoiseParticles",  "text": "default"}
+			{"tag": "protocol", "value": "XmippProtDenoiseParticles",  "text": "default"},
+			{"tag": "protocol", "value": "XmippProtSimulateCTF",   "text": "default"}
 		]},
 		{"tag": "protocol_group", "text": "Mask", "openItem": "False", "children": [
 			{"tag": "protocol", "value": "XmippProtCreateMask2D",   "text": "default"},
@@ -76,7 +77,8 @@ Protocols SPA = [
 	{"tag": "protocol", "value": "XmippProtReconstructSwarm",  "text": "default"},
 	{"tag": "protocol", "value": "XmippProtConvertPdb",  "text": "default"},
 	{"tag": "section", "text": "more", "openItem": "False", "children": [
-	{"tag": "protocol", "value": "XmippProtRCT", "text": "default"}
+	{"tag": "protocol", "value": "XmippProtRCT", "text": "default"},
+	{"tag": "protocol", "value": "XmippProtShiftParticles", "text": "default"}
 	]}
 	]},
 	{"tag": "protocol_group", "text": "Preprocess", "openItem": "False", "children": [
@@ -97,6 +99,7 @@ Protocols SPA = [
 		{"tag": "protocol", "value": "XmippProtReconstructHighRes", "text": "default"},
 		{"tag": "protocol", "value": "XmippProtProjMatch",  "text": "default"},
 		{"tag": "protocol", "value": "XmippProtLocalCTF",  "text": "default"},
+		{"tag": "protocol", "value": "XmippProtDeepAlign",  "text": "default"},
 		{"tag": "section", "text": "more", "openItem": "False", "children": []}
 	]},
     {"tag": "protocol_group", "text": "Postprocess", "openItem": "False", "children": [
@@ -129,7 +132,6 @@ Protocols SPA = [
 
 	]},
 	{"tag": "section", "text": "more", "openItem": "False", "children": [
-	{"tag": "protocol", "value": "XmippProtSubtractProjection", "text": "default"},
 	{"tag": "protocol", "value": "XmippProtCombinePdb", "text": "default"},
 	{"tag": "protocol", "value": "XmippProtAnalyzeLocalCTF", "text": "default"},
     {"tag": "protocol", "value": "XmippProtConsensusLocalCTF", "text": "default"}
@@ -143,7 +145,10 @@ Protocols SPA = [
 		{"tag": "protocol_group", "text": "Sets", "openItem": "False", "children": []},
 		{"tag": "protocol_group", "text": "Calculators", "openItem": "False", "children": [
 			{"tag": "protocol", "value": "XmippProtImageOperateParticles", "text": "default"},
-			{"tag": "protocol", "value": "XmippProtImageOperateVolumes", "text": "default"}
+			{"tag": "protocol", "value": "XmippProtImageOperateVolumes", "text": "default"},
+		    {"tag": "protocol", "value": "XmippProtVolSubtraction", "text": "default"},
+		    {"tag": "protocol", "value": "XmippProtVolAdjust", "text": "default"},
+            {"tag": "protocol", "value": "XmippProtVolConsensus", "text": "default"}
 		]}
 	]}]
 
@@ -192,6 +197,8 @@ Tomography = [
         ]},
     {"tag": "section", "text": "Subtomogram averaging", "children": []},
     {"tag": "section", "text": "Postprocessing", "children": [
+        {"tag": "protocol", "value": "XmippProtVolSubtraction", "text": "default"},
+        {"tag": "protocol", "value": "XmippProtVolConsensus", "text": "default"},
         {"tag": "protocol_group", "text": "Denoise", "openItem": "False", "children": []},
 	    {"tag": "protocol_group", "text": "Segmentation", "openItem": "False", "children": []}
     ]}]
diff --git a/xmipp3/protocols/__init__.py b/xmipp3/protocols/__init__.py
index 734dc8003..3cc3957cc 100644
--- a/xmipp3/protocols/__init__.py
+++ b/xmipp3/protocols/__init__.py
@@ -50,7 +50,6 @@
 from .protocol_ctf_micrographs import XmippProtCTFMicrographs
 from .protocol_ctf_correct_wiener2d import XmippProtCTFCorrectWiener2D
 from .protocol_consensus_classes3D import XmippProtConsensusClasses3D
-from .protocol_subtract_projection import XmippProtSubtractProjection
 from .protocol_denoise_particles import XmippProtDenoiseParticles
 from .protocol_deep_micrograph_screen import  XmippProtDeepMicrographScreen
 from .protocol_eliminate_empty_images import (XmippProtEliminateEmptyParticles,
@@ -88,10 +87,10 @@
 from .protocol_reconstruct_significant import XmippProtReconstructSignificant
 from .protocol_reconstruct_swarm import XmippProtReconstructSwarm
 from .protocol_resolution3d import XmippProtResolution3D
+from .protocol_resolution_bfactor import XmippProtbfactorResolution
 from .protocol_resolution_directional import XmippProtMonoDir
 from .protocol_resolution_fso import XmippProtFSO
 from .protocol_resolution_monogenic_signal import XmippProtMonoRes
-from .protocol_resolution_monotomo import XmippProtMonoTomo
 from .protocol_resolution_deepres import XmippProtDeepRes
 from .protocol_postProcessing_deepPostProcessing import XmippProtDeepVolPostProc
 from .protocol_rotational_spectra import XmippProtRotSpectra
@@ -99,6 +98,8 @@
 from .protocol_screen_particles import XmippProtScreenParticles
 from .protocol_screen_deepConsensus import XmippProtScreenDeepConsensus, XmippProtDeepConsSubSet
 from .protocol_screen_deeplearning import XmippProtScreenDeepLearning
+from .protocol_shift_particles import XmippProtShiftParticles
+from .protocol_simulate_ctf import XmippProtSimulateCTF
 from .protocol_solid_angles import XmippProtSolidAngles
 from .protocol_split_volume import XmippProtSplitvolume
 from .protocol_trigger_data import XmippProtTriggerData
@@ -116,12 +117,14 @@
 from .protocol_metaprotocol_create_subset import XmippMetaProtCreateSubset
 from .protocol_metaprotocol_create_output import XmippMetaProtCreateOutput
 from .protocol_metaprotocol_discrete_heterogeneity_scheduler import XmippMetaProtDiscreteHeterogeneityScheduler # keep this after XmippMetaProtCreateSubset and XmippMetaProtCreateOutput
-from .protocol_angular_alignment_sph import XmippProtAngularAlignmentSPH
-from .protocol_volume_deform_sph import XmippProtVolumeDeformSPH
-from .protocol_structure_map_sph import XmippProtStructureMapSPH
+# from .protocol_angular_alignment_sph import XmippProtAngularAlignmentSPH
+# from .protocol_volume_deform_sph import XmippProtVolumeDeformSPH
+# from .protocol_structure_map_sph import XmippProtStructureMapSPH
 from .protocol_align_volume_and_particles import XmippProtAlignVolumeParticles
 from .protocol_local_ctf import XmippProtLocalCTF
 from .protocol_analyze_local_ctf import XmippProtAnalyzeLocalCTF
 from .protocol_consensus_local_ctf import XmippProtConsensusLocalCTF
 from .protocol_particle_pick_remove_duplicates import XmippProtPickingRemoveDuplicates
 from .protocol_metaprotocol_golden_highres import XmippMetaProtGoldenHighRes
+from .protocol_volume_adjust_sub import XmippProtVolAdjust, XmippProtVolSubtraction
+from .protocol_volume_consensus import XmippProtVolConsensus
diff --git a/xmipp3/protocols/protocol_align_volume.py b/xmipp3/protocols/protocol_align_volume.py
index aa62a4d32..6ea0ae89d 100644
--- a/xmipp3/protocols/protocol_align_volume.py
+++ b/xmipp3/protocols/protocol_align_volume.py
@@ -218,10 +218,14 @@ def createOutputStep(self):
             outputArgs = {'outputVolume': vols[0]}
             
         self._defineOutputs(**outputArgs)
-        for pointer in self.inputVolumes:
-            self._defineSourceRelation(pointer, list(outputArgs.values())[0])
-    #--------------------------- INFO functions --------------------------------------------
-    
+        if len(vols) > 1:
+            for pointer in self.inputVolumes:
+                self._defineSourceRelation(pointer, outputArgs['outputVolumes'])
+        else:
+            for pointer in self.inputVolumes:
+                self._defineSourceRelation(pointer, outputArgs['outputVolume'])
+
+    #  --------------------------- INFO functions --------------------------------------------
     def _validate(self):
         errors = []
         for pointer in self.inputVolumes:
diff --git a/xmipp3/protocols/protocol_align_volume_and_particles.py b/xmipp3/protocols/protocol_align_volume_and_particles.py
index 6045c32ef..dba4b4992 100644
--- a/xmipp3/protocols/protocol_align_volume_and_particles.py
+++ b/xmipp3/protocols/protocol_align_volume_and_particles.py
@@ -35,6 +35,8 @@
 from pwem.objects import Transform, Volume
 from pwem.constants import ALIGN_PROJ
 
+from pyworkflow.utils.path import cleanPath
+
 from xmipp3.convert import (rowToAlignment, alignmentToRow, writeSetOfParticles,
                             readSetOfParticles)
 from xmipp3.constants import SYM_URL
@@ -42,6 +44,9 @@
 ALIGN_MASK_CIRCULAR = 0
 ALIGN_MASK_BINARY_FILE = 1
 
+ALIGN_GLOBAL = 0
+ALIGN_LOCAL = 1
+
 
 class XmippProtAlignVolumeParticles(ProtAlignVolume):
     """ 
@@ -72,6 +77,8 @@ def _defineParams(self, form):
                       help='Select one set of particles to be aligned against '
                            'the reference set of particles using the transformation '
                            'calculated with the reference and input volumes.')
+        form.addParam('alignmentMode', params.EnumParam, default=ALIGN_GLOBAL, choices=["Global","Local"],
+                      label="Alignment mode")
         form.addParam('symmetryGroup', params.StringParam, default='c1',
                       label="Symmetry group",
                       help='See %s page for a description of the symmetries '
@@ -148,10 +155,14 @@ def alignVolumeStep(self, maskArgs):
         args = "--i1 %s --i2 %s --apply %s" % \
                (self.fnRefVol, self.fnInputVol, outVolFn)
         args += maskArgs
-        args += " --frm "
+        if self.alignmentMode.get()==ALIGN_GLOBAL:
+            args += " --frm"
+        else:
+            args += " --local"
         args += " --copyGeo %s" % fhInputTranMat        
         self.runJob("xmipp_volume_align", args)
-
+        cleanPath(self.fnRefVol)
+        cleanPath(self.fnInputVol)
 
     def alignParticlesStep(self):
 
@@ -176,6 +187,7 @@ def alignParticlesStep(self):
             alignmentToRow(resultMat, rowOut, ALIGN_PROJ)
             rowOut.addToMd(outputParts)
         outputParts.write(outParticlesFn)
+        cleanPath(self.imgsInputFn)
 
 
     def createOutputStep(self):   
diff --git a/xmipp3/protocols/protocol_angular_alignment_sph.py b/xmipp3/protocols/protocol_angular_alignment_sph.py
index c99064c78..8b6f9770e 100644
--- a/xmipp3/protocols/protocol_angular_alignment_sph.py
+++ b/xmipp3/protocols/protocol_angular_alignment_sph.py
@@ -1,3 +1,11 @@
+########
+
+# This protocol has been removed from Scipion as there is a newer version being developed that
+# will solve some of the problems of this current implementation. The code will be available
+# again in a newer release.
+
+########
+
 
 # **************************************************************************
 # *
@@ -25,162 +33,162 @@
 # *
 # **************************************************************************
 
-import pyworkflow.protocol.params as params
-from pyworkflow.utils.path import moveFile
-
-from pwem.protocols import ProtAnalysis3D
-import pwem.emlib.metadata as md
-from pwem.emlib.image import ImageHandler
-from pwem.constants import ALIGN_PROJ
-
-from xmipp3.convert import (writeSetOfParticles, createItemMatrix,
-                            setXmippAttributes)
-from xmipp3.base import writeInfoField, readInfoField
-import numpy as np
-
-from pyworkflow import VERSION_2_0
-
-
-class XmippProtAngularAlignmentSPH(ProtAnalysis3D):
-    """ Protocol for flexible angular alignment based on spherical harmonics. """
-    _label = 'sph angular align'
-    _lastUpdateVersion = VERSION_2_0
-
-    # --------------------------- DEFINE param functions --------------------------------------------
-    def _defineParams(self, form):
-        form.addSection(label='Input')
-        form.addParam('inputParticles', params.PointerParam, label="Input particles", pointerClass='SetOfParticles')
-        form.addParam('inputVolume', params.PointerParam, label="Input volume", pointerClass='Volume')
-        form.addParam('targetResolution', params.FloatParam, label="Target resolution (A)", default=8.0,
-                      help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
-                           "2/3 of the Fourier spectrum.")
-        form.addParam('depth', params.IntParam, default=3,
-                      label='Harmonical depth', expertLevel=params.LEVEL_ADVANCED,
-                      help='Harmonical depth of the deformation=1,2,3,...')
-        form.addParam('maxShift', params.FloatParam, default=-1,
-                      label='Maximum shift (px)', expertLevel=params.LEVEL_ADVANCED,
-                      help='Maximum shift allowed in pixels')
-        form.addParam('maxAngular', params.FloatParam, default=5,
-                      label='Maximum angular change (degrees)', expertLevel=params.LEVEL_ADVANCED,
-                      help='Maximum angular change allowed (in degrees)')
-        form.addParam('maxResolution', params.FloatParam, default=4.0,
-                      label='Maximum resolution (A)', expertLevel=params.LEVEL_ADVANCED,
-                      help='Maximum resolution (A)')
-        form.addParallelSection(threads=1, mpi=8)
-
-    def _createFilenameTemplates(self):
-        """ Centralize how files are called """
-        myDict = {
-            'imgsFn': self._getExtraPath('input_particles.xmd'),
-            'fnVol': self._getExtraPath('input_volume.vol'),
-            'fnOut': self._getExtraPath('output_particles.xmd'),
-            'fnOutDir': self._getExtraPath()
-                 }
-        self._updateFilenamesDict(myDict)
-
-    # --------------------------- INSERT steps functions --------------------------------------------
-    def _insertAllSteps(self):
-        self._createFilenameTemplates()
-        self._insertFunctionStep("convertStep")
-        self._insertFunctionStep("alignmentStep")
-        self._insertFunctionStep("createOutputStep")
-
-    # --------------------------- STEPS functions ---------------------------------------------------
-    def convertStep(self):
-        imgsFn = self._getFileName('imgsFn')
-        fnVol = self._getFileName('fnVol')
-
-        inputParticles = self.inputParticles.get()
-        writeSetOfParticles(inputParticles, imgsFn)
-        Xdim = inputParticles.getXDim()
-        Ts = inputParticles.getSamplingRate()
-        newTs = self.targetResolution.get() * 1.0 /3.0
-        newTs = max(Ts, newTs)
-        self.newXdim = int(Xdim * Ts / newTs)
-        writeInfoField(self._getExtraPath(), "sampling", md.MDL_SAMPLINGRATE, newTs)
-        writeInfoField(self._getExtraPath(), "size", md.MDL_XSIZE, self.newXdim)
-        if self.newXdim != Xdim:
-            self.runJob("xmipp_image_resize",
-                        "-i %s -o %s --save_metadata_stack %s --fourier %d" %
-                        (imgsFn,
-                         self._getExtraPath('scaled_particles.stk'),
-                         self._getExtraPath('scaled_particles.xmd'),
-                         self.newXdim), numberOfMpi=1)
-            moveFile(self._getExtraPath('scaled_particles.xmd'), imgsFn)
-
-        ih = ImageHandler()
-        ih.convert(self.inputVolume.get(), fnVol)
-        Xdim = self.inputVolume.get().getDim()[0]
-        if Xdim != self.newXdim:
-            self.runJob("xmipp_image_resize"
-                        ,"-i %s --dim %d " %(fnVol, self.newXdim), numberOfMpi=1)
-
-
-    def alignmentStep(self):
-        imgsFn = self._getFileName('imgsFn')
-        fnVol = self._getFileName('fnVol')
-        fnOut =  self._getFileName('fnOut')
-        fnOutDir = self._getFileName('fnOutDir')
-        Ts = readInfoField(self._getExtraPath(), "sampling", md.MDL_SAMPLINGRATE)
-        params = ' -i %s --ref %s -o %s --optimizeAlignment --optimizeDeformation ' \
-                 '--depth %d --max_shift %f --max_angular_change %f --sampling %f ' \
-                 ' --max_resolution %f --odir %s --resume' %\
-                 (imgsFn, fnVol, fnOut, self.depth, self.maxShift, self.maxAngular,
-                  Ts, self.maxResolution, fnOutDir)
-        if self.inputParticles.get().isPhaseFlipped(): #preguntar
-            params += ' --phaseFlipped'
-        self.runJob("xmipp_angular_sph_alignment", params, numberOfMpi=self.numberOfMpi.get())
-
-
-    def createOutputStep(self):
-        from sklearn.manifold import TSNE
-        fnOut = self._getFileName('fnOut')
-        mdOut = md.MetaData(fnOut)
-        i = 0
-        for row in md.iterRows(mdOut):
-            coeffs = mdOut.getValue(md.MDL_SPH_COEFFICIENTS, row.getObjId())
-            if i==0:
-                coeffMatrix = coeffs
-            else:
-                coeffMatrix = np.vstack((coeffMatrix, coeffs))
-            i+=1
-        X_tsne_1d = TSNE(n_components=1).fit_transform(coeffMatrix)
-        X_tsne_2d = TSNE(n_components=2).fit_transform(coeffMatrix)
-
-        newMdOut = md.MetaData()
-        i=0
-        for row in md.iterRows(mdOut):
-            newRow = row
-            newRow.setValue(md.MDL_SPH_TSNE_COEFF1D, float(X_tsne_1d[i,0]))
-            newRow.setValue(md.MDL_SPH_TSNE_COEFF2D, [float(X_tsne_2d[i, 0]),  float(X_tsne_2d[i, 1])])
-            newRow.addToMd(newMdOut)
-            i+=1
-            newMdOut.write(fnOut)
-
-        inputSet = self.inputParticles.get()
-        partSet = self._createSetOfParticles()
-
-        partSet.copyInfo(inputSet)
-        partSet.setAlignmentProj()
-        partSet.copyItems(inputSet,
-                          updateItemCallback=self._updateParticle,
-                          itemDataIterator=md.iterRows(fnOut, sortByLabel=md.MDL_ITEM_ID))
-
-        self._defineOutputs(outputParticles=partSet)
-        self._defineTransformRelation(self.inputParticles, partSet)
-
-
-# --------------------------- UTILS functions --------------------------------------------
-    def _updateParticle(self, item, row):
-        setXmippAttributes(item, row, md.MDL_ANGLE_ROT, md.MDL_ANGLE_TILT,
-                           md.MDL_ANGLE_PSI, md.MDL_SHIFT_X, md.MDL_SHIFT_Y,
-                           md.MDL_FLIP, md.MDL_SPH_DEFORMATION,
-                           md.MDL_SPH_COEFFICIENTS, md.MDL_SPH_TSNE_COEFF1D,
-                           md.MDL_SPH_TSNE_COEFF2D)
-        createItemMatrix(item, row, align=ALIGN_PROJ)
-
-    def getInputParticles(self):
-        return self.inputParticles.get()
+# import pyworkflow.protocol.params as params
+# from pyworkflow.utils.path import moveFile
+#
+# from pwem.protocols import ProtAnalysis3D
+# import pwem.emlib.metadata as md
+# from pwem.emlib.image import ImageHandler
+# from pwem.constants import ALIGN_PROJ
+#
+# from xmipp3.convert import (writeSetOfParticles, createItemMatrix,
+#                             setXmippAttributes)
+# from xmipp3.base import writeInfoField, readInfoField
+# import numpy as np
+#
+# from pyworkflow import VERSION_2_0
+#
+#
+# class XmippProtAngularAlignmentSPH(ProtAnalysis3D):
+#     """ Protocol for flexible angular alignment based on spherical harmonics. """
+#     _label = 'sph angular align'
+#     _lastUpdateVersion = VERSION_2_0
+#
+#     # --------------------------- DEFINE param functions --------------------------------------------
+#     def _defineParams(self, form):
+#         form.addSection(label='Input')
+#         form.addParam('inputParticles', params.PointerParam, label="Input particles", pointerClass='SetOfParticles')
+#         form.addParam('inputVolume', params.PointerParam, label="Input volume", pointerClass='Volume')
+#         form.addParam('targetResolution', params.FloatParam, label="Target resolution (A)", default=8.0,
+#                       help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
+#                            "2/3 of the Fourier spectrum.")
+#         form.addParam('depth', params.IntParam, default=3,
+#                       label='Harmonical depth', expertLevel=params.LEVEL_ADVANCED,
+#                       help='Harmonical depth of the deformation=1,2,3,...')
+#         form.addParam('maxShift', params.FloatParam, default=-1,
+#                       label='Maximum shift (px)', expertLevel=params.LEVEL_ADVANCED,
+#                       help='Maximum shift allowed in pixels')
+#         form.addParam('maxAngular', params.FloatParam, default=5,
+#                       label='Maximum angular change (degrees)', expertLevel=params.LEVEL_ADVANCED,
+#                       help='Maximum angular change allowed (in degrees)')
+#         form.addParam('maxResolution', params.FloatParam, default=4.0,
+#                       label='Maximum resolution (A)', expertLevel=params.LEVEL_ADVANCED,
+#                       help='Maximum resolution (A)')
+#         form.addParallelSection(threads=1, mpi=8)
+#
+#     def _createFilenameTemplates(self):
+#         """ Centralize how files are called """
+#         myDict = {
+#             'imgsFn': self._getExtraPath('input_particles.xmd'),
+#             'fnVol': self._getExtraPath('input_volume.vol'),
+#             'fnOut': self._getExtraPath('output_particles.xmd'),
+#             'fnOutDir': self._getExtraPath()
+#                  }
+#         self._updateFilenamesDict(myDict)
+#
+#     # --------------------------- INSERT steps functions --------------------------------------------
+#     def _insertAllSteps(self):
+#         self._createFilenameTemplates()
+#         self._insertFunctionStep("convertStep")
+#         self._insertFunctionStep("alignmentStep")
+#         self._insertFunctionStep("createOutputStep")
+#
+#     # --------------------------- STEPS functions ---------------------------------------------------
+#     def convertStep(self):
+#         imgsFn = self._getFileName('imgsFn')
+#         fnVol = self._getFileName('fnVol')
+#
+#         inputParticles = self.inputParticles.get()
+#         writeSetOfParticles(inputParticles, imgsFn)
+#         Xdim = inputParticles.getXDim()
+#         Ts = inputParticles.getSamplingRate()
+#         newTs = self.targetResolution.get() * 1.0 /3.0
+#         newTs = max(Ts, newTs)
+#         self.newXdim = int(Xdim * Ts / newTs)
+#         writeInfoField(self._getExtraPath(), "sampling", md.MDL_SAMPLINGRATE, newTs)
+#         writeInfoField(self._getExtraPath(), "size", md.MDL_XSIZE, self.newXdim)
+#         if self.newXdim != Xdim:
+#             self.runJob("xmipp_image_resize",
+#                         "-i %s -o %s --save_metadata_stack %s --fourier %d" %
+#                         (imgsFn,
+#                          self._getExtraPath('scaled_particles.stk'),
+#                          self._getExtraPath('scaled_particles.xmd'),
+#                          self.newXdim), numberOfMpi=1)
+#             moveFile(self._getExtraPath('scaled_particles.xmd'), imgsFn)
+#
+#         ih = ImageHandler()
+#         ih.convert(self.inputVolume.get(), fnVol)
+#         Xdim = self.inputVolume.get().getDim()[0]
+#         if Xdim != self.newXdim:
+#             self.runJob("xmipp_image_resize"
+#                         ,"-i %s --dim %d " %(fnVol, self.newXdim), numberOfMpi=1)
+#
+#
+#     def alignmentStep(self):
+#         imgsFn = self._getFileName('imgsFn')
+#         fnVol = self._getFileName('fnVol')
+#         fnOut =  self._getFileName('fnOut')
+#         fnOutDir = self._getFileName('fnOutDir')
+#         Ts = readInfoField(self._getExtraPath(), "sampling", md.MDL_SAMPLINGRATE)
+#         params = ' -i %s --ref %s -o %s --optimizeAlignment --optimizeDeformation ' \
+#                  '--depth %d --max_shift %f --max_angular_change %f --sampling %f ' \
+#                  ' --max_resolution %f --odir %s --resume' %\
+#                  (imgsFn, fnVol, fnOut, self.depth, self.maxShift, self.maxAngular,
+#                   Ts, self.maxResolution, fnOutDir)
+#         if self.inputParticles.get().isPhaseFlipped(): #preguntar
+#             params += ' --phaseFlipped'
+#         self.runJob("xmipp_angular_sph_alignment", params, numberOfMpi=self.numberOfMpi.get())
+#
+#
+#     def createOutputStep(self):
+#         from sklearn.manifold import TSNE
+#         fnOut = self._getFileName('fnOut')
+#         mdOut = md.MetaData(fnOut)
+#         i = 0
+#         for row in md.iterRows(mdOut):
+#             coeffs = mdOut.getValue(md.MDL_SPH_COEFFICIENTS, row.getObjId())
+#             if i==0:
+#                 coeffMatrix = coeffs
+#             else:
+#                 coeffMatrix = np.vstack((coeffMatrix, coeffs))
+#             i+=1
+#         X_tsne_1d = TSNE(n_components=1).fit_transform(coeffMatrix)
+#         X_tsne_2d = TSNE(n_components=2).fit_transform(coeffMatrix)
+#
+#         newMdOut = md.MetaData()
+#         i=0
+#         for row in md.iterRows(mdOut):
+#             newRow = row
+#             newRow.setValue(md.MDL_SPH_TSNE_COEFF1D, float(X_tsne_1d[i,0]))
+#             newRow.setValue(md.MDL_SPH_TSNE_COEFF2D, [float(X_tsne_2d[i, 0]),  float(X_tsne_2d[i, 1])])
+#             newRow.addToMd(newMdOut)
+#             i+=1
+#             newMdOut.write(fnOut)
+#
+#         inputSet = self.inputParticles.get()
+#         partSet = self._createSetOfParticles()
+#
+#         partSet.copyInfo(inputSet)
+#         partSet.setAlignmentProj()
+#         partSet.copyItems(inputSet,
+#                           updateItemCallback=self._updateParticle,
+#                           itemDataIterator=md.iterRows(fnOut, sortByLabel=md.MDL_ITEM_ID))
+#
+#         self._defineOutputs(outputParticles=partSet)
+#         self._defineTransformRelation(self.inputParticles, partSet)
+#
+#
+# # --------------------------- UTILS functions --------------------------------------------
+#     def _updateParticle(self, item, row):
+#         setXmippAttributes(item, row, md.MDL_ANGLE_ROT, md.MDL_ANGLE_TILT,
+#                            md.MDL_ANGLE_PSI, md.MDL_SHIFT_X, md.MDL_SHIFT_Y,
+#                            md.MDL_FLIP, md.MDL_SPH_DEFORMATION,
+#                            md.MDL_SPH_COEFFICIENTS, md.MDL_SPH_TSNE_COEFF1D,
+#                            md.MDL_SPH_TSNE_COEFF2D)
+#         createItemMatrix(item, row, align=ALIGN_PROJ)
+#
+#     def getInputParticles(self):
+#         return self.inputParticles.get()
 
 
 
diff --git a/xmipp3/protocols/protocol_center_particles.py b/xmipp3/protocols/protocol_center_particles.py
index 2781bf07c..11982cc2b 100644
--- a/xmipp3/protocols/protocol_center_particles.py
+++ b/xmipp3/protocols/protocol_center_particles.py
@@ -140,7 +140,6 @@ def realignStep(self):
                         md.MDL_XCOOR)+int(centerPoint[0]))
                     rowOut.setValue(md.MDL_YCOOR, rowOut.getValue(
                         md.MDL_YCOOR)+int(centerPoint[1]))
-
                     rowOut.addToMd(mdNewClass)
                 mdNewClass.write(block + "@" + self._getExtraPath(
                     'final_classes.xmd'), MD_APPEND)
@@ -229,7 +228,6 @@ def _fillParticles(self, outputParticles, outputCoords, inputParticles):
         for row in md.iterRows(myParticles):
             #To create the new particle
             p = rowToParticle(row)
-            outputParticles.append(p)
 
             #To create the new coordinate
             newCoord = Coordinate()
@@ -249,6 +247,9 @@ def _fillParticles(self, outputParticles, outputCoords, inputParticles):
                 newCoord.setPosition(x * scale, y * scale)
                 newCoord.setMicrograph(mic)
                 outputCoords.append(newCoord)
+                p.setCoordinate(newCoord)
+            #Storing the new particle
+            outputParticles.append(p)
 
         boxSize = inputParticles.getXDim() * scale
         outputCoords.setBoxSize(boxSize)
diff --git a/xmipp3/protocols/protocol_consensus_classes3D.py b/xmipp3/protocols/protocol_consensus_classes3D.py
index 614ae6091..86b64cc79 100644
--- a/xmipp3/protocols/protocol_consensus_classes3D.py
+++ b/xmipp3/protocols/protocol_consensus_classes3D.py
@@ -146,6 +146,7 @@ def createOutputStep(self):
             # newClass.copyInfo(clRep)
             newClass.setAcquisition(clRep.getAcquisition())
             newClass.setRepresentative(clRep.getRepresentative())
+            newClass.setSamplingRate(clRep.getSamplingRate())
 
             outputClasses.append(newClass)
 
diff --git a/xmipp3/protocols/protocol_convert_pdb.py b/xmipp3/protocols/protocol_convert_pdb.py
index 1329d2e69..c58e9800c 100644
--- a/xmipp3/protocols/protocol_convert_pdb.py
+++ b/xmipp3/protocols/protocol_convert_pdb.py
@@ -32,8 +32,8 @@
 import pyworkflow.protocol.constants as const
 from pyworkflow.utils import replaceBaseExt, removeExt, getExt
 
-from pwem.convert import cifToPdb, downloadPdb
-from pwem.objects import Volume
+from pwem.convert import cifToPdb, downloadPdb, headers
+from pwem.objects import Volume, Transform
 from pwem.protocols import ProtInitialVolume
 
 
@@ -44,7 +44,7 @@ class XmippProtConvertPdb(ProtInitialVolume):
     IMPORT_OBJ = 1
     IMPORT_FROM_FILES = 2 
        
-    #--------------------------- DEFINE param functions --------------------------------------------
+    # --------------------------- DEFINE param functions --------------------------------------------
     def _defineParams(self, form):
         """ Define the parameters that will be input for the Protocol.
         This definition is also used to generate automatically the GUI.
@@ -66,16 +66,29 @@ def _defineParams(self, form):
         form.addParam('sampling', params.FloatParam, default=1.0, 
                       label="Sampling rate (Å/px)",
                       help='Sampling rate (Angstroms/pixel)')
-        form.addParam('setSize', params.BooleanParam, label='Set final size?', default=False)
-        form.addParam('size', params.IntParam, condition='setSize', allowsNull=True, 
-                      label="Final size (px)",
-                      help='Final size in pixels. If no value is provided, protocol will estimate it.')
+        form.addParam('vol', params.BooleanParam, label='Use a volume as an empty template?', default=False,
+                      help='Use an existing volume to define the size and origin for the output volume. If this option'
+                           'is selected, make sure that "Center PDB" in advanced parameters is set to *No*.')
+        form.addParam('volObj', params.PointerParam, pointerClass='Volume',
+                      label="Input volume ", condition='vol', allowsNull=True,
+                      help='The origin and the final size of the output volume will be taken from this volume.')
+        form.addParam('setSize', params.BooleanParam, label='Set final size?', default=False, condition='vol == False')
+        form.addParam('size_z', params.IntParam, condition='setSize', allowsNull=True,
+                      label="Final size (px) Z",
+                      help='Final size in Z in pixels. If no value is provided, protocol will estimate it.')
+        form.addParam('size_y', params.IntParam, condition='setSize', allowsNull=True,
+                      label="Final size (px) Y",
+                      help='Final size in Y in pixels. If no value is provided, protocol will estimate it.')
+        form.addParam('size_x', params.IntParam, condition='setSize', allowsNull=True,
+                      label="Final size (px) X",
+                      help='Final size in X in pixels. If desired output size is x = y = z you can only fill this '
+                           'field. If no value is provided, protocol will estimate it.')
         form.addParam('centerPdb', params.BooleanParam, default=True, 
                       expertLevel=const.LEVEL_ADVANCED, 
                       label="Center PDB",
                       help='Center PDB with the center of mass')
     
-    #--------------------------- INSERT steps functions --------------------------------------------
+    # --------------------------- INSERT steps functions --------------------------------------------
     def _insertAllSteps(self):
         """ In this function the steps that are going to be executed should
         be defined. Two of the most used functions are: _insertFunctionStep or _insertRunJobStep
@@ -85,7 +98,7 @@ def _insertAllSteps(self):
         self._insertFunctionStep('convertPdbStep')
         self._insertFunctionStep('createOutput')
     
-    #--------------------------- STEPS functions --------------------------------------------
+    # --------------------------- STEPS functions --------------------------------------------
     def pdbDownloadStep(self):
         """Download all pdb files in file_list and unzip them."""
         downloadPdb(self.pdbId.get(), self._getPdbFileName(), self._log)
@@ -101,19 +114,34 @@ def convertPdbStep(self):
             cifToPdb(pdbFn, pdbFn2)
             pdbFn = pdbFn2
 
-        args = '-i %s --sampling %f -o %s' % (pdbFn, self.sampling.get(), outFile)
+        samplingR = self.sampling.get()
+
+        args = '-i %s --sampling %f -o %s' % (pdbFn, samplingR, outFile)
         
         if self.centerPdb:
             args += ' --centerPDB'
-        
+
+        if self.vol:
+            vol = self.volObj.get()
+            size = vol.getDim()
+            ccp4header = headers.Ccp4Header(vol.getFileName(), readHeader=True)
+            self.shifts = ccp4header.getOrigin()
+            args += ' --size %d %d %d --orig %d %d %d' % (size[2], size[1], size[0],
+                                                          self.shifts[0]/samplingR,
+                                                          self.shifts[1]/samplingR,
+                                                          self.shifts[2]/samplingR)
+
         if self.setSize:
             args += ' --size'
-            
-        if self.size.hasValue():
-            args += ' %d' % self.size.get()
+
+        if self.size_x.hasValue():
+            args += ' %d' % self.size_x.get()
+
+        if self.size_y.hasValue() and self.size_z.hasValue():
+            args += ' %d %d' % (self.size_y.get(), self.size_z.get())
 
         self.info("Input file: " + pdbFn)
-        self.info("Output file: " +outFile)
+        self.info("Output file: " + outFile)
         
         program = "xmipp_volume_from_pdb"
         self.runJob(program, args)
@@ -122,11 +150,15 @@ def createOutput(self):
         volume = Volume()
         volume.setSamplingRate(self.sampling.get())
         volume.setFileName(self._getVolName())
+        if self.vol:
+            origin = Transform()
+            origin.setShiftsTuple(self.shifts)
+            volume.setOrigin(origin)
         self._defineOutputs(outputVolume=volume)
         if self.inputPdbData == self.IMPORT_OBJ:
             self._defineSourceRelation(self.pdbObj, volume)
     
-    #--------------------------- INFO functions --------------------------------------------
+    # --------------------------- INFO functions --------------------------------------------
     def _summary(self):
         """ Even if the full set of parameters is available, this function provides
         summary information about an specific run.
@@ -157,7 +189,7 @@ def _validate(self):
         
         return errors
     
-    #--------------------------- UTLIS functions --------------------------------------------
+    # --------------------------- UTLIS functions --------------------------------------------
     def _getPdbFileName(self):
         if self.inputPdbData == self.IMPORT_FROM_ID:
             return self._getExtraPath('%s.cif' % self.pdbId.get())
@@ -168,4 +200,3 @@ def _getPdbFileName(self):
     
     def _getVolName(self):
         return self._getExtraPath(replaceBaseExt(self._getPdbFileName(), "vol"))
-    
diff --git a/xmipp3/protocols/protocol_create_gallery.py b/xmipp3/protocols/protocol_create_gallery.py
index 09b3ed8b9..781fb87d6 100644
--- a/xmipp3/protocols/protocol_create_gallery.py
+++ b/xmipp3/protocols/protocol_create_gallery.py
@@ -72,6 +72,9 @@ def _defineParams(self, form):
         form.addParam('maxFreq',FloatParam, default=0.25,
                       expertLevel=LEVEL_ADVANCED,
                       label='Maximum frequency', help="Normalized to 0.5")
+        form.addParam('shiftSigma',FloatParam, default=0.0,
+                      expertLevel=LEVEL_ADVANCED,
+                      label='Shift sigma', help="In pixels")
 
     #--------------------------- INSERT steps functions ------------------------
     def _insertAllSteps(self):
@@ -98,7 +101,8 @@ def createGallery(self):
 _projTiltRandomness   even 
 _projPsiRange    '0 0 1'
 _projPsiRandomness   even 
-""" % (xdim, xdim, self.rot0, self.rotF,rotN, self.tilt0, self.tiltF, tiltN)
+_noiseCoord '%f 0'
+""" % (xdim, xdim, self.rot0, self.rotF,rotN, self.tilt0, self.tiltF, tiltN, self.shiftSigma)
         fhParam = open(self._getExtraPath("projectionParameters.xmd"), 'w')
         fhParam.write(paramContent)
         fhParam.close()
diff --git a/xmipp3/protocols/protocol_ctf_consensus.py b/xmipp3/protocols/protocol_ctf_consensus.py
index f46505952..8f5bc929a 100644
--- a/xmipp3/protocols/protocol_ctf_consensus.py
+++ b/xmipp3/protocols/protocol_ctf_consensus.py
@@ -444,7 +444,7 @@ def fillOutput(self, ctfSet, micSet, newDone, label):
                     setAttribute(ctf, '_ctf2_defocusAngle_diff',
                                  anglesDifference(ctf.getDefocusAngle(),
                                                   ctf2.getDefocusAngle()))
-                    if ctf.hasPhaseShift() and ctf2.hasPhaseShit():
+                    if ctf.hasPhaseShift() and ctf2.hasPhaseShift():
                         setAttribute(ctf, '_ctf2_phaseShift_diff',
                                      anglesDifference(ctf.getPhaseShift(),
                                                       ctf2.getPhaseShift()))
@@ -464,7 +464,7 @@ def fillOutput(self, ctfSet, micSet, newDone, label):
                                                         ctf2.getDefocusAngle())
                         ctf.setStandardDefocus(newDefocusU, newDefocusV,
                                                newDefocusAngle)
-                        if ctf.hasPhaseShift() and ctf2.hasPhaseShit():
+                        if ctf.hasPhaseShift() and ctf2.hasPhaseShift():
                             newPhaseShift = averageAngles(ctf.getPhaseShift(),
                                                           ctf2.getPhaseShift())
                             ctf.setPhaseShift(newPhaseShift)
diff --git a/xmipp3/protocols/protocol_deep_align.py b/xmipp3/protocols/protocol_deep_align.py
new file mode 100644
index 000000000..7a9fb0bb9
--- /dev/null
+++ b/xmipp3/protocols/protocol_deep_align.py
@@ -0,0 +1,841 @@
+# **************************************************************************
+# *
+# * Authors:     Amaya Jimenez
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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'
+# *
+# **************************************************************************
+
+from pyworkflow import VERSION_3_0
+from pyworkflow.protocol import STEPS_PARALLEL
+from pyworkflow.protocol.params import (PointerParam, StringParam, FloatParam,
+                                        IntParam, BooleanParam, GPU_LIST)
+from pyworkflow.protocol.constants import LEVEL_ADVANCED
+from pyworkflow.utils.path import moveFile, cleanPattern
+from pwem.protocols import ProtRefine3D
+from xmipp3.base import writeInfoField, readInfoField
+from pwem.emlib.metadata import iterRows, getFirstRow
+import pwem.emlib.metadata as md
+from xmipp3.convert import createItemMatrix, setXmippAttributes, readSetOfParticles
+from pwem import ALIGN_PROJ
+from pwem import emlib
+from pwem.emlib.image import ImageHandler
+import os
+import sys
+import numpy as np
+import math
+from shutil import copy
+from os import remove
+from os.path import exists, join
+import xmipp3
+
+class XmippProtDeepAlign(ProtRefine3D, xmipp3.XmippProtocol):
+    """Performs an angular assignment using deep learning"""
+    _label = 'deep align'
+    _lastUpdateVersion = VERSION_3_0
+    _conda_env = 'xmipp_DLTK_v0.3'
+    _ih = ImageHandler()
+
+    def __init__(self, **args):
+        ProtRefine3D.__init__(self, **args)
+        self.stepsExecutionMode = STEPS_PARALLEL
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    def _defineParams(self, form):
+        form.addHidden(GPU_LIST, StringParam, default='0',
+                       expertLevel=LEVEL_ADVANCED,
+                       label="Choose GPU IDs",
+                       help="GPU may have several cores. Set it to zero"
+                            " if you do not know what we are talking about."
+                            " First core index is 0, second 1 and so on.")
+        form.addSection(label='Input')
+        form.addParam('inputSet', PointerParam, label="Input images",
+                      pointerClass='SetOfParticles')
+        form.addParam('inputVolume', PointerParam, label="Volume",
+                      pointerClass='Volume')
+
+        form.addParam('modelPretrain', BooleanParam, default=False,
+                      label='Choose your if you want to use pretrained models',
+                      help='Setting "yes" you can choose previously trained models. '
+                           'If you choose "no" new models will be trained.')
+        form.addParam('pretrainedModels', PointerParam,
+                      pointerClass=self.getClassName(),
+                      condition='modelPretrain==True',
+                      label='Set pretrained models',
+                      help='Choose the protocol where your models were trained. '
+                           'Be careful with using proper models for your new prediction.')
+
+        form.addParam('inputTrainSet', PointerParam, label="Input training set",
+                      pointerClass='SetOfParticles',
+                      pointerCondition='hasAlignmentProj',
+                      help='The set of particles previously aligned to be used as training set',
+                      condition='modelPretrain==False')
+        form.addParam('targetResolution', FloatParam, label="Target resolution",
+                      default=3.0,
+                      help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
+                           "2/3 of the Fourier spectrum.")
+        form.addParam('symmetryGroup', StringParam, default="c1",
+                      label='Symmetry group',
+                      help='If no symmetry is present, give c1')
+        form.addParam('numEpochs', IntParam,
+                      label="Number of epochs for training",
+                      default=10,
+                      help="Number of epochs for training.",
+                      condition='modelPretrain==False')
+        form.addParam('batchSize', IntParam,
+                      label="Batch size for training",
+                      default=128,
+                      help="Batch size for training.",
+                      condition='modelPretrain==False')
+        form.addParam('spanConesTilt', FloatParam,
+                      label="Distance between region centers",
+                      default=30,
+                      help="Distance in degrees between region centers.",
+                      condition='modelPretrain==False')
+        form.addParam('numConesSelected', IntParam,
+                      label="Number of selected regions per image",
+                      default=2,
+                      help="Number of selected regions per image.")
+        form.addParam('applyCTF', BooleanParam, default=False,
+                      label='Correct CTF',
+                      help='Setting "yes" a wiener filter will be applied to correct the ctf in the input particles. ')
+        form.addParam('gpuAlign', BooleanParam, label="Use GPU alignment", default=True,
+                      help='Use GPU alignment algorithm to determine the final 3D alignment parameters')
+        form.addParam('myMPI', IntParam, label="Xmipp MPIs", default=8,
+                      help='Number of MPI to run the Xmipp programs to prepare the input images sets.')
+
+        form.addParallelSection(threads=8, mpi=1)
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAllSteps(self):
+
+        deps = []
+        deps2 = []
+
+        self.lastIter = 0
+        self.imgsFn = self._getExtraPath('input_imgs.xmd')
+        self.trainImgsFn = self._getExtraPath('train_input_imgs.xmd')
+
+        firstStepId = self._insertFunctionStep("convertStep")
+        firstStepId = self._insertFunctionStep("computeTrainingSet", 'projections', prerequisites=[firstStepId])
+
+        # Trainig steps
+        firstStepId = self._insertFunctionStep("prepareImagesForTraining", prerequisites=[firstStepId])
+
+        if self.useQueueForSteps() or self.useQueue():
+            myStr = os.environ["CUDA_VISIBLE_DEVICES"]
+        else:
+            myStr = self.gpuList.get()
+            os.environ["CUDA_VISIBLE_DEVICES"] = self.gpuList.get()
+
+        numGPU = myStr.split(',')
+        for idx, gpuId in enumerate(numGPU):
+            stepId = self._insertFunctionStep("trainNClassifiers2ClassesStep", idx, gpuId, len(numGPU),
+                                              prerequisites=[firstStepId])
+            deps.append(stepId)
+
+        # Predict step
+        predictStepId = self._insertFunctionStep("predictStep", numGPU[0], prerequisites=deps)
+
+        # Correlation step
+        if self.gpuAlign:
+            for idx, gpuId in enumerate(numGPU):
+                stepId = self._insertFunctionStep("correlationCudaStep", idx, str(idx), len(numGPU),
+                                                  prerequisites=[predictStepId])
+                deps2.append(stepId)
+        else:
+            stepId = self._insertFunctionStep("correlationSignificantStep", prerequisites=[predictStepId])
+            deps2.append(stepId)
+
+        stepId = self._insertFunctionStep("createOutputMetadataStep", prerequisites=deps2)
+
+        self._insertFunctionStep("createOutputStep", prerequisites=[stepId])
+
+    # --------------------------- STEPS functions ---------------------------------------------------
+    def convertStep(self):
+
+        if self.modelPretrain.get() is True:
+            fnPreProtocol = self.pretrainedModels.get()._getExtraPath()
+            preXDim = readInfoField(fnPreProtocol, "size", emlib.MDL_XSIZE)
+            self.inputTrainSet = self.pretrainedModels.get().inputTrainSet
+
+        from ..convert import writeSetOfParticles
+        inputParticles = self.inputSet.get()
+        writeSetOfParticles(inputParticles, self.imgsFn)
+
+        Ts = inputParticles.getSamplingRate()
+        row = getFirstRow(self.imgsFn)
+        hasCTF = row.containsLabel(emlib.MDL_CTF_DEFOCUSU) or emlib.containsLabel(
+                    emlib.MDL_CTF_MODEL)
+
+        fnCorrected = self.imgsFn
+        
+        if hasCTF and self.applyCTF.get() is True:
+            fnCorrectedStk = self._getExtraPath('corrected_particles.stk')
+            fnCorrected = self._getExtraPath('corrected_particles.xmd')
+            args = "-i %s -o %s --save_metadata_stack %s --keep_input_columns" % (
+                    self.imgsFn, fnCorrectedStk, fnCorrected)
+            args += " --sampling_rate %f --correct_envelope" % Ts
+            if self.inputSet.get().isPhaseFlipped():
+                args += " --phase_flipped"
+            self.runJob("xmipp_ctf_correct_wiener2d", args,numberOfMpi=self.myMPI.get())
+
+        Xdim = inputParticles.getXDim()
+        newTs = self.targetResolution.get() * 1.0 / 3.0
+        newTs = max(Ts, newTs)
+        if self.modelPretrain.get() is False:
+            self.newXdim = int(float(Xdim * Ts / newTs))
+        else:
+            self.newXdim = int(preXDim)
+        self.firstMaxShift = int(round(self.newXdim / 10))
+        writeInfoField(self._getExtraPath(), "sampling",
+                       emlib.MDL_SAMPLINGRATE, newTs)
+        writeInfoField(self._getExtraPath(), "size", emlib.MDL_XSIZE,
+                       self.newXdim)
+        if self.newXdim != Xdim:
+            self.runJob("xmipp_image_resize",
+                        "-i %s -o %s --save_metadata_stack %s --fourier %d" %
+                        (fnCorrected,
+                         self._getExtraPath('scaled_particles.stk'),
+                         self._getExtraPath('scaled_particles.xmd'),
+                         self.newXdim), numberOfMpi=self.myMPI.get())
+            moveFile(self._getExtraPath('scaled_particles.xmd'), self.imgsFn)
+
+        if exists(self._getExtraPath('corrected_particles.stk')):
+            remove(self._getExtraPath('corrected_particles.stk'))
+            remove(self._getExtraPath('corrected_particles.xmd'))
+
+        fnVol = self._getTmpPath("volume.vol")
+        self._ih.convert(self.inputVolume.get(), fnVol)
+        Xdim = self.inputVolume.get().getDim()[0]
+        if Xdim != self.newXdim:
+            self.runJob("xmipp_image_resize",
+                        "-i %s --fourier %d" % (fnVol, self.newXdim),
+                        numberOfMpi=self.myMPI.get())
+
+        inputTrain = self.inputTrainSet.get()
+        writeSetOfParticles(inputTrain, self.trainImgsFn)
+        row = getFirstRow(self.trainImgsFn)
+        hasCTF = row.containsLabel(emlib.MDL_CTF_DEFOCUSU) or emlib.containsLabel(
+                    emlib.MDL_CTF_MODEL)
+
+        fnCorrected = self.trainImgsFn
+
+        if hasCTF and self.applyCTF.get() is True:
+            fnCorrectedStk = self._getExtraPath('corrected_particles.stk')
+            fnCorrected = self._getExtraPath('corrected_particles.xmd')
+            args = "-i %s -o %s --save_metadata_stack %s --keep_input_columns" % (
+                    self.trainImgsFn, fnCorrectedStk, fnCorrected)
+            args += " --sampling_rate %f --correct_envelope" % Ts
+            if self.inputSet.get().isPhaseFlipped():
+                args += " --phase_flipped"
+            self.runJob("xmipp_ctf_correct_wiener2d", args,numberOfMpi=self.myMPI.get())
+
+        Xdim = inputTrain.getXDim()
+        if self.newXdim != Xdim:
+            self.runJob("xmipp_image_resize",
+                        "-i %s -o %s --save_metadata_stack %s --fourier %d" %
+                        (fnCorrected,
+                         self._getExtraPath('scaled_train_particles.stk'),
+                         self._getExtraPath('scaled_train_particles.xmd'),
+                         self.newXdim), numberOfMpi=self.myMPI.get())
+            moveFile(self._getExtraPath('scaled_train_particles.xmd'),
+                     self.trainImgsFn)
+
+        if exists(self._getExtraPath('corrected_particles.stk')):
+            remove(self._getExtraPath('corrected_particles.stk'))
+            remove(self._getExtraPath('corrected_particles.xmd'))
+
+    def generateConeCenters(self, fn):
+        fnVol = self._getTmpPath("volume.vol")
+        fnCenters = self._getExtraPath(fn + ".stk")
+        fnCentersMd = self._getExtraPath(fn + ".doc")
+        # if self.spanConesTilt.get()>30:
+        #     sampling_rate = 30
+        # else:
+        #     sampling_rate = self.spanConesTilt.get()
+
+        self.runJob("xmipp_angular_project_library",
+                    "-i %s -o %s --sym c1 --sampling_rate %d"
+                    % (fnVol, fnCenters, self.spanConesTilt.get()),
+                    numberOfMpi=self.myMPI.get())
+        mdExp = emlib.MetaData(fnCentersMd)
+        return mdExp.size()
+
+    def angularDistance(self, rot, tilt, mdCones):
+        # Angular distance between particle and region center
+        dist = []
+        for row in iterRows(mdCones):
+            rotCenterCone = row.getValue(emlib.MDL_ANGLE_ROT)
+            tiltCenterCone = row.getValue(emlib.MDL_ANGLE_TILT)
+            if rotCenterCone < 0:
+                rotCenterCone = rotCenterCone + 360
+            if tiltCenterCone < 0:
+                tiltCenterCone = tiltCenterCone + 360
+            srot = math.sin(math.radians(rot))
+            crot = math.cos(math.radians(rot))
+            stilt = math.sin(math.radians(tilt))
+            ctilt = math.cos(math.radians(tilt))
+            srotCone = math.sin(math.radians(rotCenterCone))
+            crotCone = math.cos(math.radians(rotCenterCone))
+            stiltCone = math.sin(math.radians(tiltCenterCone))
+            ctiltCone = math.cos(math.radians(tiltCenterCone))
+            aux = (stilt * crot * stiltCone * crotCone) + (
+                    stilt * srot * stiltCone * srotCone) + (
+                          ctilt * ctiltCone)
+            auxA = aux
+            if aux < -1:
+                auxA = -1
+            elif aux > 1:
+                auxA = 1
+            auxAcos = math.degrees(math.acos(auxA))
+            dist.append(auxAcos)
+        minDist = min(dist)
+        finalCone = dist.index(minDist) + 1
+        return finalCone
+
+    def computeTrainingSet(self, nameTrain):
+
+        totalCones = self.generateConeCenters('coneCenters')
+        self.numCones = totalCones
+        fnCentersMd = self._getExtraPath("coneCenters.doc")
+        mdCones = emlib.MetaData(fnCentersMd)
+
+        auxList = []
+
+        mdTrain = emlib.MetaData(self.trainImgsFn)
+        mdList = []
+        for i in range(totalCones):
+            mdList.append(emlib.MetaData())
+        for row in iterRows(mdTrain):
+            rot = row.getValue(emlib.MDL_ANGLE_ROT)
+            tilt = row.getValue(emlib.MDL_ANGLE_TILT)
+            flip = row.getValue(emlib.MDL_FLIP)
+            if flip:
+                tilt = tilt + 180
+            if rot < 0:
+                rot = rot + 360
+            if tilt < 0:
+                tilt = tilt + 360
+            numCone = self.angularDistance(rot, tilt, mdCones)
+            mdCone = mdList[numCone - 1]
+            auxList.append(numCone - 1)
+            row.addToMd(mdCone)
+        for i in range(totalCones):
+            fnTrain = self._getExtraPath(nameTrain + "%d.xmd" % (i + 1))
+            mdList[i].write(fnTrain)
+
+
+    def prepareImagesForTraining(self):
+
+        fnCentersMd = self._getExtraPath("coneCenters.doc")
+        mdCones = emlib.MetaData(fnCentersMd)
+        span = self.spanConesTilt.get()
+        counterCones = 0
+        for row in iterRows(mdCones):
+            rotCenter = row.getValue(emlib.MDL_ANGLE_ROT)
+            tiltCenter = row.getValue(emlib.MDL_ANGLE_TILT)
+            if rotCenter < 0:
+                rotCenter = rotCenter + 360
+            if tiltCenter < 0:
+                tiltCenter = tiltCenter + 360
+            iniRot = rotCenter - span
+            endRot = rotCenter + span
+            iniTilt = tiltCenter - span
+            endTilt = tiltCenter + span
+            if iniRot < 0:
+                iniRot = iniRot + 360
+            if iniTilt < 0:
+                iniTilt = iniTilt + 360
+            if endRot < 0:
+                endRot = endRot + 360
+            if endTilt < 0:
+                endTilt = endTilt + 360
+            mdProj = emlib.MetaData(self._getExtraPath('projections%d.xmd' % (counterCones + 1)))
+            sizeProj = mdProj.size()
+            if sizeProj > 0:
+                lastLabel = counterCones + 1
+                self.projectStep(300, iniRot, endRot, iniTilt, endTilt,
+                                 'projectionsCudaCorr', counterCones + 1)
+                if self.modelPretrain.get() is False:
+                    self.generateExpImagesStep(10000, 'projections',
+                                                   'projectionsExp',
+                                                   counterCones + 1)
+            else:
+                remove(self._getExtraPath('projections%d.xmd' % (counterCones + 1)))
+            counterCones = counterCones + 1
+
+        if self.modelPretrain.get() is False:
+            fnToFilter = self._getExtraPath('projectionsExp%d.xmd' % (lastLabel))
+            self.runJob("xmipp_transform_filter", " -i %s --fourier low_pass %f" %
+                        (fnToFilter, 0.15), numberOfMpi=self.myMPI.get())
+
+    def projectStep(self, numProj, iniRot, endRot, iniTilt, endTilt, fn, idx):
+
+        newXdim = readInfoField(self._getExtraPath(), "size",
+                                emlib.MDL_XSIZE)
+        fnVol = self._getTmpPath("volume.vol")
+
+        uniformProjectionsStr = """
+# XMIPP_STAR_1 *
+data_block1
+_dimensions2D   '%d %d'
+_projRotRange    '%d %d %d'
+_projRotRandomness   random
+_projRotNoise   '0'
+_projTiltRange    '%d %d 1'
+_projTiltRandomness   random
+_projTiltNoise   '0'
+_projPsiRange    '0 0 1'
+_projPsiRandomness   random
+_projPsiNoise   '0'
+_noisePixelLevel   '0'
+_noiseCoord   '0'
+""" % (newXdim, newXdim, iniRot, endRot, numProj, iniTilt, endTilt)
+        fnParams = self._getExtraPath("uniformProjections%d.xmd" % idx)
+        fh = open(fnParams, "w")
+        fh.write(uniformProjectionsStr)
+        fh.close()
+
+        fnProjs = self._getExtraPath(fn + "%d.stk" % idx)
+        self.runJob("xmipp_phantom_project",
+                    "-i %s -o %s --method fourier 1 0.5 "
+                    "--params %s" % (fnVol, fnProjs, fnParams), numberOfMpi=1)
+
+        fnProjsXmd=fnProjs[:-3]+'xmd'
+        self.runJob("xmipp_metadata_utilities",
+                    "-i %s --fill ref lineal 1 1 " % (fnProjsXmd), numberOfMpi=1)
+
+        cleanPattern(self._getExtraPath('uniformProjections*'))
+
+    def generateExpImagesStep(self, Nimgs, nameProj, nameExp, label):
+
+        newXdim = readInfoField(self._getExtraPath(), "size",
+                                emlib.MDL_XSIZE)
+        fnProj = self._getExtraPath(nameProj + "%d.xmd" % label)
+        fnExp = self._getExtraPath(nameExp + "%d.xmd" % label)
+        fnLabels = self._getExtraPath('labels.txt')
+        mdIn = emlib.MetaData(fnProj)
+        mdExp = emlib.MetaData()
+        maxPsi = 180
+        maxShift = round(newXdim / 10)
+        idx = 1
+        NimgsMd = mdIn.size()
+        Nrepeats = int(Nimgs / NimgsMd)
+        # if Nrepeats<10:
+        #     Nrepeats=10
+        print("Nrepeats", Nrepeats)
+        if (label == 1 and exists(fnLabels)):
+            remove(fnLabels)
+        fileLabels = open(fnLabels, "a")
+        for row in iterRows(mdIn):
+            fnImg = row.getValue(emlib.MDL_IMAGE)
+            myRow = row
+            I = emlib.Image(fnImg)
+            Xdim, Ydim, _, _ = I.getDimensions()
+            Xdim2 = Xdim / 2
+            Ydim2 = Ydim / 2
+            if Nrepeats==0:
+                myRow.addToMd(mdExp)
+                idx += 1
+                fileLabels.write(str(label - 1) + '\n')
+            else:
+                for i in range(Nrepeats):
+                    psiDeg = np.random.uniform(-maxPsi, maxPsi)
+                    psi = psiDeg * math.pi / 180.0
+                    deltaX = np.random.uniform(-maxShift, maxShift)
+                    deltaY = np.random.uniform(-maxShift, maxShift)
+                    c = math.cos(psi)
+                    s = math.sin(psi)
+                    M = np.float32([[c, s, (1 - c) * Xdim2 - s * Ydim2 + deltaX],
+                                    [-s, c, s * Xdim2 + (1 - c) * Ydim2 + deltaY]])
+                    newFn = ('%06d@' % idx) + fnExp[:-3] + 'stk'
+                    self._ih.applyTransform(fnImg, newFn, M, (Ydim, Xdim), doWrap=True)
+
+                    myRow.setValue(emlib.MDL_IMAGE, newFn)
+                    myRow.setValue(emlib.MDL_ANGLE_PSI, psiDeg)
+                    myRow.setValue(emlib.MDL_SHIFT_X, deltaX)
+                    myRow.setValue(emlib.MDL_SHIFT_Y, deltaY)
+                    myRow.addToMd(mdExp)
+                    idx += 1
+                    fileLabels.write(str(label - 1) + '\n')
+        mdExp.write(fnExp)
+        fileLabels.close()
+        if (label - 1) > 0:
+            labelPrev = -1
+            for n in range(1, label):
+                if exists(self._getExtraPath(nameExp + "%d.xmd" % (label - n))):
+                    labelPrev = label - n
+                    break
+            if labelPrev is not -1:
+                lastFnExp = self._getExtraPath(nameExp + "%d.xmd" % (labelPrev))
+                self.runJob("xmipp_metadata_utilities",
+                            " -i %s --set union %s -o %s " %
+                            (lastFnExp, fnExp, fnExp), numberOfMpi=1)
+        remove(fnProj)
+
+    def trainNClassifiers2ClassesStep(self, thIdx, gpuId, totalGpu):
+
+        mdNumCones = emlib.MetaData(self._getExtraPath("coneCenters.doc"))
+        self.numCones = mdNumCones.size()
+
+        for i in range(self.numCones):
+
+            idx = i + 1
+
+            if (idx % totalGpu) != thIdx:
+                continue
+
+            modelFn = 'modelCone%d' % idx
+            if self.modelPretrain.get() is True:
+                if exists(self.pretrainedModels.get()._getExtraPath(modelFn + '.h5')):
+                    copy(self.pretrainedModels.get()._getExtraPath(modelFn + '.h5'),
+                         self._getExtraPath(modelFn + '.h5'))
+
+            expCheck = self._getExtraPath('projectionsExp%d.xmd' % idx)
+            if exists(expCheck):
+                if not exists(self._getExtraPath(modelFn + '.h5')):
+                    fnLabels = self._getExtraPath('labels.txt')
+                    fileLabels = open(fnLabels, "r")
+                    expSet = self._getExtraPath('projectionsExp%d.xmd' % self.numCones)
+                    if not exists(expSet):
+                        for n in range(1, self.numCones):
+                            if exists(self._getExtraPath('projectionsExp%d.xmd' % (self.numCones - n))):
+                                expSet = self._getExtraPath('projectionsExp%d.xmd' % (self.numCones - n))
+                                break
+                    newFnLabels = self._getExtraPath('labels%d.txt' % idx)
+                    newFileLabels = open(newFnLabels, "w")
+                    lines = fileLabels.readlines()
+                    for line in lines:
+                        if line == str(idx - 1) + '\n':
+                            newFileLabels.write('1\n')
+                        else:
+                            newFileLabels.write('0\n')
+                    newFileLabels.close()
+                    fileLabels.close()
+
+                    newXdim = readInfoField(self._getExtraPath(), "size",
+                                            emlib.MDL_XSIZE)
+                    fnLabels = self._getExtraPath('labels%d.txt' % idx)
+
+                    print("Training region ", idx, " in GPU ", gpuId)
+                    sys.stdout.flush()
+
+                    try:
+                        args = "%s %s %s %s %d %d %d %d " % (
+                        expSet, fnLabels, self._getExtraPath(),
+                        modelFn+'_aux', self.numEpochs, newXdim, 2, self.batchSize.get())
+                        #args += " %(GPU)s"
+                        args += " %s " % (gpuId)
+                        #args += " %s " %(int(idx % totalGpu))
+                        self.runJob("xmipp_cone_deepalign", args, numberOfMpi=1, env=self.getCondaEnv())
+                    except Exception as e:
+                        raise Exception(
+                            "ERROR: Please, if you are suffering memory problems, "
+                            "check the target resolution to work with lower dimensions.")
+
+                    moveFile(self._getExtraPath(modelFn + '_aux.h5'), self._getExtraPath(modelFn + '.h5'))
+
+    def predictStep(self, gpuId):
+
+        if not exists(self._getExtraPath('conePrediction.txt')):
+            # if self.useQueueForSteps() or self.useQueue():
+            #     myStr = os.environ["CUDA_VISIBLE_DEVICES"]
+            # else:
+            #     myStr = self.gpuList.get()
+            # numGPU = myStr.split(',')
+            # numGPU = numGPU[0]
+            # print("Predict", myStr, numGPU)
+            # sys.stdout.flush()
+
+            mdNumCones = emlib.MetaData(self._getExtraPath("coneCenters.doc"))
+            self.numCones = mdNumCones.size()
+
+            imgsOutStk = self._getExtraPath('images_out_filtered.stk')
+            imgsOutXmd = self._getExtraPath('images_out_filtered.xmd')
+            self.runJob("xmipp_transform_filter", " -i %s -o %s "
+                                              "--save_metadata_stack %s "
+                                              "--keep_input_columns "
+                                              "--fourier low_pass %f " %
+                    (self.imgsFn, imgsOutStk, imgsOutXmd, 0.15), numberOfMpi=self.myMPI.get())
+
+            numMax = int(self.numConesSelected)
+            newXdim = readInfoField(self._getExtraPath(), "size",
+                                emlib.MDL_XSIZE)
+            args = "%s %s %d %d %d " % (imgsOutXmd, self._getExtraPath(), newXdim, self.numCones, numMax)
+            #args += " %(GPU)s"
+            args += " %s "%(gpuId)
+            self.runJob("xmipp_cone_deepalign_predict", args, numberOfMpi=1, env=self.getCondaEnv())
+
+
+    def correlationCudaStep(self, thIdx, gpuId, totalGpu):
+
+        mdNumCones = emlib.MetaData(self._getExtraPath("coneCenters.doc"))
+        self.numCones = mdNumCones.size()
+
+        # Cuda Correlation step - creating the metadata
+        predCones = np.loadtxt(self._getExtraPath('conePrediction.txt'))
+        mdConeList = []
+        numMax = int(self.numConesSelected)
+        for i in range(self.numCones):
+            mdConeList.append(emlib.MetaData())
+        mdIn = emlib.MetaData(self.imgsFn)
+
+        for i in range(self.numCones):
+
+            idx = i + 1
+
+            if (idx % totalGpu) != thIdx:
+                continue
+
+            #modelFn = 'modelCone%d_aux' % idx
+            #f = open(join(self._getExtraPath(), modelFn+'.txt'),'r')
+            #mae = float(f.readline())
+            #f.close()
+
+            modelFn = 'modelCone%d' % idx
+
+            positions = []
+            for n in range(numMax):
+                posAux = np.where(predCones[:, (n * 2) + 1] == (i + 1))
+                positions = positions + (np.ndarray.tolist(posAux[0]))
+
+            if len(positions) > 0 and exists(self._getExtraPath(modelFn + '.h5')):
+                print("Classifying cone ", idx, "in GPU ", gpuId)
+
+                for pos in positions:
+                    id = pos + 1
+                    row = md.Row()
+                    row.readFromMd(mdIn, id)
+                    row.addToMd(mdConeList[i])
+                fnExpCone = self._getExtraPath('metadataCone%d.xmd' % (i + 1))
+                mdConeList[i].write(fnExpCone)
+
+                fnProjCone = self._getExtraPath('projectionsCudaCorr%d.xmd' % (i + 1))
+
+                self.runJob("xmipp_metadata_utilities", "-i %s --fill ref lineal 1 1 " % (fnProjCone), numberOfMpi=1)
+
+                fnOutCone = 'outCone%d.xmd' % (i + 1)
+
+                if not exists(self._getExtraPath(fnOutCone)):
+                    params = '  -i %s' % fnExpCone
+                    params += ' -r  %s' % fnProjCone
+                    params += ' -o  %s' % self._getExtraPath(fnOutCone)
+                    params += ' --dev %s '%(gpuId)
+                    self.runJob("xmipp_cuda_align_significant", params, numberOfMpi=1)
+
+
+    def correlationSignificantStep(self):
+
+        mdNumCones = emlib.MetaData(self._getExtraPath("coneCenters.doc"))
+        self.numCones = mdNumCones.size()
+
+        # Cuda Correlation step - creating the metadata
+        predCones = np.loadtxt(self._getExtraPath('conePrediction.txt'))
+        mdConeList = []
+        numMax = int(self.numConesSelected)
+        for i in range(self.numCones):
+            mdConeList.append(emlib.MetaData())
+        mdIn = emlib.MetaData(self.imgsFn)
+
+        for i in range(self.numCones):
+
+            print("Classifying cone ", i + 1)
+            positions = []
+            for n in range(numMax):
+                posAux = np.where(predCones[:, (n * 2) + 1] == (i + 1))
+                positions = positions + (np.ndarray.tolist(posAux[0]))
+
+            if len(positions) > 0:
+                for pos in positions:
+                    id = pos + 1
+                    row = md.Row()
+                    row.readFromMd(mdIn, id)
+                    row.addToMd(mdConeList[i])
+                fnExpCone = self._getExtraPath('metadataCone%d.xmd' % (i + 1))
+                mdConeList[i].write(fnExpCone)
+
+                fnProjCone = self._getExtraPath('projectionsCudaCorr%d.xmd' % (i + 1))
+                fnOutCone = 'outCone%d.xmd' % (i + 1)
+
+                if not exists(self._getExtraPath(fnOutCone)):
+                    # Correlation step - calling significant program
+                    args = '-i %s --initgallery %s --odir %s --dontReconstruct --useForValidation %d ' \
+                           '--dontCheckMirrors --maxShift 30' % (fnExpCone, fnProjCone, self._getExtraPath(), 1)
+                    self.runJob('xmipp_reconstruct_significant', args,
+                                    numberOfMpi=self.myMPI.get())
+                    copy(self._getExtraPath('images_significant_iter001_00.xmd'), self._getExtraPath(fnOutCone))
+                    remove(self._getExtraPath('angles_iter001_00.xmd'))
+                    remove(self._getExtraPath('images_significant_iter001_00.xmd'))
+
+
+    def createOutputMetadataStep(self):
+
+        mdNumCones = emlib.MetaData(self._getExtraPath("coneCenters.doc"))
+        self.numCones = mdNumCones.size()
+        numMax = int(self.numConesSelected)
+        mdIn = emlib.MetaData(self.imgsFn)
+        allInFns = mdIn.getColumnValues(emlib.MDL_IMAGE)
+
+        coneFns = []
+        coneCCs = []
+        mdCones = []
+        shiftX = []
+        shiftY = []
+        fnFinal = self._getExtraPath('outConesParticles.xmd')
+        for i in range(self.numCones):
+
+            fnOutCone = 'outCone%d.xmd' % (i + 1)
+
+            if exists(self._getExtraPath(fnOutCone)):             
+
+                if numMax == 1:
+                    if not exists(fnFinal):
+                        copy(self._getExtraPath(fnOutCone), fnFinal)
+                    else:
+                        params = ' -i %s --set union %s -o %s' % (fnFinal, self._getExtraPath(fnOutCone),
+                                                                  fnFinal)
+                        self.runJob("xmipp_metadata_utilities", params,
+                                    numberOfMpi=1)
+                else:
+                    mdCones.append(emlib.MetaData(self._getExtraPath(fnOutCone)))
+                    coneFns.append(mdCones[i].getColumnValues(emlib.MDL_IMAGE))
+                    shiftX.append(mdCones[i].getColumnValues(emlib.MDL_SHIFT_X))
+                    shiftY.append(mdCones[i].getColumnValues(emlib.MDL_SHIFT_Y))
+                    coneCCs.append(mdCones[i].getColumnValues(emlib.MDL_MAXCC))
+
+            else:
+                if numMax > 1:
+                    mdCones.append(None)
+                    coneFns.append([])
+                    coneCCs.append([])
+                    shiftX.append([])
+                    shiftY.append([])
+
+        if numMax > 1:
+            mdFinal = emlib.MetaData()
+            row = md.Row()
+            for myFn in allInFns:
+                myCCs = []
+                myCones = []
+                myPos = []
+                for n in range(self.numCones):
+                    if myFn in coneFns[n]:
+                        pos = coneFns[n].index(myFn)
+                        myPos.append(pos)
+                        if abs(shiftX[n][pos])<30 and abs(shiftY[n][pos])<30:
+                            myCCs.append(coneCCs[n][pos])
+                        else:
+                            myCCs.append(0)
+                        myCones.append(n + 1)
+                if len(myPos) > 0:
+                    if max(myCCs)==0:
+                        continue
+                    coneMax = myCones[myCCs.index(max(myCCs))]
+                    objId = myPos[myCCs.index(max(myCCs))] + 1
+                    row.readFromMd(mdCones[coneMax - 1], objId)
+                    row.addToMd(mdFinal)
+            mdFinal.write(fnFinal)
+        else:
+            mdCones = emlib.MetaData(fnFinal)
+            mdCones.removeObjects(emlib.MDValueGT(emlib.MDL_SHIFT_X, 30.))
+            mdCones.removeObjects(emlib.MDValueGT(emlib.MDL_SHIFT_Y, 30.))
+            mdCones.removeObjects(emlib.MDValueLT(emlib.MDL_SHIFT_X, -30.))
+            mdCones.removeObjects(emlib.MDValueLT(emlib.MDL_SHIFT_Y, -30.))
+            mdCones.write(fnFinal)
+
+    def createOutputStep(self):
+
+        cleanPattern(self._getExtraPath('*.stk'))
+        cleanPattern(self._getExtraPath('projectionsCudaCorr*'))
+
+        inputParticles = self.inputSet.get()
+        fnOutputParticles = self._getExtraPath('outConesParticles.xmd')
+
+        outputSetOfParticles = self._createSetOfParticles()
+        outputSetOfParticles.copyInfo(inputParticles)
+        outputSetOfParticles.setAlignmentProj()
+
+        Xdim = inputParticles.getXDim()
+        newXdim = readInfoField(self._getExtraPath(), "size",
+                                emlib.MDL_XSIZE)
+        Ts = readInfoField(self._getExtraPath(), "sampling",
+                           emlib.MDL_SAMPLINGRATE)
+        if newXdim != Xdim:
+            self.scaleFactor = Ts / inputParticles.getSamplingRate()
+            self.iterMd = md.iterRows(fnOutputParticles, emlib.MDL_ITEM_ID)
+            self.lastRow = next(self.iterMd)
+            outputSetOfParticles.copyItems(inputParticles,
+                                           updateItemCallback=self._updateItem)
+        else:
+            readSetOfParticles(fnOutputParticles, outputSetOfParticles)
+        self._defineOutputs(outputParticles=outputSetOfParticles)
+
+    def _updateItem(self, particle, row):
+        count = 0
+        while self.lastRow and particle.getObjId() == self.lastRow.getValue(
+                emlib.MDL_ITEM_ID):
+            count += 1
+            if count:
+                self._createItemMatrix(particle, self.lastRow)
+            try:
+                self.lastRow = next(self.iterMd)
+            except StopIteration:
+                self.lastRow = None
+        particle._appendItem = count > 0
+
+    def _createItemMatrix(self, particle, row):
+
+        row.setValue(emlib.MDL_SHIFT_X,
+                     row.getValue(emlib.MDL_SHIFT_X) * self.scaleFactor)
+        row.setValue(emlib.MDL_SHIFT_Y,
+                     row.getValue(emlib.MDL_SHIFT_Y) * self.scaleFactor)
+        setXmippAttributes(particle, row, emlib.MDL_SHIFT_X,
+                           emlib.MDL_SHIFT_Y,
+                           emlib.MDL_ANGLE_ROT, emlib.MDL_ANGLE_TILT,
+                           emlib.MDL_ANGLE_PSI)
+        createItemMatrix(particle, row, align=ALIGN_PROJ)
+
+    # --------------------------- INFO functions --------------------------------
+    def _summary(self):
+        summary = []
+        summary.append("Images evaluated: %i" % self.inputSet.get().getSize())
+        summary.append("Volume: %s" % self.inputVolume.getNameId())
+        return summary
+
+    def _methods(self):
+        methods = []
+        if hasattr(self, 'outputParticles'):
+            methods.append("We classify %i input images %s regarding to volume %s." \
+                % (self.inputSet.get().getSize(), self.getObjectTag('inputSet'),
+                   self.getObjectTag('inputVolume')))
+        return methods
+
+    def _validate(self):
+        errors = []
+        if self.numberOfMpi>1:
+            errors.append("You must select Threads to make the parallelization in Scipion level. "
+                          "To parallelize the Xmipp program use MPIs in the form.")
+        if self.spanConesTilt.get()>30:
+            errors.append("The distance between region centers should be lower than 31 degress.")
+        return errors
+
+
diff --git a/xmipp3/protocols/protocol_deep_denoising.py b/xmipp3/protocols/protocol_deep_denoising.py
index 0896e3db9..490354445 100644
--- a/xmipp3/protocols/protocol_deep_denoising.py
+++ b/xmipp3/protocols/protocol_deep_denoising.py
@@ -229,7 +229,7 @@ def _validate(self):
         if not self.checkIfInputIsCompareReprojection()  and self.modelTrainPredMode.get()==ITER_TRAIN and self.inputProjections.get() is None:
           errorMsg.append("Error, in training mode, both particles and projections must be provided")
         if self.imageSize.get() is None and self.modelTrainPredMode.get()==ITER_TRAIN:
-          errorMsg.append("Error, in training mode, image size must be provdided")
+          errorMsg.append("Error, in training mode, image size must be provided")      
         return errorMsg
 
     def _getResizedSize(self):
@@ -387,6 +387,14 @@ def createOutputStep(self):
             cleanPath(self._getExtraPath('resizedProjections.xmd'))
         if os.path.exists(self._getExtraPath('resizedEmptyParts.xmd')):
             cleanPath(self._getExtraPath('resizedEmptyParts.xmd'))
+        # extra cleaning    
+        if os.path.exists(self._getExtraPath('training_conf.json')):
+            cleanPath(self._getExtraPath('training_conf.json'))
+        if os.path.exists(self._getExtraPath('predict_conf.json')):
+            cleanPath(self._getExtraPath('predict_conf.json'))            
+        if os.path.exists(self._getExtraPath('batchImages/')):
+            #cleanPath(self._getExtraPath('batchImages/*.png'))
+            cleanPath(self._getExtraPath('batchImages'))
 
     def _processRow(self, particle, row):
         particle.setLocation(xmippToLocation(row.getValue(emlib.MDL_IMAGE)))
diff --git a/xmipp3/protocols/protocol_eliminate_empty_images.py b/xmipp3/protocols/protocol_eliminate_empty_images.py
index 201634ed4..03a73f0c9 100644
--- a/xmipp3/protocols/protocol_eliminate_empty_images.py
+++ b/xmipp3/protocols/protocol_eliminate_empty_images.py
@@ -153,13 +153,14 @@ def _stepsCheck(self):
     def _checkNewInput(self):
         # Check if there are new particles to process from the input set
         partsFile = self.getInput().getFileName()
-        self.lastCheck = getattr(self, 'lastCheck', datetime.now())
+        self.lastmTime = getattr(self, 'lastmTime', None)
         mTime = datetime.fromtimestamp(os.path.getmtime(partsFile))
         # If the input movies.sqlite have not changed since our last check,
         # it does not make sense to check for new input data
-        if self.lastCheck > mTime:
-            return None
-        self.lastCheck = datetime.now()
+        if self.lastmTime == mTime:
+            return
+
+        self.lastmTime = mTime
 
         outputStep = self._getFirstJoinStep()
 
@@ -182,7 +183,7 @@ def _checkNewOutput(self):
         if getattr(self, 'finished', False):
             return
 
-        self.finished = self.streamClosed and self.outputSize == self.lenPartsSet
+        self.finished = self.inputImages.isStreamClosed() and self.outputSize == self.lenPartsSet
 
         self.createOutputs()
 
diff --git a/xmipp3/protocols/protocol_extract_asymmetric_unit.py b/xmipp3/protocols/protocol_extract_asymmetric_unit.py
index 8eeda5424..b16ca0f2c 100644
--- a/xmipp3/protocols/protocol_extract_asymmetric_unit.py
+++ b/xmipp3/protocols/protocol_extract_asymmetric_unit.py
@@ -22,7 +22,7 @@
 # *  e-mail address 'scipion@cnb.csic.es'
 # *
 # **************************************************************************
-
+import os
 
 from pyworkflow import VERSION_3_0
 from pwem.objects import Volume
@@ -179,7 +179,9 @@ def _methods(self):
     # --------------------------- UTILS functions -----------------------------
 
     def _getOutputVol(self):
-        return self._getExtraPath("output_volume.mrc")
+        prefix = os.path.basename(self.inputVolumes.get().getFileName()).split(".")[0]
+
+        return self._getExtraPath(prefix + "_output_volume.mrc")
 
     def replace_at_index(self, tup, ix, val):
         return tup[:ix] + (val,) + tup[ix + 1:]
\ No newline at end of file
diff --git a/xmipp3/protocols/protocol_extract_particles_movies.py b/xmipp3/protocols/protocol_extract_particles_movies.py
index a1c473782..d68d59414 100644
--- a/xmipp3/protocols/protocol_extract_particles_movies.py
+++ b/xmipp3/protocols/protocol_extract_particles_movies.py
@@ -34,7 +34,7 @@
 from pyworkflow.protocol.constants import LEVEL_ADVANCED, STEPS_PARALLEL
 from pyworkflow.protocol.params import (PointerParam, IntParam, BooleanParam,
                                         Positive, FloatParam, EnumParam)
-from pyworkflow.utils.path import cleanPath
+from pyworkflow.utils.path import cleanPath, makePath
 
 import pwem.emlib.metadata as md
 from pwem.emlib.image import ImageHandler
@@ -162,9 +162,11 @@ def _defineParams(self, form):
     def _insertAllSteps(self):
         self._createFilenameTemplates()
 
+        makePath(self._getExtraPath('DONE'))
         # Build the list of all processMovieStep ids by
         # inserting each of the steps for each movie
         self.insertedDict = {}
+        makePath(self._getExtraPath('DONE'))
         
         # Conversion step is part of processMovieStep.
         movieSteps = self._insertNewMoviesSteps(self.insertedDict,
diff --git a/xmipp3/protocols/protocol_helical_parameters.py b/xmipp3/protocols/protocol_helical_parameters.py
index 355026ab4..7967b99fd 100644
--- a/xmipp3/protocols/protocol_helical_parameters.py
+++ b/xmipp3/protocols/protocol_helical_parameters.py
@@ -54,6 +54,8 @@ def _defineParams(self, form):
         form.addParam('dihedral',params.BooleanParam,default=False,label='Apply dihedral symmetry')
         form.addParam('forceDihedralX',params.BooleanParam,default=False,expertLevel=LEVEL_ADVANCED, label='Force the dihedral axis to be in X',
                       help="If this option is chosen, then the dihedral axis is not searched and it is assumed that it is around X.")
+        form.addParam('additionalCn',params.BooleanParam,default=False,label='Apply Cn symmetry')
+        form.addParam('Cn',params.StringParam,default="C2",condition="additionalCn",label='Cn symmetry')
 
         form.addSection(label='Search limits')
         form.addParam('heightFraction',params.FloatParam,default=0.9,label='Height fraction',
@@ -94,19 +96,35 @@ def copyInput(self):
             ImageHandler().convert(self.inputVolume.get(), self.fnVolSym)
                         
     def coarseSearch(self):
-        self.runCoarseSearch(self.fnVolSym,self.dihedral.get(),float(self.heightFraction.get()),float(self.z0.get()),float(self.zF.get()),float(self.zStep.get()),
+        Cn = "c1"
+        if self.additionalCn:
+            Cn=self.Cn.get()
+        self.runCoarseSearch(self.fnVolSym,self.dihedral.get(),float(self.heightFraction.get()),
+                             float(self.z0.get()),float(self.zF.get()),float(self.zStep.get()),
                              float(self.rot0.get()),float(self.rotF.get()),float(self.rotStep.get()),
                              self.numberOfThreads.get(),self._getExtraPath('coarseParams.xmd'),
-                             int(self.cylinderInnerRadius.get()),int(self.cylinderOuterRadius.get()),int(self.height),self.inputVolume.get().getSamplingRate())
+                             int(self.cylinderInnerRadius.get()),int(self.cylinderOuterRadius.get()),int(self.height),
+                             self.inputVolume.get().getSamplingRate(), Cn)
 
     def fineSearch(self):
-        self.runFineSearch(self.fnVolSym, self.dihedral.get(), self._getExtraPath('coarseParams.xmd'), self._getExtraPath('fineParams.xmd'), 
-                           float(self.heightFraction.get()),float(self.z0.get()),float(self.zF.get()),float(self.rot0.get()),float(self.rotF.get()),
-                             int(self.cylinderInnerRadius.get()),int(self.cylinderOuterRadius.get()),int(self.height),self.inputVolume.get().getSamplingRate())
+        Cn = "c1"
+        if self.additionalCn:
+            Cn=self.Cn.get()
+        self.runFineSearch(self.fnVolSym, self.dihedral.get(), self._getExtraPath('coarseParams.xmd'),
+                           self._getExtraPath('fineParams.xmd'),
+                           float(self.heightFraction.get()),float(self.z0.get()),float(self.zF.get()),
+                           float(self.rot0.get()),float(self.rotF.get()),
+                           int(self.cylinderInnerRadius.get()),int(self.cylinderOuterRadius.get()),int(self.height),
+                           self.inputVolume.get().getSamplingRate(), Cn)
 
     def symmetrize(self):
-        self.runSymmetrize(self.fnVolSym, self.dihedral.get(), self._getExtraPath('fineParams.xmd'), self.fnVolSym, 
-                           float(self.heightFraction.get()), self.cylinderInnerRadius.get(), self.cylinderOuterRadius.get(), self.height,self.inputVolume.get().getSamplingRate())
+        Cn = "c1"
+        if self.additionalCn:
+            Cn=self.Cn.get()
+        self.runSymmetrize(self.fnVolSym, self.dihedral.get(), self._getExtraPath('fineParams.xmd'), self.fnVolSym,
+                           float(self.heightFraction.get()),
+                           self.cylinderInnerRadius.get(), self.cylinderOuterRadius.get(), self.height,
+                           self.inputVolume.get().getSamplingRate(), Cn)
 
     def createOutput(self):
         volume = Volume()
diff --git a/xmipp3/protocols/protocol_ml2d.py b/xmipp3/protocols/protocol_ml2d.py
index 1c056adb7..03ee36c37 100644
--- a/xmipp3/protocols/protocol_ml2d.py
+++ b/xmipp3/protocols/protocol_ml2d.py
@@ -64,9 +64,9 @@ def _defineFileNames(self):
         myDict = {
                   'input_particles': self._getTmpPath('input_particles.xmd'),
                   'input_references': self._getTmpPath('input_references.xmd'),
-                  'output_classes': self._getOroot() + 'classes.xmd',
+                  'output_classes': self._getOroot() + 'classes.xmd', # self._getExtraPath('classes.xmd'),
                   'final_classes': self._getPath('classes2D.sqlite'),
-                  'output_particles': self._getOroot() + 'images.xmd',
+                  'output_particles':  self._getOroot() + 'images.xmd',# self._getExtraPath('images.xmd'),
                   'classes_scipion': self._getPath('classes_scipion_iter_%(iter)02d.sqlite')
                   }
         self._updateFilenamesDict(myDict)
@@ -243,7 +243,7 @@ def _methods(self):
     #--------------------------- UTILS functions --------------------------------------------
     def _getMLParams(self):
         """ Mainly prepare the command line for call ml(f)2d program"""
-        params = ' -i %s --oroot %s' % (self._getFileName('input_particles'), self._getOroot())
+        params = ' -i %s --oroot %s' % (self._getFileName('input_particles'), self._getOroot()) #self._getPath()
         if self.doGenerateReferences:
             params += ' --nref %d' % self.numberOfClasses.get()
             self.inputReferences.set(None)
@@ -296,14 +296,14 @@ def _getIterMdFile(self, fn, it, block):
             it = self._lastIteration()
         extra = self._getOroot() + 'extra'
         mdFile = join(extra, 'iter%03d' % it, 'iter_%s.xmd' %fn)
-        if block:
-            mdFile = block + '@' + mdFile
-        
+        #if block:
+            #mdFile = block + '@' + mdFile
+
         return mdFile
     
     def _lastIteration(self):
         """ Find the last iteration number """
-        it = 0        
+        it = -1
         while True:
             if not exists(self._getIterMdClasses(it+1)):
                 break
@@ -330,7 +330,7 @@ def _updateParticle(self, item, row):
     def _updateClass(self, item):
         classId = item.getObjId()
         
-        if  classId in self._classesInfo:
+        if classId in self._classesInfo:
             index, fn, _ = self._classesInfo[classId]
             item.setAlignment2D()
             item.getRepresentative().setLocation(index, fn)
diff --git a/xmipp3/protocols/protocol_mltomo.py b/xmipp3/protocols/protocol_mltomo.py
index 7317ce560..d23a86e47 100644
--- a/xmipp3/protocols/protocol_mltomo.py
+++ b/xmipp3/protocols/protocol_mltomo.py
@@ -90,7 +90,7 @@ def _createIterTemplates(self):
         self._iterTemplate = self._getFileName('ref_it', iter=0).replace('000000', '??????')
         # Iterations will be identify by _itXXXXXX_ where XXXXXX is the
         # iteration number and is restricted to only 6 digits.
-        self._iterRegex = re.compile('_it(\d{6,6})_')
+        self._iterRegex = re.compile(r'_it(\d{6,6})_')
 
     #--------------------------- DEFINE param functions -----------------------
     def _defineParams(self, form):
diff --git a/xmipp3/protocols/protocol_movie_average.py b/xmipp3/protocols/protocol_movie_average.py
index c104b87e0..cff022adc 100644
--- a/xmipp3/protocols/protocol_movie_average.py
+++ b/xmipp3/protocols/protocol_movie_average.py
@@ -30,7 +30,8 @@
 
 import pyworkflow.protocol.params as params
 from pyworkflow import VERSION_1_1
-from pyworkflow.utils import getExt
+import pyworkflow.utils as pwutils
+import xmipp3.utils as xmutils
 
 from pwem.protocols import ProtAlignMovies
 
@@ -141,9 +142,15 @@ def _processMovie(self, movie):
         else:
             roi = None
 
+        gainFn = self.inputMovies.get().getGain()
+        ext = pwutils.getExt(self.inputMovies.get().getFirstItem().getFileName()).lower()
+        if self.inputMovies.get().getGain() and ext in ['.tif', '.tiff']:
+          self.flipY = True
+          inGainFn = self.inputMovies.get().getGain()
+          gainFn = xmutils.flipYImage(inGainFn, outDir=self._getExtraPath())
+
         self.averageMovie(movie, inputMd, outputMicFn, self.binFactor.get(),
-                          roi, self.inputMovies.get().getDark(),
-                          self.inputMovies.get().getGain(),
+                          roi, self.inputMovies.get().getDark(), gainFn,
                           splineOrder=self.INTERP_MAP[self.splineOrder.get()])
 
         self._storeSummary(movie)
@@ -162,7 +169,7 @@ def _getShiftsFile(self, movie):
         return self._getExtraPath(self._getMovieRoot(movie) + '_shifts.xmd')
 
     def _getConvertExtension(self, filename):
-        ext = getExt(filename).lower()
+        ext = pwutils.getExt(filename).lower()
         return None if ext in ['.mrc', '.mrcs', '.tiff', '.tif', '.stk'] else 'mrc'
 
     def _createOutputMovies(self):
diff --git a/xmipp3/protocols/protocol_movie_correlation.py b/xmipp3/protocols/protocol_movie_correlation.py
index f4772afa2..6d6caa6a8 100644
--- a/xmipp3/protocols/protocol_movie_correlation.py
+++ b/xmipp3/protocols/protocol_movie_correlation.py
@@ -27,7 +27,8 @@
 # *
 # **************************************************************************
 
-import os
+import os.path
+import numpy as np
 from math import ceil
 
 import pyworkflow.utils as pwutils
@@ -36,6 +37,7 @@
 import pyworkflow.protocol.params as params
 import pyworkflow.protocol.constants as cons
 import pwem.emlib.metadata as md
+from pwem import emlib
 from pwem.objects import Image
 from pwem.protocols.protocol_align_movies import createAlignmentPlot
 from pyworkflow import VERSION_1_1
@@ -43,6 +45,7 @@
 
 from xmipp3.convert import writeMovieMd
 from xmipp3.base import isXmippCudaPresent
+import xmipp3.utils as xmutils
 
 
 class XmippProtMovieCorr(ProtAlignMovies):
@@ -59,6 +62,9 @@ class XmippProtMovieCorr(ProtAlignMovies):
     # Map to xmipp interpolation values in command line
     INTERP_MAP = {INTERP_LINEAR: 1, INTERP_CUBIC: 3}
 
+    NO_ROTATION = 0
+    NO_FLIP = 0
+
     _label = 'FlexAlign'
     _lastUpdateVersion = VERSION_1_1
 
@@ -111,6 +117,13 @@ def _defineAlignmentParams(self, form):
                       label="How to fill borders",
                       help='How to fill the borders when shifting the frames')
 
+        # this must stay together with the outside mode
+        form.addParam('outsideValue', params.FloatParam, default=0.0,
+                       expertLevel=cons.LEVEL_ADVANCED,
+                       condition="outsideMode==2",
+                       label='Fill value',
+                       help="Fixed value for filling borders")
+
         #Local alignment params
         group = form.addGroup('Local alignment')
 
@@ -123,6 +136,14 @@ def _defineAlignmentParams(self, form):
                       expertLevel=cons.LEVEL_ADVANCED,
                       condition='doLocalAlignment',
                       help="If on, protocol will automatically determine necessary number of control points.")
+        line = group.addLine('Number of control points',
+                    expertLevel=cons.LEVEL_ADVANCED,
+                    help='Number of control points use for BSpline.',
+                    condition='not autoControlPoints')
+        line.addParam('controlPointX', params.IntParam, default=6, label='X')
+        line.addParam('controlPointY', params.IntParam, default=6, label='Y')
+        line.addParam('controlPointT', params.IntParam, default=5, label='t')
+
         group.addParam('minLocalRes', params.FloatParam, default=500,
                        expertLevel=cons.LEVEL_ADVANCED,
                        label='Min size of the patch (A)',
@@ -135,14 +156,6 @@ def _defineAlignmentParams(self, form):
                             "but consecutive executions will be faster and use less memory."
                             "Set to True (autotuning will be turned off) if you process just few movies")
 
-        line = group.addLine('Number of control points',
-                    expertLevel=cons.LEVEL_ADVANCED,
-                    help='Number of control points use for BSpline.',
-                    condition='not autoControlPoints')
-        line.addParam('controlPointX', params.IntParam, default=6, label='X')
-        line.addParam('controlPointY', params.IntParam, default=6, label='Y')
-        line.addParam('controlPointT', params.IntParam, default=5, label='t')
-
         group.addParam('groupNFrames', params.IntParam, default=3,
                     expertLevel=cons.LEVEL_ADVANCED,
                     label='Group N frames',
@@ -150,11 +163,21 @@ def _defineAlignmentParams(self, form):
                         The alignment is then performed on the summed frames.',
                     condition='doLocalAlignment')
 
-        form.addParam('outsideValue', params.FloatParam, default=0.0,
-                       expertLevel=cons.LEVEL_ADVANCED,
-                       condition="outsideMode==2",
-                       label='Fill value',
-                       help="Fixed value for filling borders")
+        form.addSection(label="Gain orientation")
+        form.addParam('gainRot', params.EnumParam,
+                      choices=['no rotation', '90 degrees',
+                               '180 degrees', '270 degrees'],
+                      label="Rotate gain reference:",
+                      default=self.NO_ROTATION,
+                      display=params.EnumParam.DISPLAY_COMBO,
+                      help="Rotate gain reference counter-clockwise.")
+
+        form.addParam('gainFlip', params.EnumParam,
+                      choices=['no flip', 'upside down', 'left right'],
+                      label="Flip gain reference:", default=self.NO_FLIP,
+                      display=params.EnumParam.DISPLAY_COMBO,
+                      help="Flip gain reference after rotation. "
+                           "For tiff movies, gain is automatically upside-down flipped")
 
         form.addParallelSection(threads=1, mpi=1)
 
@@ -166,32 +189,26 @@ def _processMovie(self, movie):
             print(yellowStr("We cannot process %s" % movie.getFileName()))
             print(ex)
 
-    def tryProcessMovie(self, movie):
-        movieFolder = self._getOutputMovieFolder(movie)
-
-        x, y, n = movie.getDim()
-        a0, aN = self._getFrameRange(n, 'align')
-        s0, sN = self._getFrameRange(n, 'sum')
-
-        inputMd = os.path.join(movieFolder, 'input_movie.xmd')
-        writeMovieMd(movie, inputMd, a0, aN, useAlignment=False)
-
-        args = '-i "%s" ' % inputMd
-        args += '-o "%s" ' % self._getShiftsFile(movie)
-        args += '--sampling %f ' % movie.getSamplingRate()
-        args += '--maxResForCorrelation %f ' % self.maxResForCorrelation
-        args += '--Bspline %d ' % self.INTERP_MAP[self.splineOrder.get()]
-
-        if self.binFactor > 1:
-            args += '--bin %f ' % self.binFactor
+    def getOutsideModeArg(self):
+        if self.outsideMode == self.OUTSIDE_WRAP:
+            return ' --outside wrap'
+        if self.outsideMode == self.OUTSIDE_AVG:
+            return ' --outside avg'
+        if self.outsideMode == self.OUTSIDE_VALUE:
+            return ' --outside value %f' % self.outsideValue
+        return ''
+
+    def getCropCornerArg(self, x, y):
         # Assume that if you provide one cropDim, you provide all
-
         offsetX = self.cropOffsetX.get()
         offsetY = self.cropOffsetY.get()
         cropDimX = self.cropDimX.get()
         cropDimY = self.cropDimY.get()
 
-        args += '--cropULCorner %d %d ' % (offsetX, offsetY)
+        if 0 == offsetX and 0 == offsetY and 0 == cropDimX and 0 == cropDimY:
+            return '' # user does not want to crop at all
+
+        args = ' --cropULCorner %d %d' % (offsetX, offsetY)
 
         if cropDimX <= 0:
             dimX = x - 1
@@ -203,18 +220,54 @@ def tryProcessMovie(self, movie):
         else:
             dimY = offsetY + cropDimY - 1
 
-        args += '--cropDRCorner %d %d ' % (dimX, dimY)
+        args += ' --cropDRCorner %d %d' % (dimX, dimY)
+        return args
 
-        if self.outsideMode == self.OUTSIDE_WRAP:
-            args += "--outside wrap"
-        elif self.outsideMode == self.OUTSIDE_AVG:
-            args += "--outside avg"
-        elif self.outsideMode == self.OUTSIDE_AVG:
-            args += "--outside value %f" % self.outsideValue
+    def getUserAngle(self):
+      anglesDic = {0:0, 1:90, 2:180, 3:270}
+      return anglesDic[self.gainRot.get()]
+
+    def getUserFlip(self, imag_array):
+      flipDic = {0: np.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
+                 1: np.asarray([[1, 0, 0], [0, -1, imag_array.shape[0]], [0, 0, 1]]),
+                 2: np.asarray([[-1, 0, imag_array.shape[1]], [0, 1, 0], [0, 0, 1]])}
+      return flipDic[self.gainFlip.get()]
+
+    def getGPUArgs(self):
+        args = ' --device %(GPU)s'
+        if self.doLocalAlignment.get():
+            args += ' --processLocalShifts '
+        if self.skipAutotuning.get():
+            args += " --skipAutotuning"
+        args += ' --storage "%s"' % self._getExtraPath("fftBenchmark.txt")
+        args += ' --controlPoints %d %d %d' % (self.controlPointX, self.controlPointY, self.controlPointT)
+        args += ' --patchesAvg %d' % self.groupNFrames
+        return args
+
+    def tryProcessMovie(self, movie):
+        movieFolder = self._getOutputMovieFolder(movie)
+
+        x, y, n = movie.getDim()
+        a0, aN = self._getFrameRange(n, 'align')
+        s0, sN = self._getFrameRange(n, 'sum')
+
+        inputMd = os.path.join(movieFolder, 'input_movie.xmd')
+        writeMovieMd(movie, inputMd, a0, aN, useAlignment=False)
 
-        args += ' --frameRange %d %d ' % (0, aN-a0)
-        args += ' --frameRangeSum %d %d ' % (s0-a0, sN-a0)
-        args += ' --max_shift %d ' % self.maxShift
+        args = '-i "%s" ' % inputMd
+        args += ' -o "%s"' % self._getShiftsFile(movie)
+        args += ' --sampling %f' % movie.getSamplingRate()
+        args += ' --maxResForCorrelation %f' % self.maxResForCorrelation
+        args += ' --Bspline %d' % self.INTERP_MAP[self.splineOrder.get()]
+
+        if self.binFactor > 1:
+            args += ' --bin %f' % self.binFactor
+
+        args += self.getCropCornerArg(x, y)
+        args += self.getOutsideModeArg()
+        args += ' --frameRange %d %d' % (0, aN-a0)
+        args += ' --frameRangeSum %d %d' % (s0-a0, sN-a0)
+        args += ' --max_shift %d' % self.maxShift
 
         if self.doSaveAveMic or self.doComputePSD:
             fnAvg = self._getExtraPath(self._getOutputMicName(movie))
@@ -222,16 +275,26 @@ def tryProcessMovie(self, movie):
 
         if self.doComputePSD:
             fnInitial = os.path.join(movieFolder, "initialMic.mrc")
-            args  += ' --oavgInitial %s' % fnInitial
+            args  += ' --oavgInitial "%s"' % fnInitial
 
         if self.doSaveMovie:
-            args += ' --oaligned %s' % self._getExtraPath(self._getOutputMovieName(movie))
+            args += ' --oaligned "%s"' % self._getExtraPath(self._getOutputMovieName(movie))
 
         if self.inputMovies.get().getDark():
-            args += ' --dark ' + self.inputMovies.get().getDark()
+            args += ' --dark "%s"' % self.inputMovies.get().getDark()
 
         if self.inputMovies.get().getGain():
-            args += ' --gain ' + self.inputMovies.get().getGain()
+            ext = pwutils.getExt(self.inputMovies.get().getFirstItem().getFileName()).lower()
+            if ext in ['.tif', '.tiff']:
+              self.flipY = True
+              inGainFn = self.inputMovies.get().getGain()
+              gainFn = xmutils.flipYImage(inGainFn, outDir = self._getExtraPath())
+            else:
+              gainFn = self.inputMovies.get().getGain()
+
+            if self.gainRot.get() != 0 or self.gainFlip.get() != 0:
+              gainFn = self.transformGain(gainFn)
+            args += ' --gain "%s"' % gainFn
 
         if self.autoControlPoints.get():
             self._setControlPoints()
@@ -240,14 +303,7 @@ def tryProcessMovie(self, movie):
             args += ' --minLocalRes %f' % self.minLocalRes
 
         if self.useGpu.get():
-            args += ' --device %(GPU)s'
-            if self.doLocalAlignment.get():
-                args += ' --processLocalShifts '
-            if self.skipAutotuning.get():
-                args += " --skipAutotuning"
-            args += ' --storage ' + self._getExtraPath("fftBenchmark.txt")
-            args += ' --controlPoints %d %d %d' % (self.controlPointX, self.controlPointY, self.controlPointT)
-            args += ' --patchesAvg %d' % self.groupNFrames
+            args += self.getGPUArgs()
             self.runJob('xmipp_cuda_movie_alignment_correlation', args, numberOfMpi=1)
         else:
             self.runJob('xmipp_movie_alignment_correlation', args, numberOfMpi=1)
@@ -263,6 +319,25 @@ def tryProcessMovie(self, movie):
         self._saveAlignmentPlots(movie)
 
     #--------------------------- UTILS functions ------------------------------
+    def transformGain(self, gainFn, outFn=None):
+      '''Transforms the gain image with the user especifications'''
+      if outFn == None:
+        ext = pwutils.getExt(gainFn)
+        baseName = os.path.basename(gainFn).replace(ext, '_transformed' + ext)
+        outFn = os.path.abspath(self._getExtraPath(baseName))
+
+      gainImg = xmutils.readImage(gainFn)
+      imag_array = np.asarray(gainImg.getData(), dtype=np.float64)
+
+      # Building the transformation matrix
+      angle = self.getUserAngle()
+      M = self.getUserFlip(imag_array)
+      print('Transforming gain: {} degrees rotation, {} flip'.
+            format(angle, ['no', 'vertical', 'horizontal'][self.gainFlip.get()]))
+      flipped_array, M = xmutils.rotation(imag_array, angle, imag_array.shape, M)
+      xmutils.writeImageFromArray(flipped_array, outFn)
+      return outFn
+
     def _getShiftsFile(self, movie):
         return self._getExtraPath(self._getMovieRoot(movie) + '_shifts.xmd')
 
@@ -274,12 +349,12 @@ def _stepsCheck(self):
         self.inputMovies.get().close()
 
     def _setControlPoints(self):
-            x, y,frames = self.inputMovies.get().getDim()
-            Ts = self.inputMovies.get().getSamplingRate()
-            # one control point each 1000 A
-            self.controlPointX.set(int(x / Ts) / 1000 + 2)
-            self.controlPointY.set(int(y / Ts) / 1000 + 2)
-            self.controlPointT.set(ceil(frames/7.) + 2)
+        x, y, frames = self.inputMovies.get().getDim()
+        Ts = self.inputMovies.get().getSamplingRate()
+        # one control point each 1000 A
+        self.controlPointX.set(max([int(x / Ts) / 1000 + 2, 3]))
+        self.controlPointY.set(max([int(y / Ts) / 1000 + 2, 3]))
+        self.controlPointT.set(max([ceil(frames/7.) + 2, 3]))
 
     def _getMovieShifts(self, movie):
         from ..convert import readShiftsMovieAlignment
@@ -348,54 +423,53 @@ def _createOutputMovie(self, movie):
         self._setAlignmentInfo(movie, alignedMovie)
         return alignedMovie
 
-    def _validate(self):
-        if self.autoControlPoints.get():
-            self._setControlPoints() # make sure we work with proper values
-        errors = ProtAlignMovies._validate(self)
-        getXmippHome = self.getClassPackage().Plugin.getHome
-        if self.doLocalAlignment.get():
-            cudaBinaryFn = getXmippHome('bin', 'xmipp_cuda_movie_alignment_correlation')
-            if not os.path.isfile(cudaBinaryFn):
-                errors.append('GPU version not found, make sure that Xmipp is '
-                              'compiled with GPU\n'
-                              '( *CUDA=True* in _scipion.conf_ + '
-                              '_run_: $ *scipion installb xmippSrc* ).')
-                return errors
-            elif not self.useGpu.get():
-                errors.append("GPU is needed to do local alignment.")
-                return errors
 
+    def _validateParallelProcessing(self):
         nGpus = len(self.gpuList.get().split())
         nMPI = self.numberOfMpi.get()
         nThreads = self.numberOfThreads.get()
-        validThreads = nGpus if nGpus == 1 else nGpus + 1
-        if self.useGpu and nMPI * nThreads != validThreads:
-            coreStr = ('threads' if nThreads > 1 else
-                       'MPI' if nMPI > 1 else
-                       'threads or MPI')
-            errors.append("The number of threads/mpi must be one more than "
-                          "the number of GPUs to use (single threading is also "
-                          "valid for one single GPU)\n"
-                          "i.e. *%s = %d* , when using *%d GPUs*"
-                          % (coreStr, validThreads, nGpus))
-        else:
-            cpuBinaryFn = getXmippHome('bin', 'xmipp_movie_alignment_correlation')
-            if not os.path.isfile(cpuBinaryFn):
-                errors.append('CPU version not found for some reason, try to GPU=True.')
-                return errors
+        errors = []
+        if nThreads != 1:
+            errors.append('Multithreading is not supported. Use a single thread or one MPI.')
+        validMPIs = 1 if nGpus == 1 else nGpus + 1
+        if self.useGpu.get() and nMPI != validMPIs:
+            errors.append('Invalid number of MPI. Please set it to %d, as you set %d GPU(s)' % (validMPIs, nGpus))
+        return errors
+
+    def _validateBinary(self):
+        errors = []
+        getXmippHome = self.getClassPackage().Plugin.getHome
+        cpuBinaryFn = getXmippHome('bin', 'xmipp_movie_alignment_correlation')
+        if self.useGpu.get() and not isXmippCudaPresent("xmipp_cuda_movie_alignment_correlation"):
+            errors.append('GPU version not found, make sure that Xmipp is '
+                          'compiled with GPU\n'
+                          '( *CUDA=True* in _scipion.conf_ + '
+                          '_run_: $ *scipion installb xmippSrc* ).')
+        elif not os.path.isfile(cpuBinaryFn):
+            errors.append('CPU version not found for some reason, try to use the GPU version.')
+        return errors
+
+    def _validate(self):
+        if self.autoControlPoints.get():
+            self._setControlPoints()  # make sure we work with proper values
+        # check execution issues
+        errors = ProtAlignMovies._validate(self)
+        errors.extend(self._validateBinary())
+        errors.extend(self._validateParallelProcessing())
+        if errors:
+            return errors
+
+        # check settings issues
+        if self.doLocalAlignment.get() and not self.useGpu.get():
+            errors.append("GPU is needed to do local alignment.")
         if (self.controlPointX < 3):
             errors.append("You have to use at least 3 control points in X dim")
-            return errors # to avoid possible division by zero later
         if (self.controlPointY < 3):
             errors.append("You have to use at least 3 control points in Y dim")
-            return errors # to avoid possible division by zero later
         if (self.controlPointT < 3):
             errors.append("You have to use at least 3 control points in T dim")
-            return errors # to avoid possible division by zero later
-        if self.useGpu and not isXmippCudaPresent("xmipp_cuda_movie_alignment_correlation"):
-            errors.append("I cannot find the Xmipp GPU programs in the path")
 
         return errors
 
     def _citations(self):
-        return ['strelak2020flexalign']
\ No newline at end of file
+        return ['strelak2020flexalign']
diff --git a/xmipp3/protocols/protocol_movie_gain.py b/xmipp3/protocols/protocol_movie_gain.py
index 4eabe3cab..b88542133 100644
--- a/xmipp3/protocols/protocol_movie_gain.py
+++ b/xmipp3/protocols/protocol_movie_gain.py
@@ -27,10 +27,10 @@
 
 import numpy as np
 import os
-import math
 import sys
 
 from pyworkflow import VERSION_1_1
+import pyworkflow.utils as pwutils
 from pyworkflow.object import Set
 from pyworkflow.protocol import STEPS_PARALLEL
 from pyworkflow.protocol.params import (PointerParam, IntParam,
@@ -43,15 +43,23 @@
 from pwem.protocols import EMProtocol, ProtProcessMovies
 
 from pwem import emlib
-from xmipp3.utils import normalize_array
+import xmipp3.utils as xmutils
 
 
 class XmippProtMovieGain(ProtProcessMovies):
     """ Estimate the gain image of a camera, directly analyzing one of its movies.
+    It can correct the orientation of an external gain image (by comparing it with the estimated).
+    Finally, it estimates the residual gain (the gain of the movie after correcting with a gain).
+    The gain used in the correction will be preferably the external gain, but can also be the estimated
+    gain if the first is not found.
+    The same criteria is used for assigning the gain to the output movies (external corrected > external > estimated)
     """
     _label = 'movie gain'
     _lastUpdateVersion = VERSION_1_1
 
+    estimatedDatabase = 'estGains.sqlite'
+    residualDatabase = 'resGains.sqlite'
+
     def __init__(self, **args):
         EMProtocol.__init__(self, **args)
         self.stepsExecutionMode = STEPS_PARALLEL
@@ -65,6 +73,26 @@ def _defineParams(self, form):
                       label=Message.LABEL_INPUT_MOVS,
                       help='Select one or several movies. A gain image will '
                            'be calculated for each one of them.')
+        form.addParam('estimateGain', BooleanParam, default=True,
+                      label="Estimate movies gain",
+                      help='Estimate the gain from a set of movies using the algorith from xmipp')
+        form.addParam('estimateOrientation', BooleanParam, default=True,
+                      label="Estimate external gain orientation",
+                      help='Estimate the relative orientation between the estimated '
+                           'and the existing gain')
+        form.addParam('estimateResidualGain', BooleanParam, default=True,
+                      label="Estimate residual gain",
+                      help='If there is a gain image associated with input '
+                           'movies, you can decide to use it instead of '
+                           'estimating raw/residual gain image. Location of '
+                           'this gain image needs to be indicated in import '
+                           'movies protocol.')
+        form.addParam('normalizeGain', BooleanParam, default=True,
+                      label="Normalize existing gain", expertLevel=LEVEL_ADVANCED,
+                      help='Normalize the input gain so that it has a mean of 1')
+        form.addParam('estimateSigma', BooleanParam, default=False, expertLevel=LEVEL_ADVANCED,
+                      label="Estimate the sigma parameter",
+                      help='Estimate the sigma parameter for the gain image computation')
         form.addParam('frameStep', IntParam, default=5,
                       label="Frame step", expertLevel=LEVEL_ADVANCED,
                       help='By default, every 5th frame is used to compute '
@@ -77,20 +105,6 @@ def _defineParams(self, form):
                            'compute the movie gain. If you set '
                            'this parameter to 2, 3, ..., then only every 2nd, '
                            '3rd, ... movie will be used.')
-        form.addParam('estimateOrientation', BooleanParam, default=True,
-                      label="Estimate gain orientation",
-                      help='Estimate the relative orientation between the estimated '
-                           'and the existing gain')
-        form.addParam('normalizeGain', BooleanParam, default=True,
-                      label="Normalize existing gain", expertLevel=LEVEL_ADVANCED,
-                      help='Normalize the input gain so that it has a mean of 1')
-        form.addParam('useExistingGainImage', BooleanParam, default=True,
-                      label="Estimate residual gain",
-                      help='If there is a gain image associated with input '
-                           'movies, you can decide to use it instead of '
-                           'estimating raw/residual gain image. Location of '
-                           'this gain image needs to be indicated in import '
-                           'movies protocol.')
 
         # It should be in parallel (>2) in order to be able of attaching
         #  new movies to the output while estimating residual gain
@@ -98,7 +112,13 @@ def _defineParams(self, form):
 
     # -------------------------- STEPS functions ------------------------------
     def createOutputStep(self):
-        pass
+        if self.estimateGain.get():
+            estGainsSet = self._loadOutputSet(SetOfImages, self.estimatedDatabase)
+            self._updateOutputSet('estimatedGains', estGainsSet, Set.STREAM_CLOSED)
+
+        if self.estimateResidualGain.get():
+            resGainsSet = self._loadOutputSet(SetOfImages, self.residualDatabase)
+            self._updateOutputSet('residualGains', resGainsSet, Set.STREAM_CLOSED)
 
     def _insertNewMoviesSteps(self, insertedDict, inputMovies):
         """ Insert steps to process new movies (from streaming)
@@ -130,7 +150,7 @@ def _insertNewMoviesSteps(self, insertedDict, inputMovies):
                                                       prerequisites=self.convertCIStep)
                 # adding normStep as dependency for all other steps
                 self.convertCIStep.append(normStepId)
-
+            self.estimatedIds, self.estimatedResIds = [], []
             # For each movie insert the step to process it
             for movie in self.inputMovies.get():
                 if movie.getObjId() not in insertedDict:
@@ -139,27 +159,39 @@ def _insertNewMoviesSteps(self, insertedDict, inputMovies):
                     insertedDict[movie.getObjId()] = stepId
         return deps
 
+    def estimateGainFun(self, movie, noSigma=False, residual=False):
+        movieId = movie.getObjId()
+        movieFn = movie.getFileName()
+
+        # Check which estimated gain matches with the experimental gain
+        args = self.getArgs(movieFn, movieId, residual=residual)
+        if not self.estimateSigma.get() or noSigma:
+            args += " --sigma 0"
+        if residual:
+            args += " --gainImage {}".format(self.getFinalGainPath())
+        self.runJob("xmipp_movie_estimate_gain", args, numberOfMpi=1)
+
     def estimateOrientationStep(self, movieDict):
         movie = Movie()
         movie.setAttributesFromDict(movieDict, setBasic=True, ignoreMissing=True)
-
         movieId = movie.getObjId()
-        movieFn = movie.getFileName()
+        estGainFn = self.getEstimatedGainPath(movieId)
         expGainFn = self.inputMovies.get().getGain()
-        resGainFn = self.getCurrentGain(movieId)
 
-        # Check which estimated gain matches with the experimental gain
-        args = self.getArgs(movieFn, movieId, " --sigma 0")
-        self.runJob("xmipp_movie_estimate_gain", args, numberOfMpi=1)
+        if not movieId in self.estimatedIds:
+            self.estimatedIds.append(movieId)
+            self.estimateGainFun(movie, noSigma=True)
+
+        estGain = xmutils.readImage(estGainFn)
+        expGain = xmutils.readImage(expGainFn)
+        self.match_orientation(expGain, estGain)
 
-        resGain = emlib.Image()
-        resGain.read(resGainFn)
-        expGain = emlib.Image()
-        expGain.read(expGainFn)
-        self.match_orientation(expGain, resGain)
+        orientedSet = self._loadOutputSet(SetOfImages, 'orientedGain.sqlite')
+        orientedSet = self.updateGainsOutput(movie, orientedSet, self.getOrientedGainPath())
+        self._updateOutputSet('orientedGain', orientedSet, Set.STREAM_CLOSED)
 
     def normalizeGainStep(self):
-        gainFn = self.getFinalGain()
+        gainFn = self.getFinalGainPath()
 
         oriGain = emlib.Image()
         oriGain.read(gainFn)
@@ -169,33 +201,30 @@ def normalizeGainStep(self):
         oriArray = oriArray / np.mean(oriArray)
 
         oriGain.setData(oriArray)
-        oriGain.write(self.getBestGain())
+        oriGain.write(self.getFinalGainPath())
 
     def _processMovie(self, movie):
         movieId = movie.getObjId()
         if not self.doGainProcess(movieId):
             return
-        fnMovie = movie.getFileName()
         inputGain = self.getInputGain()
-        args = self.getArgs(fnMovie, movieId)
-
-        if self.useExistingGainImage.get() and inputGain is not None:
-            args += " --gainImage %s" % self.getFinalGain()
-
-        self.runJob("xmipp_movie_estimate_gain", args, numberOfMpi=1)
-
-        # We take the inverse of the estimated gain computed by xmipp,
-        #  stored in correction file
-        moveFile(self._getExtraPath("movie_%06d_correction.xmp" % movieId),
-                 self.getCurrentGain(movieId))
-
-        # If the gain hasn't been oriented or normalized, we still need bestGain
-        if not os.path.exists(self.getBestGain()):
-            # No previous gain: bestGain is the estimated
+          
+        if self.estimateGain.get() and not movieId in self.estimatedIds:
+                self.estimatedIds.append(movieId)
+                self.estimateGainFun(movie)
+
+        if self.estimateResidualGain.get() and not movieId in self.estimatedResIds:
+            print('\nEstimating residual gain')
+            self.estimatedResIds.append(movieId)
+            self.estimateGainFun(movie, residual=True)
+
+        # If the gain hasn't been oriented or normalized, we still need orientedGain
+        if not os.path.exists(self.getOrientedGainPath()):
+            # No previous gain: orientedGain is the estimated
             if not inputGain is None:
                 G = emlib.Image()
                 G.read(inputGain)
-                G.write(self.getBestGain())
+                G.write(self.getOrientedGainPath())
 
         fnSummary = self._getPath("summary.txt")
         fnMonitorSummary = self._getPath("summaryForMonitor.txt")
@@ -206,22 +235,22 @@ def _processMovie(self, movie):
             fhSummary = open(fnSummary, "a")
             fnMonitorSummary = open(fnMonitorSummary, "a")
 
-        estim_gain = self.getCurrentGain(movieId)
-        if os.path.exists(estim_gain):
+        resid_gain = self.getResidualGainPath(movieId)
+        if os.path.exists(resid_gain):
             G = emlib.Image()
-            G.read(estim_gain)
+            G.read(resid_gain)
             mean, dev, min, max = G.computeStats()
             Gnp = G.getData()
             p = np.percentile(Gnp, [2.5, 25, 50, 75, 97.5])
-            fhSummary.write("movie_%06d: mean=%f std=%f [min=%f,max=%f]\n" %
+            fhSummary.write("movie_%06d_residual: mean=%f std=%f [min=%f,max=%f]\n" %
                             (movieId, mean, dev, min, max))
             fhSummary.write(
                 "            2.5%%=%f 25%%=%f 50%%=%f 75%%=%f 97.5%%=%f\n" %
                 (p[0], p[1], p[2], p[3], p[4]))
             fhSummary.close()
-            fnMonitorSummary.write("movie_%06d: %f %f %f %f\n" %
+            fnMonitorSummary.write("movie_%06d_residual: %f %f %f %f\n" %
                                    (movieId, dev, p[0], p[4], max))
-            fnMonitorSummary.close()
+        fnMonitorSummary.close()
 
     def _loadOutputSet(self, SetClass, baseName, fixGain=False):
         """
@@ -239,12 +268,12 @@ def _loadOutputSet(self, SetClass, baseName, fixGain=False):
             outputSet = SetClass(filename=setFile)
             outputSet.setStreamState(outputSet.STREAM_OPEN)
 
-        if isinstance(self.inputMovies.get(), SetOfMovies):
-            inputMovies = self.inputMovies.get()
-            outputSet.copyInfo(inputMovies)
+            if isinstance(self.inputMovies.get(), SetOfMovies) or isinstance(self.inputMovies.get(), Movie):
+                inputMovies = self.inputMovies.get()
+                outputSet.copyInfo(inputMovies)
 
-        if fixGain and os.path.isfile(self.getBestGain()):
-            outputSet.setGain(self.getFinalGain())
+                if fixGain:
+                    outputSet.setGain(self.getFinalGainPath(tifFlipped=True))
 
         return outputSet
 
@@ -256,18 +285,23 @@ def _checkNewOutput(self):
         if getattr(self, 'finished', False):
             return
         if isinstance(self.inputMovies.get(), Movie):
-            saveMovie = self.getAttributeValue('doSaveMovie', False)
-            imageSet = self._loadOutputSet(SetOfImages,
-                                           'movies.sqlite')
             movie = self.inputMovies.get()
-            imgOut = Image()
-            imgOut.setObjId(movie.getObjId())
-            imgOut.setSamplingRate(movie.getSamplingRate())
-            imgOut.setFileName(self.getCurrentGain(movie.getObjId()))
-            imageSet.setSamplingRate(movie.getSamplingRate())
-            imageSet.append(imgOut)
-
-            self._updateOutputSet('outputGains', imageSet, Set.STREAM_CLOSED)
+            movieId = movie.getObjId()
+            streamMode = Set.STREAM_CLOSED
+            saveMovie = self.getAttributeValue('doSaveMovie', False)
+            moviesSet = self._loadOutputSet(SetOfMovies, 'movies.sqlite', fixGain=True)
+            moviesSet.append(movie)
+            self._updateOutputSet('outputMovies', moviesSet, streamMode)
+
+            if self.estimateGain.get():
+                estGainsSet = self._loadOutputSet(SetOfImages, self.estimatedDatabase)
+                estGainsSet = self.updateGainsOutput(movie, estGainsSet, self.getEstimatedGainPath(movieId))
+                self._updateOutputSet('estimatedGains', estGainsSet, streamMode)
+            if self.estimateResidualGain.get():
+                resGainsSet = self._loadOutputSet(SetOfImages, self.residualDatabase)
+                resGainsSet = self.updateGainsOutput(movie, resGainsSet, self.getResidualGainPath(movieId))
+                self._updateOutputSet('residualGains', resGainsSet, streamMode)
+
             outputStep = self._getFirstJoinStep()
             outputStep.setStatus(cons.STATUS_NEW)
             self.finished = True
@@ -298,27 +332,28 @@ def _checkNewOutput(self):
 
             if any([self.doGainProcess(i.getObjId()) for i in newDone]):
                 # update outputGains if any residualGain is processed in newDone
-                imageSet = self._loadOutputSet(SetOfImages,
-                                               'gains.sqlite')
+                if self.estimateGain.get():
+                    estGainsSet = self._loadOutputSet(SetOfImages, self.estimatedDatabase)
+                if self.estimateResidualGain.get():
+                    resGainsSet = self._loadOutputSet(SetOfImages, self.residualDatabase)
+                  
                 for movie in newDone:
                     movieId = movie.getObjId()
                     if not self.doGainProcess(movieId):
                         continue
-                    imgOut = Image()
-                    imgOut.setObjId(movieId)
-                    imgOut.setSamplingRate(movie.getSamplingRate())
-                    imgOut.setFileName(self.getCurrentGain(movieId))
-                    imageSet.setSamplingRate(movie.getSamplingRate())
-                    imageSet.append(imgOut)
-
-                self._updateOutputSet('outputGains', imageSet, streamMode)
-
-            moviesSet = self._loadOutputSet(SetOfMovies,
-                                            'movies.sqlite',
-                                            fixGain=True)
+                    if self.estimateGain.get():
+                        estGainsSet = self.updateGainsOutput(movie, estGainsSet, self.getEstimatedGainPath(movieId))
+                    if self.estimateResidualGain.get():
+                        resGainsSet = self.updateGainsOutput(movie, resGainsSet, self.getResidualGainPath(movieId))
+
+                if self.estimateGain.get():
+                    self._updateOutputSet('estimatedGains', estGainsSet, streamMode)
+                if self.estimateResidualGain.get():
+                    self._updateOutputSet('residualGains', resGainsSet, streamMode)
+
+            moviesSet = self._loadOutputSet(SetOfMovies, 'movies.sqlite', fixGain=True)
             for movie in newDone:
                 moviesSet.append(movie)
-
             self._updateOutputSet('outputMovies', moviesSet, streamMode)
 
             if self.finished:  # Unlock createOutputStep if finished all jobs
@@ -326,6 +361,17 @@ def _checkNewOutput(self):
                 if outputStep and outputStep.isWaiting():
                     outputStep.setStatus(cons.STATUS_NEW)
 
+    def updateGainsOutput(self, movie, imgSet, imageFile):
+        movieId = movie.getObjId()
+        imgOut = Image()
+        imgOut.setObjId(movieId)
+        imgOut.setSamplingRate(movie.getSamplingRate())
+        imgOut.setFileName(imageFile)
+
+        imgSet.setSamplingRate(movie.getSamplingRate())
+        imgSet.append(imgOut)
+        return imgSet
+
     def _updateOutputSet(self, outputName, outputSet, state=Set.STREAM_OPEN):
         outputSet.setStreamState(state)
 
@@ -349,12 +395,12 @@ def match_orientation(self, exp_gain, est_gain):
             with respect to the estimated
             Input: 2 Xmipp Images
         '''
-        print('Estimating best orientation')
+        print('\nEstimating best orientation')
         sys.stdout.flush()
         best_cor = 0
         #Building conjugate of FT of estimated gain for correlations
         est_gain_array = est_gain.getData()
-        est_gain_array = normalize_array(est_gain_array)
+        est_gain_array = xmutils.normalize_array(est_gain_array)
         est_gain_array_FT_conj = np.conj(np.fft.fft2(est_gain_array))
 
         # Iterating for mirrors
@@ -369,7 +415,7 @@ def match_orientation(self, exp_gain, est_gain):
                     M = np.identity(3)
                 angle = irot * 90
                 #Transformating the imag array (mirror + rotation)
-                imag_array, R = rotation(imag_array, angle, est_gain_array.shape, M)
+                imag_array, R = xmutils.rotation(imag_array, angle, est_gain_array.shape, M)
 
                 # calculating correlation
                 correlationFunction = arrays_correlation_FT(imag_array,est_gain_array_FT_conj)
@@ -378,11 +424,11 @@ def match_orientation(self, exp_gain, est_gain):
                 maxVal = np.amax(correlationFunction)
                 minLoc = np.where(correlationFunction == minVal)
                 maxLoc = np.where(correlationFunction == maxVal)
-                #print(minVal,maxVal,minLoc,maxLoc)
+
                 if abs(minVal) > abs(best_cor):
-                    corLoc=translation_correction(minLoc,est_gain_array.shape)
+                    corLoc = translation_correction(minLoc,est_gain_array.shape)
                     best_cor = minVal
-                    best_transf=(angle,imir)
+                    best_transf = (angle,imir)
                     best_R = R
                     T = np.asarray([[1, 0, np.asscalar(corLoc[1])], [0, 1, np.asscalar(corLoc[0])], [0, 0, 1]])
                 if abs(maxVal) > abs(best_cor):
@@ -394,40 +440,82 @@ def match_orientation(self, exp_gain, est_gain):
 
         # Multiply by inverse of translation matrix
         best_M = np.matmul(np.linalg.inv(T), best_R)
-        best_gain_array = applyTransform(np.asarray(exp_gain.getData(), dtype=np.float64), best_M, est_gain_array.shape)
+        best_gain_array = xmutils.applyTransform(np.asarray(exp_gain.getData(), dtype=np.float64), best_M, est_gain_array.shape)
 
-        print('Best correlation: ',best_cor)
-        print('Rotation angle: {}\nVertical mirror: {}'.format(best_transf[0],best_transf[1]==1))
-        if best_cor > 0:
-            best_gain_array = invert_array(best_gain_array)
+        print('Best correlation: ', best_cor)
+        print('Rotation angle: {}\nHorizontal mirror: {}'.format(best_transf[0],best_transf[1]==1))
 
-        best_gain = emlib.Image()
-        best_gain.setData(best_gain_array)
-        best_gain.write(self.getBestGain())
+        inv_best_gain_array = invert_array(best_gain_array)
+        if best_cor > 0:
+            xmutils.writeImageFromArray(best_gain_array, self.getOrientedGainPath())
+            #xmutils.writeImageFromArray(inv_best_gain_array, self.getBestCorrectionPath())
+        else:
+            xmutils.writeImageFromArray(inv_best_gain_array, self.getOrientedGainPath())
+            #xmutils.writeImageFromArray(best_gain_array, self.getBestCorrectionPath())
 
     # ------------------------- UTILS functions --------------------------------
-    def getCurrentGain(self, movieId):
+    def invertImage(self, img, outFn):
+        array = img.getData()
+        inv_array = invert_array(array)
+        xmutils.writeImageFromArray(inv_array, outFn)
+
+    def getInputGain(self):
+        return self.inputMovies.get().getGain()
+    
+    def getEstimatedGainPath(self, movieId):
         return self._getExtraPath("movie_%06d_gain.xmp" % movieId)
 
-    def getBestGain(self):
-        return self._getExtraPath("bestGain.mrc")
+    def getResidualGainPath(self, movieId):
+        return self._getExtraPath("movie_%06d_residual_gain.xmp" % movieId)
+
+    def getFlippedOrientedGainPath(self):
+        return self._getExtraPath("orientedGain_flipped.mrc")
+
+    def getOrientedGainPath(self):
+        return self._getExtraPath("orientedGain.mrc")
+
+    def getOrientedCorrectionPath(self):
+        return self._getExtraPath("orientedCorrection.mrc")
 
-    def getFinalGain(self):
-        fnBest = self.getBestGain()
+    def getFinalGainPath(self, tifFlipped=False):
+        fnBest = self.getOrientedGainPath()
         if os.path.exists(fnBest):
             # If the best orientatin has been calculated, take it
             finalGainFn = fnBest
-        else:
+        elif self.getInputGain() != None:
+            # Elif, take the input gain provided
             finalGainFn = self.getInputGain()
+        else:
+            # Elif, take the estimated gain
+            finalGainFn = self.searchEstimatedGainPath()
+            if finalGainFn == None:
+                # If no gains have been estimated, estimate one and use that
+                firstMovie = self.inputMovies.get().getFirstItem()
+                movieId = firstMovie.getObjId()
+                if not movieId in self.estimatedIds:
+                    self.estimatedIds.append(movieId)
+                    self.estimateGainFun(firstMovie)
+                finalGainFn = self.getEstimatedGainPath(movieId)
+
+        ext = pwutils.getExt(self.inputMovies.get().getFirstItem().getFileName()).lower()
+        if ext in ['.tif', '.tiff'] and tifFlipped:
+            finalGainFn = xmutils.flipYImage(finalGainFn, outDir = self._getExtraPath())
+
         return finalGainFn
 
-    def getInputGain(self):
-        return self.inputMovies.get().getGain()
+    def searchEstimatedGainPath(self):
+        for fn in os.listdir(self._getExtraPath()):
+            if fn.endswith('gain.xmp') and not 'residual' in fn:
+                return self._getExtraPath(fn)
+        return None
 
-    def getArgs(self, movieFn, movieId, extraArgs=''):
+    def getArgs(self, movieFn, movieId, extraArgs='', residual=False):
+        if residual:
+            outbase = self._getExtraPath("movie_%06d_residual" % movieId)
+        else:
+            outbase = self._getExtraPath("movie_%06d" % movieId)
         return ("-i %s --oroot %s --iter 1 --singleRef --frameStep %d %s"
-                % (movieFn, self._getExtraPath("movie_%06d" % movieId),
-                   self.frameStep, extraArgs))
+                % (movieFn, outbase, self.frameStep, extraArgs))
 
     def doGainProcess(self, movieId):
         return (movieId-1) % self.movieStep.get() == 0
@@ -461,37 +549,11 @@ def _summary(self):
 
 
 # --------------------- WORKERS --------------------------------------
-
-def applyTransform(imag_array, M, shape):
-    ''' Apply a transformation(M) to a np array(imag) and return it in a given shape
-    '''
-    imag = emlib.Image()
-    imag.setData(imag_array)
-    imag = imag.applyWarpAffine(list(M.flatten()), shape, True)
-    return imag.getData()
-
-
-def rotation(imag, angle, shape, P):
-    '''Rotate a np.array and return also the transformation matrix
-    #imag: np.array
-    #angle: angle in degrees
-    #shape: output shape
-    #P: transform matrix (further transformation in addition to the rotation)'''
-    (hsrc, wsrc) = imag.shape
-    angle *= math.pi / 180
-    T = np.asarray([[1, 0, -wsrc / 2], [0, 1, -hsrc / 2], [0, 0, 1]])
-    R = np.asarray([[math.cos(angle), math.sin(angle), 0], [-math.sin(angle), math.cos(angle), 0], [0, 0, 1]])
-    M = np.matmul(np.matmul(np.linalg.inv(T), np.matmul(R, T)), P)
-
-    transformed = applyTransform(imag, M, shape)
-    return transformed, M
-
-
 def arrays_correlation_FT(ar1,ar2_ft_conj,normalize=True):
     '''Return the correlation matrix of an array and the FT_conjugate of a second array using the fourier transform
     '''
     if normalize:
-        ar1=normalize_array(ar1)
+        ar1=xmutils.normalize_array(ar1)
 
     ar1_FT = np.fft.fft2(ar1)
     corr2FT = np.multiply(ar1_FT, ar2_ft_conj)
diff --git a/xmipp3/protocols/protocol_postProcessing_deepPostProcessing.py b/xmipp3/protocols/protocol_postProcessing_deepPostProcessing.py
index b0fa24d2f..73681fbf5 100644
--- a/xmipp3/protocols/protocol_postProcessing_deepPostProcessing.py
+++ b/xmipp3/protocols/protocol_postProcessing_deepPostProcessing.py
@@ -262,8 +262,9 @@ def createOutputStep(self):
             volume.setOrigin(inVol.getOrigin(force=True))
 
             self._defineOutputs(Volume=volume)
-            self._defineTransformRelation(self.inputHalf1, volume)
-            self._defineTransformRelation(self.inputHalf2, volume)
+            self._defineTransformRelation(inVol, volume)
+            if not self.halfMapsAttached.get():
+              self._defineTransformRelation(self.inputHalf2, volume)
         else:
             inVol = self.inputVolume.get()
             volume.setSamplingRate(inVol.getSamplingRate())
diff --git a/xmipp3/protocols/protocol_preprocess/protocol_create_mask3d.py b/xmipp3/protocols/protocol_preprocess/protocol_create_mask3d.py
index efc1de695..4d01a1064 100644
--- a/xmipp3/protocols/protocol_preprocess/protocol_create_mask3d.py
+++ b/xmipp3/protocols/protocol_preprocess/protocol_create_mask3d.py
@@ -84,7 +84,9 @@ def _defineParams(self, form):
         form.addParam('threshold', FloatParam, default=0.0,
                       condition='volumeOperation==%d and %s'
                       % (OPERATION_THRESHOLD, isVolume),
-                      label='Threshold')
+                      label='Threshold',
+                      help="Select the threshold. Gray values lesser than the threshold" \
+                           "will be set to zero, otherwise will be one (mask area).")
         isSegmentation = 'volumeOperation==%d and %s' % (OPERATION_SEGMENT, isVolume)
         form.addParam('segmentationType', EnumParam, default=SEGMENTATION_DALTON, 
                       condition=isSegmentation,
@@ -150,19 +152,20 @@ def _defineParams(self, form):
         form.addSection(label='Postprocessing')
         form.addParam('doSmall', BooleanParam, default=False,
                       label='Remove small objects',
-                      help="The input mask has to be binary")
+                      help="To remove small clusters of points. "
+                           "The input mask has to be binary.")
         form.addParam('smallSize', IntParam, default=50,
                       label='Minimum size',condition="doSmall",
                       help='Connected components whose size is smaller than '
                            'this number in voxels will be removed')
         form.addParam('doBig', BooleanParam, default=False,
                       label='Keep largest component',
-                      help="The input mask has to be binary")
+                      help="To keep cluster greater than a given size. The input mask has to be binary")
         form.addParam('doSymmetrize', BooleanParam, default=False,
                       label='Symmetrize mask')
         form.addParam('symmetry', StringParam, default='c1',
                       label='Symmetry group',condition="doSymmetrize",
-                      help="See http://xmipp.cnb.csic.es/twiki/bin/view/Xmipp/Symmetry \n"
+                      help="To obtain a symmetric mask. See http://xmipp.cnb.csic.es/twiki/bin/view/Xmipp/Symmetry \n"
                            "for a description of the symmetry groups format. \n"
                            "If no symmetry is present, give c1")
         form.addParam('doMorphological', BooleanParam, default=False,
diff --git a/xmipp3/protocols/protocol_preprocess/protocol_preprocess.py b/xmipp3/protocols/protocol_preprocess/protocol_preprocess.py
index 0b6991268..ce149072d 100644
--- a/xmipp3/protocols/protocol_preprocess/protocol_preprocess.py
+++ b/xmipp3/protocols/protocol_preprocess/protocol_preprocess.py
@@ -175,7 +175,7 @@ def _defineProcessParams(self, form):
         form.addParam('doPhaseFlip', BooleanParam, default=False,
                       label='Phase flip images')
         XmippPreprocessHelper._defineProcessParams(form)
-    
+
     #--------------------------- INSERT steps functions ------------------------
     def _insertProcessStep(self):
         self.isFirstStep = True
diff --git a/xmipp3/protocols/protocol_projmatch/projmatch_steps.py b/xmipp3/protocols/protocol_projmatch/projmatch_steps.py
index e06a91faf..8bf1ddf73 100644
--- a/xmipp3/protocols/protocol_projmatch/projmatch_steps.py
+++ b/xmipp3/protocols/protocol_projmatch/projmatch_steps.py
@@ -459,7 +459,7 @@ def insertAngularClassAverageStep(self, iterN, refN, **kwargs):
               'outClasses' : outClasses
               }
     
-    args = ' -i ctfGroup[0-9][0-9][0-9][0-9][0-9][0-9]\$@'
+    args = r' -i ctfGroup[0-9][0-9][0-9][0-9][0-9][0-9]\$@'
     args += '%(docFileInputAngles)s --lib %(projLibraryDoc)s -o %(outClasses)s'
     
     # FIXME: This option no exist in the form
diff --git a/xmipp3/protocols/protocol_resolution_bfactor.py b/xmipp3/protocols/protocol_resolution_bfactor.py
new file mode 100644
index 000000000..79b4271af
--- /dev/null
+++ b/xmipp3/protocols/protocol_resolution_bfactor.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# *
+# * Authors:     Jose Luis Vilas (jlvilas@cnb.csic.es)
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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 numpy as np
+
+from pwem.emlib.image import ImageHandler
+from pwem.objects import FSC
+from pwem.protocols import ProtAnalysis3D
+from pyworkflow import VERSION_2_0
+from pyworkflow.protocol.constants import STEPS_PARALLEL
+import pyworkflow.protocol.params as params
+import pwem.emlib.metadata as md
+
+from xmipp3.convert import locationToXmipp, writeSetOfParticles
+
+from pyworkflow.protocol.params import (PointerParam, BooleanParam, FloatParam,
+                                        LEVEL_ADVANCED)
+from pyworkflow.object import Float
+from pyworkflow.utils import getExt
+from pwem.objects import Volume, SetOfParticles
+
+
+FN_METADATA_BFACTOR_RESOLUTION = 'bfactor_resolution.xmd'
+
+class XmippProtbfactorResolution(ProtAnalysis3D):
+    """    
+    Given a local resolution map and an atomic model, this protocols provides the matching between the
+    local resolution with the local bfactor per residue.
+    """
+    _label = 'local resolution/local bfactor'
+    _lastUpdateVersion = VERSION_2_0
+
+    def __init__(self, **args):
+        ProtAnalysis3D.__init__(self, **args)
+
+    # --------------------------- DEFINE param functions ----------------------
+    def _defineParams(self, form):
+        form.addSection(label='Input')
+
+        form.addParam('pdbfile', PointerParam, pointerClass='AtomStruct',
+                      label="Atomic model", important=True,
+                      help='Select an atomic model. The atom positions will be taken'
+                           ' to estimate the local resolution around them and then, the '
+                           ' local resolution associated to each residue.')
+
+        form.addParam('normalizeResolution', BooleanParam, default=True,
+                      label="Normalize Resolution",
+                      help='The normalizedlocal resolution map is defined as'
+                           '(LR - FSC)/FSC, where LR is the local resolution of a'
+                           'given voxel, and FSC is the FSC resolution in A. This '
+                           'map provides information about whether the local resolution is'
+                           'greater or lesser than the FSC. The local resolution '
+                           'normalized map is used to carry out the matching with the local'
+                           'bfactor per residue. Yes means that the local resolution will be'
+                           'normalized by the algorithm. No means that the input local '
+                           'resolution map is already a normalized local resolution map.')
+
+        form.addParam('localResolutionMap', PointerParam, pointerClass='Volume',
+                      label="Local Resolution Map", important=True,
+                      condition='normalizeResolution',
+                      help='Select a local resolution map. Alternatively, the input.'
+                           ' can be a normalized local resolution map, in this case'
+                           'set the Normalize resolution to No')
+
+        form.addParam('normalizedMap', PointerParam, pointerClass='Volume',
+                      label="Normalized Local Resolution Map", important=True,
+                      condition='not normalizeResolution',
+                      help='Select a normalized local resolution map. The local'
+                           ' resolution normalized map is defined as '
+                           '(LR - FSC)/FSC, where LR is the local resolution of a'
+                           'given voxel, and FSC is the FSC resolution in A')
+
+        form.addParam('fscResolution', FloatParam,
+                      condition = 'normalizeResolution',
+                      label="FSC resolution (A)",
+                      help='The global resolution of the map in A')
+
+        form.addParam('medianEstimation', BooleanParam, default=True,
+                      label="Use median",
+                      help='The local resolution per residue can be estimated using'
+                           'the mean (by default - No) or the median (yes)')
+
+        form.addParallelSection(threads=4, mpi=0)
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+
+    def _insertAllSteps(self):
+
+        # 1 Check the input and convert to mrc if it is the case
+        self._insertFunctionStep('convertInputStep')
+
+        # 2 Carry out the matching betweent the local resolution per residue and bfactor
+        self._insertFunctionStep('matchingBfactorLocalResolution')
+
+    def mrc_convert(self, fileName, outputFileName):
+        """Check if the extension is .mrc, if not then uses xmipp to convert it
+        """
+        ext = getExt(fileName)
+        if ((ext != '.mrc') and (ext != '.map')):
+            params = ' -i %s' % fileName
+            params += ' -o %s' % outputFileName
+            self.runJob('xmipp_image_convert', params)
+            return outputFileName
+        else:
+            return fileName
+
+    def convertInputStep(self):
+        """ Read the input volume and check the file extension to convert to mrc is it is the case.
+        """
+        self.vol = self.mrc_convert(self.localResolutionMap.get().getFileName(),
+                                    self._getTmpPath('localResolutionMap.mrc'))
+
+    def matchingBfactorLocalResolution(self):
+        """ The local resolution map and the pdb are taken and analyzed to match the
+        local resolution and bfactor per residue. The output will be a pdb file with the
+        bfactor column substituted by the normalized local resolution. This is the (local
+        resolution - fscResolution)/fscResolution.
+        """
+
+        params = ' --atmodel "%s"' % self.pdbfile.get().getFileName()
+        params += ' --vol %s' % self.vol
+        if self.normalizeResolution.get():
+            params += ' --fscResolution %s' % self.fscResolution.get()
+        params += ' --sampling %f' % self.localResolutionMap.get().getSamplingRate()
+        if self.medianEstimation.get():
+            params += ' --useMedian '
+        params += ' -o %s' % self._getExtraPath()
+
+        self.runJob('xmipp_resolution_pdb_bfactor', params)
diff --git a/xmipp3/protocols/protocol_resolution_fso.py b/xmipp3/protocols/protocol_resolution_fso.py
index 8d21ff8db..06f1ae04c 100644
--- a/xmipp3/protocols/protocol_resolution_fso.py
+++ b/xmipp3/protocols/protocol_resolution_fso.py
@@ -25,73 +25,84 @@
 # *
 # **************************************************************************
 
-import numpy as np
 
-from pwem.emlib.image import ImageHandler
-from pwem.objects import FSC
-from pwem.protocols import ProtAnalysis3D
 from pyworkflow import VERSION_2_0
-from pyworkflow.protocol.constants import STEPS_PARALLEL
-import pyworkflow.protocol.params as params
-import pwem.emlib.metadata as md
-
-from xmipp3.convert import locationToXmipp, writeSetOfParticles
-
-from pyworkflow.protocol.params import (PointerParam, BooleanParam, FloatParam,
-                                        LEVEL_ADVANCED)
 from pyworkflow.object import Float
 from pyworkflow.utils import getExt
-from pwem.objects import Volume, SetOfParticles
-
-
+from pyworkflow.protocol.params import (PointerParam, BooleanParam, FloatParam,
+                                        LEVEL_ADVANCED)
+from pwem.objects import Volume
+from pwem.protocols import ProtAnalysis3D
 
 OUTPUT_3DFSC = '3dFSC.mrc'
-OUTPUT_SPHERE = 'fsc/sphere.mrc'
-OUTPUT_DIRECTIONAL_FILTER = 'fsc/filteredMap.mrc'
+OUTPUT_DIRECTIONAL_FILTER = 'filteredMap.mrc'
+OUTPUT_DIRECTIONAL_DISTRIBUTION = 'Resolution_Distribution.xmd'
 
 
 class XmippProtFSO(ProtAnalysis3D):
     """    
-    Given two half maps the protocol estimates Fourier Shell Occupancy to determine the global anisotropy of the map
+    Given two half maps the protocol estimates Fourier Shell Occupancy to determine the global anisotropy of the map.
+    See more information here: https://github.com/I2PC/xmipp/wiki/FSO---Fourier-Shell-Occupancy
     """
     _label = 'resolution fso'
     _lastUpdateVersion = VERSION_2_0
-    
+
     def __init__(self, **args):
         ProtAnalysis3D.__init__(self, **args)
-    
+
     # --------------------------- DEFINE param functions ----------------------
     def _defineParams(self, form):
         form.addSection(label='Input')
 
+        form.addParam('halfVolumesFile', BooleanParam, default=False,
+                      label="Are the half volumes stored with the input volume?",
+                      help='Usually, the half volumes are stored as properties of '
+                      'the input volume. If this is not the case, set this to '
+                      'False and specify the two halves you want to use.')
+
+        form.addParam('inputHalves', PointerParam, pointerClass='Volume',
+                      label="Input Half Maps",
+                      condition = 'halfVolumesFile',
+                      help='Select a half maps for determining its '
+                      ' resolution anisotropy and resolution.')
+
         form.addParam('half1', PointerParam, pointerClass='Volume',
+                      condition = "not halfVolumesFile",
                       label="Half Map 1", important=True,
                       help='Select one map for determining the '
-		      'directional FSC resolution.')
+		                    'directional FSC resolution.')
 
         form.addParam('half2', PointerParam, pointerClass='Volume',
+                      condition = "not halfVolumesFile",
                       label="Half Map 2", important=True,
                       help='Select the second map for determining the '
                       'directional FSC resolution.')
-        
-        form.addParam('mask', PointerParam, pointerClass='VolumeMask', 
-                      allowsNull=True, 
-                      label="Mask", 
+
+        form.addParam('mask', PointerParam, pointerClass='VolumeMask',
+                      allowsNull=True,
+                      label="Mask",
                       help='The mask determines which points are specimen'
                       ' and which are not')
 
-        form.addParam('bestAngle', BooleanParam, default=True, 
-                      label="Find Best Cone angle",
-                      help='The algorithm will try to determine by cross validation'
-			   'the best cone angle to then determine the directional FSC'
-			   'Note that the cone angle is the angle between the axis '
-			   'of the cone and the generatrix')
-
-        form.addParam('coneAngle', FloatParam, default=20.0, 
-		      condition = 'not bestAngle ',
+        form.addParam('coneAngle', FloatParam, default=17.0,
+                      expertLevel=LEVEL_ADVANCED,
                       label="Cone Angle",
-                      help='Angle between the axis of the cone and the generatrix')
-        
+                      help='Angle between the axis of the cone and the generatrix. '
+                           'An angle of 17 degrees is the best angle (see publication'
+                           'Vilas 2021) to measuare directional FSCs')
+
+        form.addParam('estimate3DFSC', BooleanParam, default=True,
+                      label="Estimate 3DFSC and directional filtered map",
+                      help='Set to estimate the 3DFSCD map, and applyting the '
+                           ' 3DFSC as anisotropic filter to obtain a directional'
+                           'filtered map.')
+
+        form.addParam('threshold', FloatParam, expertLevel=LEVEL_ADVANCED,
+                      default=0.143,
+                      label="FSC Threshold",
+                      help='Threshold for the fsc. By default the standard 0.143. '
+                           'Other common thresholds are 0.5 and 0.3.')
+
         form.addParallelSection(threads = 4, mpi = 0)
 
     # --------------------------- INSERT steps functions --------------------------------------------
@@ -99,71 +110,115 @@ def _defineParams(self, form):
     def _createFilenameTemplates(self):
         """ Centralize how files are called """
         myDict = {OUTPUT_3DFSC: self._getExtraPath("3dFSC.mrc"),
-                 OUTPUT_SPHERE: self._getExtraPath("fsc/sphere.mrc"),
-		 OUTPUT_DIRECTIONAL_FILTER: self._getExtraPath("fsc/filteredMap.mrc"),
-                 }
+                  OUTPUT_DIRECTIONAL_FILTER: self._getExtraPath("filteredMap.mrc"),
+                  }
         self._updateFilenamesDict(myDict)
 
 
     def _insertAllSteps(self):
-        self._createFilenameTemplates() 
+        self._createFilenameTemplates()
         # Convert input into xmipp Metadata format
         self._insertFunctionStep('convertInputStep')
-        self._insertFunctionStep('resolutionDirectionalFSCStep')
+        self._insertFunctionStep('FSOestimationStep')
         self._insertFunctionStep('createOutputStep')
 
     def mrc_convert(self, fileName, outputFileName):
         """Check if the extension is .mrc, if not then uses xmipp to convert it
         """
         ext = getExt(fileName)
-        if (ext !='.mrc'):
-            params = ' -i %s' % fileName
-            params += ' -o %s' % outputFileName
+        if (ext != '.mrc') and (ext != '.map'):
+            params = ' -i "%s"' % fileName
+            params += ' -o "%s"' % outputFileName
             self.runJob('xmipp_image_convert', params)
-            #outputFileName = outputFileName # +':mrc'
-            return outputFileName
+            return outputFileName+':mrc'
         else:
-            return fileName
+            return fileName+':mrc'
 
     def convertInputStep(self):
         """ Read the input volume.
         """
-        self.vol1Fn = self.mrc_convert(self.half1.get().getFileName(),
+
+        if self.halfVolumesFile:
+            self.vol1Fn, self.vol2Fn = self.inputHalves.get().getHalfMaps().split(',')
+        else:
+            self.vol1Fn = self.half1.get().getFileName()
+            self.vol2Fn = self.half2.get().getFileName()
+
+        extVol1 = getExt(self.vol1Fn)
+        extVol2 = getExt(self.vol2Fn)
+
+        if (extVol1 == '.mrc') or (extVol1 == '.map'):
+            self.vol1Fn = self.vol1Fn + ':mrc'
+        if (extVol2 == '.mrc') or (extVol2 == '.map'):
+            self.vol2Fn = self.vol2Fn + ':mrc'
+
+        if self.mask.hasValue():
+            self.maskFn = self.mask.get().getFileName()
+            extMask = getExt(self.maskFn)
+            if (extMask == '.mrc') or (extMask == '.map'):
+                self.maskFn = self.maskFn + ':mrc'
+
+        """
+        if self.halfVolumesFile:
+            self.vol1Fn, self.vol2Fn = self.inputHalves.get().getHalfMaps().split(',')
+        else:
+            self.vol1Fn = self.mrc_convert(self.half1.get().getFileName(),
                                   self._getTmpPath('half1.mrc'))
-        self.vol2Fn = self.mrc_convert(self.half2.get().getFileName(),
+            self.vol2Fn = self.mrc_convert(self.half2.get().getFileName(),
                                   self._getTmpPath('half2.mrc'))
-        self.maskFn = self.mrc_convert(self.mask.get().getFileName(),
+        if (self.mask.hasValue()):
+            self.maskFn = self.mrc_convert(self.mask.get().getFileName(),
                                   self._getExtraPath('mask.mrc'))
+        """
+
 
-    def resolutionDirectionalFSCStep(self):
+    def FSOestimationStep(self):
         import os
-        fndir = self._getExtraPath("fsc") 
+        fndir = self._getExtraPath("fsc")
 
-        os.mkdir(fndir) 	
+        os.mkdir(fndir)
 
-        params = ' --half1 %s' % self.vol1Fn
-        params += ' --half2 %s' % self.vol2Fn
+        params = ' --half1 "%s"' % self.vol1Fn
+        params += ' --half2 "%s"' % self.vol2Fn
+        params += ' -o %s' % self._getExtraPath()
+        if self.halfVolumesFile:
+            params += ' --sampling %f' % self.inputHalves.get().getSamplingRate()
+        else:
+            params += ' --sampling %f' % self.half1.get().getSamplingRate()
 
         if self.mask.hasValue():
-            params += ' --mask %s' % self.maskFn
-
-        params += ' --sampling %f' % self.half1.get().getSamplingRate()
-        if self.bestAngle.get() is False:
-            params += ' --anglecone %f' % self.coneAngle.get()
-        params += ' --threedfsc %s' % self._getExtraPath("3dFSC.mrc")
-        params += ' --fscfolder %s' % (fndir+'/')
-        params += ' --anisotropy %s' % self._getExtraPath("fso.xmd")
-        
+            params += ' --mask "%s"' % self.maskFn
+
+        params += ' --anglecone %f' % self.coneAngle.get()
+
+        if self.estimate3DFSC.get():
+            params += ' --threedfsc_filter'
+
+        params += ' --threshold %s' % self.threshold.get()
+        params += ' --threads %s' % self.numberOfThreads.get()
+
         self.runJob('xmipp_resolution_fso', params)
-      
-     
+
+
     def createOutputStep(self):
-        volume=Volume()
-        volume.setFileName(self._getExtraPath("3dFSC.mrc"))
 
-        volume.setSamplingRate(self.half1.get().getSamplingRate())
-        self._defineOutputs(fsc3D=volume)
-        self._defineSourceRelation(self.half1, volume)
+        if self.estimate3DFSC:
+            volume = Volume()
+            volume.setFileName(self._getExtraPath("3dFSC.mrc"))
+            volume.setFileName(self._getExtraPath("filteredMap.mrc"))
+            if self.halfVolumesFile:
+                volume.setSamplingRate(self.inputHalves.get().getSamplingRate())
+                self._defineOutputs(fsc3D=volume)
+                self._defineOutputs(directionalFilteredMap=volume)
+                self._defineSourceRelation(self.inputHalves, volume)
+            else:
+                volume.setSamplingRate(self.half1.get().getSamplingRate())
+                self._defineOutputs(fsc3D=volume)
+                self._defineOutputs(directionalFilteredMap=volume)
+                self._defineSourceRelation(self.half1, volume)
+
+
+
 
 
 
@@ -175,11 +230,25 @@ def _methods(self):
             messages.append(
                 'Information about the method/article in ')
         return messages
-    
+
+    def _validate(self):
+        errors = []
+
+        if self.halfVolumesFile.get():
+            if not self.inputHalves.get():
+                errors.append("You need to select the Associated halves")
+        else:
+            if not self.half1.get():
+                errors.append("You need to select the half1")
+            if not self.half2.get():
+                errors.append("You need to select the half2")
+
+        return errors
+
     def _summary(self):
         summary = []
         summary.append(" ")
         return summary
 
     def _citations(self):
-        return ['Vilas2020']
+        return ['Vilas2021']
diff --git a/xmipp3/protocols/protocol_resolution_monogenic_signal.py b/xmipp3/protocols/protocol_resolution_monogenic_signal.py
index 640f442bc..d8485d045 100644
--- a/xmipp3/protocols/protocol_resolution_monogenic_signal.py
+++ b/xmipp3/protocols/protocol_resolution_monogenic_signal.py
@@ -29,15 +29,14 @@
 
 from pyworkflow import VERSION_1_1
 from pyworkflow.protocol.params import (PointerParam, BooleanParam, FloatParam,
-                                        LEVEL_ADVANCED)
+                                        LEVEL_ADVANCED, EnumParam)
 from pwem.protocols import ProtAnalysis3D
 from pyworkflow.object import Float
 from pwem.emlib.image import ImageHandler
 from pyworkflow.utils import getExt
 from pwem.objects import Volume
 import pwem.emlib.metadata as md
-from pwem.convert import Ccp4Header
-
+from ..utils import DeprecatedParam
 
 MONORES_METHOD_URL = 'http://github.com/I2PC/scipion/wiki/XmippProtMonoRes'
 OUTPUT_RESOLUTION_FILE = 'monoresResolutionMap.mrc'
@@ -56,112 +55,130 @@ class XmippProtMonoRes(ProtAnalysis3D):
     """
     _label = 'local MonoRes'
     _lastUpdateVersion = VERSION_1_1
-    
+
     def __init__(self, **args):
         ProtAnalysis3D.__init__(self, **args)
-        self.min_res_init = Float() 
+        self.min_res_init = Float()
         self.max_res_init = Float()
-       
-    
+
     # --------------------------- DEFINE param functions ----------------------
     def _defineParams(self, form):
         form.addSection(label='Input')
 
-        form.addParam('halfVolumes', BooleanParam, default=False,
+        self.halfVolumes = DeprecatedParam('useHalfVolumes', self)
+        form.addParam('useHalfVolumes', BooleanParam, default=False,
                       label="Would you like to use half volumes?",
                       help='The noise estimation for determining the '
-                      'local resolution is performed via half volumes.')
-
-        form.addParam('inputVolumes', PointerParam, pointerClass='Volume',
+                           'local resolution is performed via half volumes.')
+
+        self.halfVolumesFile = DeprecatedParam('hasHalfVolumesFile', self)
+        form.addParam('hasHalfVolumesFile', BooleanParam, default=True,
+                      condition='useHalfVolumes', allowsNull=True,
+                      label="Are the half volumes stored with the input volume?",
+                      help='Usually, the half volumes are stored as properties of '
+                           'the input volume. If this is not the case, set this to '
+                           'False and specify the two halves you want to use.')
+
+        self.inputVolumes = DeprecatedParam('fullMap', self)
+        form.addParam('fullMap', PointerParam, pointerClass='Volume',
                       label="Input Volume", important=True,
-                      condition = 'not halfVolumes',
+                      condition='not useHalfVolumes',
                       help='Select a volume for determining its '
-                      'local resolution.')
+                           'local resolution.')
 
-        form.addParam('inputVolume', PointerParam, pointerClass='Volume',
-                      label="Volume Half 1", important=True,
-                      condition = 'halfVolumes', 
+        form.addParam('associatedHalves', PointerParam, pointerClass='Volume',
+                      label="Input Half Maps", important=True,
+                      condition='useHalfVolumes and hasHalfVolumesFile',
                       help='Select a volume for determining its '
-                      'local resolution.')
+                           'local resolution.')
+
+        self.inputVolume = DeprecatedParam('halfMap1', self)
+        form.addParam('halfMap1', PointerParam, pointerClass='Volume',
+                      label="Volume Half 1", important=True,
+                      condition='useHalfVolumes and not hasHalfVolumesFile',
+                      help='Select the first half of a volume for determining its '
+                           'local resolution.')
 
-        form.addParam('inputVolume2', PointerParam, pointerClass='Volume',
+        self.inputVolume2 = DeprecatedParam('halfMap2', self)
+        form.addParam('halfMap2', PointerParam, pointerClass='Volume',
                       label="Volume Half 2", important=True,
-                      condition='halfVolumes',
-                      help='Select a second volume for determining a '
-                      'local resolution.')
+                      condition='useHalfVolumes and not hasHalfVolumesFile',
+                      help='Select the second half of a volume for determining a '
+                           'local resolution.')
 
-        form.addParam('Mask', PointerParam, pointerClass='VolumeMask', 
-                      condition='(halfVolumes) or (not halfVolumes)',
+        self.Mask = DeprecatedParam('mask', self)
+        form.addParam('mask', PointerParam, pointerClass='VolumeMask',
                       allowsNull=True,
-                      label="Binary Mask", 
+                      label="Binary Mask",
                       help='The mask determines which points are specimen'
-                      ' and which are not')
+                           ' and which are not')
 
         form.addParam('maskExcl', PointerParam, pointerClass='VolumeMask',
-		      expertLevel=LEVEL_ADVANCED,
+                      expertLevel=LEVEL_ADVANCED,
                       allowsNull=True,
-                      label="Exclude Area", 
+                      label="Exclude Area",
                       help='The mask determines the area of the protein to be'
-		      'excluded in the estimation of the local resolution')
+                           'excluded in the estimation of the local resolution')
 
         group = form.addGroup('Extra parameters')
         line = group.addLine('Resolution Range (Å)',
-                            help="If the user knows the range of resolutions or"
-                                " only a range of frequencies needs to be analysed." 
-                                "If Low is empty MonoRes will try to estimate the range. "
-                                "it should be better if a range is provided")
-        
-        group.addParam('significance', FloatParam, default=0.95, 
+                             help="If the user knows the range of resolutions or"
+                                  " only a range of frequencies needs to be analysed."
+                                  "If Low is empty MonoRes will try to estimate the range. "
+                                  "it should be better if a range is provided")
+
+        group.addParam('significance', FloatParam, default=0.95,
                        expertLevel=LEVEL_ADVANCED,
-                      label="Significance",
-                      help='Relution is computed using hipothesis tests, '
-                      'this value determines the significance of that test')
-        
-        group.addParam('maskthreshold', FloatParam, default=0.5, 
+                       label="Significance",
+                       help='Relution is computed using hypothesis tests, '
+                            'this value determines the significance of that test')
+
+        self.maskthreshold = DeprecatedParam('maskThreshold', self)
+        group.addParam('maskThreshold', FloatParam, default=0.5,
                        expertLevel=LEVEL_ADVANCED,
-                      label="Mask threshold",
-                      help='If the provided mask is not binary. Then, MonoRes'
-                      'will try to binarize it. Bmask values below the threshold'
-                      'will be change to 0 and above the thresthol will be 1')
-        
-        form.addParam('noiseonlyinhalves', BooleanParam, expertLevel=LEVEL_ADVANCED,
+                       label="Mask threshold",
+                       help='If the provided mask is not binary. Then, MonoRes'
+                            'will try to binarize it. Mask values below the threshold'
+                            'will be change to 0 and above the thresthol will be 1')
+
+        self.noiseonlyinhalves = DeprecatedParam('noiseOnlyInHalves', self)
+        form.addParam('noiseOnlyInHalves', BooleanParam, expertLevel=LEVEL_ADVANCED,
                       default=True,
-                      label="Use noise inside protein?", 
-                      condition = 'halfVolumes',
+                      label="Use noise inside protein?",
+                      condition='useHalfVolumes',
                       help='(Yes recommended) the noise distribution will be estimated'
-		      ' in the protein region (inside the mask) by means of the '
-		      'difference of both half maps.')
+                           ' in the protein region (inside the mask) by means of the '
+                           'difference of both half maps.')
 
         form.addParam('gaussianNoise', BooleanParam, expertLevel=LEVEL_ADVANCED,
                       default=False,
-                      label="Consider noise gaussian?", 
-                      help='It assumens the noise in the map as gaussian. '
-			'Note that this assumption might not be true, despite ,it is '
-			' in general.')
-        
+                      label="Consider noise gaussian?",
+                      help='It assumes the noise in the map as gaussian. '
+                           'Note that this assumption might not be true, despite ,it is '
+                           ' in general.')
+
         line.addParam('minRes', FloatParam, default=0, label='High')
         line.addParam('maxRes', FloatParam, allowsNull=True, label='Low')
         line.addParam('stepSize', FloatParam, allowsNull=True,
                       expertLevel=LEVEL_ADVANCED, label='Step')
-        
-        form.addParallelSection(threads = 4, mpi = 0)
 
-    # --------------------------- INSERT steps functions --------------------------------------------
+        form.addParallelSection(threads=4, mpi=0)
 
+    # --------------------------- INSERT steps functions --------------------------------------------
     def _createFilenameTemplates(self):
         """ Centralize how files are called """
         myDict = {
-                 FN_MEAN_VOL: self._getExtraPath('mean_volume.mrc'),
-                 METADATA_MASK_FILE: self._getExtraPath('mask_data.xmd'),
-                 BINARY_MASK: self._getExtraPath('binarized_mask.mrc'),
-                 FN_GAUSSIAN_MAP: self._getExtraPath('gaussianfilted.mrc')
-                 }
+            FN_MEAN_VOL: self._getExtraPath('mean_volume.mrc'),
+            METADATA_MASK_FILE: self._getExtraPath('mask_data.xmd'),
+            BINARY_MASK: self._getExtraPath('binarized_mask.mrc'),
+            FN_GAUSSIAN_MAP: self._getExtraPath('gaussianfilted.mrc')
+        }
         self._updateFilenamesDict(myDict)
 
     def _insertAllSteps(self):
-            # Convert input into xmipp Metadata format
-        self._createFilenameTemplates() 
-        self._insertFunctionStep('convertInputStep', )
+        # Convert input into xmipp Metadata format
+        self._createFilenameTemplates()
+        self._insertFunctionStep('convertInputStep')
         self._insertFunctionStep('resolutionMonogenicSignalStep')
         self._insertFunctionStep('createOutputStep')
         self._insertFunctionStep("createHistogram")
@@ -169,96 +186,96 @@ def _insertAllSteps(self):
     def convertInputStep(self):
         """ Read the input volume.
         """
-        
-        self.micsFn = self._getPath()
-
-        if self.halfVolumes:
-            self.vol1Fn = self.inputVolume.get().getFileName()
-            self.vol2Fn = self.inputVolume2.get().getFileName()
+        if self.useHalfVolumes:
+            if not self.hasHalfVolumesFile:
+                self.vol1Fn = self.halfMap1.get().getFileName()
+                self.vol2Fn = self.halfMap2.get().getFileName()
+            else:
+                self.vol1Fn, self.vol2Fn = self.associatedHalves.get().getHalfMaps().split(',')
             extVol1 = getExt(self.vol1Fn)
             extVol2 = getExt(self.vol2Fn)
             if (extVol1 == '.mrc') or (extVol1 == '.map'):
                 self.vol1Fn = self.vol1Fn + ':mrc'
             if (extVol2 == '.mrc') or (extVol2 == '.map'):
                 self.vol2Fn = self.vol2Fn + ':mrc'
-                
-            if not self.Mask.hasValue():
+
+            if not self.mask.hasValue():
                 self.ifNomask(self.vol1Fn)
             else:
-                self.maskFn = self.Mask.get().getFileName()
-
-            self.inputVolumes.set(None)
+                self.maskFn = self.mask.get().getFileName()
         else:
-            self.vol0Fn = self.inputVolumes.get().getFileName()
+            self.vol0Fn = self.fullMap.get().getFileName()
             extVol0 = getExt(self.vol0Fn)
             if (extVol0 == '.mrc') or (extVol0 == '.map'):
                 self.vol0Fn = self.vol0Fn + ':mrc'
-                
-            if not self.Mask.hasValue():
+
+            if not self.mask.hasValue():
                 self.ifNomask(self.vol0Fn)
             else:
-                self.maskFn = self.Mask.get().getFileName()
-            
-            self.inputVolume.set(None)
-            self.inputVolume2.set(None)
-
+                self.maskFn = self.mask.get().getFileName()
 
         extMask = getExt(self.maskFn)
         if (extMask == '.mrc') or (extMask == '.map'):
             self.maskFn = self.maskFn + ':mrc'
-            
-        if self.Mask.hasValue():
+
+        if self.mask.hasValue():
             params = ' -i %s' % self.maskFn
             params += ' -o %s' % self._getFileName(BINARY_MASK)
-            params += ' --select below %f' % self.maskthreshold.get()
+            params += ' --select below %f' % self.maskThreshold.get()
             params += ' --substitute binarize'
-             
+
             self.runJob('xmipp_transform_threshold', params)
 
     def ifNomask(self, fnVol):
-        if self.halfVolumes:
-            xdim, _ydim, _zdim = self.inputVolume.get().getDim()
-            params = ' -i %s' % fnVol
+        if self.useHalfVolumes:
+            if not self.hasHalfVolumesFile:
+                xdim, _ydim, _zdim = self.halfMap1.get().getDim()
+                params = ' -i %s' % fnVol
+            else:
+                xdim, _ydim, _zdim = self.associatedHalves.get().getDim()
+                params = ' -i %s' % fnVol
         else:
-            xdim, _ydim, _zdim = self.inputVolumes.get().getDim()
+            xdim, _ydim, _zdim = self.fullMap.get().getDim()
             params = ' -i %s' % fnVol
         params += ' -o %s' % self._getFileName(FN_GAUSSIAN_MAP)
-        setsize = 0.02*xdim
-        params += ' --fourier real_gaussian %f' % (setsize)
-     
+        setsize = 0.02 * xdim
+        params += ' --fourier real_gaussian %f' % setsize
+
         self.runJob('xmipp_transform_filter', params)
         img = ImageHandler().read(self._getFileName(FN_GAUSSIAN_MAP))
         imgData = img.getData()
-        max_val = np.amax(imgData)*0.05
-         
+        max_val = np.amax(imgData) * 0.05
+
         params = ' -i %s' % self._getFileName(FN_GAUSSIAN_MAP)
         params += ' --select below %f' % max_val
         params += ' --substitute binarize'
         params += ' -o %s' % self._getFileName(BINARY_MASK)
-     
+
         self.runJob('xmipp_transform_threshold', params)
-        
-        self.maskFn = self._getFileName(BINARY_MASK)
 
+        self.maskFn = self._getFileName(BINARY_MASK)
 
     def resolutionMonogenicSignalStep(self):
         if self.stepSize.hasValue():
             freq_step = self.stepSize.get()
         else:
-            freq_step = 0.25
+            freq_step = 0.5
 
-        if self.halfVolumes:
+        if self.useHalfVolumes:
             params = ' --vol %s' % self.vol1Fn
             params += ' --vol2 %s' % self.vol2Fn
-            params += ' --sampling_rate %f' % self.inputVolume.get().getSamplingRate()
-            if (self.noiseonlyinhalves.get() is True):
+            if self.hasHalfVolumesFile:
+                params += ' --sampling_rate %f' % self.associatedHalves.get().getSamplingRate()
+            else:
+                params += ' --sampling_rate %f' % self.halfMap1.get().getSamplingRate()
+            if self.noiseOnlyInHalves.get() is True:
                 params += ' --noiseonlyinhalves'
         else:
             params = ' --vol %s' % self.vol0Fn
-            params += ' --sampling_rate %f' % self.inputVolumes.get().getSamplingRate()
-        
+            params += ' --sampling_rate %f' % self.fullMap.get().getSamplingRate()
+
         params += ' --mask %s' % self._getFileName(BINARY_MASK)
-        
+
         if self.maskExcl.hasValue():
             params += ' --maskExcl %s' % self.maskExcl.get().getFileName()
 
@@ -269,17 +286,15 @@ def resolutionMonogenicSignalStep(self):
         if self.gaussianNoise.get() is True:
             params += ' --gaussian'
         params += ' --significance %f' % self.significance.get()
-        params += ' --threads %i' % self.numberOfThreads.get()  
+        params += ' --threads %i' % self.numberOfThreads.get()
 
         self.runJob('xmipp_resolution_monogenic_signal', params)
 
-
-
     def createHistogram(self):
 
         M = float(self.max_res_init)
         m = float(self.min_res_init)
-        range_res = round((M - m)*4.0)
+        range_res = round((M - m) * 4.0)
 
         params = ' -i %s' % self._getExtraPath(OUTPUT_RESOLUTION_FILE)
         params += ' --mask binary_file %s' % self._getExtraPath(OUTPUT_MASK_FILE)
@@ -288,14 +303,13 @@ def createHistogram(self):
         params += ' -o %s' % self._getExtraPath(FN_METADATA_HISTOGRAM)
 
         self.runJob('xmipp_image_histogram', params)
-        
-        
+
     def readMetaDataOutput(self):
         mData = md.MetaData(self._getFileName(METADATA_MASK_FILE))
         NvoxelsOriginalMask = float(mData.getValue(md.MDL_COUNT, mData.firstObject()))
         NvoxelsOutputMask = float(mData.getValue(md.MDL_COUNT2, mData.firstObject()))
         nvox = int(round(
-                ((NvoxelsOriginalMask-NvoxelsOutputMask)/NvoxelsOriginalMask)*100))
+            ((NvoxelsOriginalMask - NvoxelsOutputMask) / NvoxelsOriginalMask) * 100))
         return nvox
 
     def getMinMax(self, imageFile):
@@ -306,48 +320,32 @@ def getMinMax(self, imageFile):
         return min_res, max_res
 
     def createOutputStep(self):
-        volume=Volume()
+        volume = Volume()
         volume.setFileName(self._getExtraPath(OUTPUT_RESOLUTION_FILE))
-        if (self.halfVolumes):
-            volume.setSamplingRate(self.inputVolume.get().getSamplingRate())
-            volume.setOrigin(self.inputVolume.get().getOrigin(True))
-            self._defineOutputs(resolution_Volume=volume)
-            self._defineSourceRelation(self.inputVolume, volume)
-            inputVolumeFileName = self.inputVolume.get().getFileName()
+        if self.useHalfVolumes:
+            if not self.hasHalfVolumesFile:
+                volume.setSamplingRate(self.halfMap1.get().getSamplingRate())
+                volume.setOrigin(self.halfMap1.get().getOrigin(True))
+                self._defineOutputs(resolution_Volume=volume)
+                self._defineSourceRelation(self.halfMap1, volume)
+            else:
+                volume.setSamplingRate(self.associatedHalves.get().getSamplingRate())
+                volume.setOrigin(self.associatedHalves.get().getOrigin(True))
+                self._defineOutputs(resolution_Volume=volume)
+                self._defineSourceRelation(self.associatedHalves, volume)
         else:
-            volume.setSamplingRate(self.inputVolumes.get().getSamplingRate())
-            volume.setOrigin(self.inputVolumes.get().getOrigin(True))
+            volume.setSamplingRate(self.fullMap.get().getSamplingRate())
+            volume.setOrigin(self.fullMap.get().getOrigin(True))
             self._defineOutputs(resolution_Volume=volume)
-            self._defineSourceRelation(self.inputVolumes, volume)
-            inputVolumeFileName = self.inputVolumes.get().getFileName()
+            self._defineSourceRelation(self.fullMap, volume)
 
-        #Setting the min max for the summary
+        # Setting the min max for the summary
         imageFile = self._getExtraPath(OUTPUT_RESOLUTION_FILE_CHIMERA)
         min_, max_ = self.getMinMax(imageFile)
-        self.min_res_init.set(round(min_*100)/100)
-        self.max_res_init.set(round(max_*100)/100)
+        self.min_res_init.set(round(min_ * 100) / 100)
+        self.max_res_init.set(round(max_ * 100) / 100)
         self._store(self.min_res_init)
         self._store(self.max_res_init)
-            
-
-        # fill ccp4 header so we can transfer the origin to the
-        # viewer (otherwise since imageFile is not a Scipion object
-        # no sampling/origin information can be transfered
-
-        #Ccp4Header(imageFile).copyCCP4Header(
-        #    inputVolumeFileName, volume.getShiftsFromOrigin(),
-        #    volume.getSamplingRate(), originField=Ccp4Header.START)
-
-        # also update the output volume header. This is not needed
-        # since sampling and origin is in the database but
-        # it may be usefull if other programs -outside scipion-
-        # require  these data.
-
-        #Ccp4Header(volume.getFileName()).copyCCP4Header(
-        #     inputVolumeFileName, volume.getShiftsFromOrigin(),
-        #     volume.getSamplingRate(), originField=Ccp4Header.START)
-
-
 
     # --------------------------- INFO functions ------------------------------
 
@@ -357,7 +355,7 @@ def _methods(self):
             messages.append(
                 'Information about the method/article in ' + MONORES_METHOD_URL)
         return messages
-    
+
     def _summary(self):
         summary = []
         summary.append("Highest resolution %.2f Å,   "
@@ -365,6 +363,24 @@ def _summary(self):
                                                          self.max_res_init))
         return summary
 
+    def _validate(self):
+        errors = []
+
+        if not self.maxRes.get():
+            errors.append("You must provide a low resolution limit")
+
+        if self.useHalfVolumes.get():
+            if self.hasHalfVolumesFile.get():
+                if not self.associatedHalves.get():
+                    errors.append("You need to select the Associated halves")
+            else:
+                if not self.halfMap1.get() or not self.halfMap2.get():
+                    errors.append("You need to select the volumes half")
+        else:
+            if not self.fullMap.get():
+                errors.append("You need to select an input volume")
+
+        return errors
+
     def _citations(self):
         return ['Vilas2018']
-
diff --git a/xmipp3/protocols/protocol_resolution_monotomo.py b/xmipp3/protocols/protocol_resolution_monotomo.py
deleted file mode 100644
index 78ed936e3..000000000
--- a/xmipp3/protocols/protocol_resolution_monotomo.py
+++ /dev/null
@@ -1,285 +0,0 @@
-# -*- coding: utf-8 -*-
-# **************************************************************************
-# *
-# * Authors:     Jose Luis Vilas (jlvilas@cnb.csic.es)
-# *
-# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
-# *
-# * 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 numpy as np
-
-from pyworkflow import VERSION_2_0
-from pyworkflow.protocol.params import (PointerParam, BooleanParam, FloatParam,
-                                        LEVEL_ADVANCED)
-from pwem.protocols import ProtAnalysis3D
-from pyworkflow.object import Float
-from pwem.emlib.image import ImageHandler
-from pyworkflow.utils import getExt
-from pwem.objects import Volume
-import pwem.emlib.metadata as md
-
-MONORES_METHOD_URL = 'http://github.com/I2PC/scipion/wiki/XmippProtMonoRes'
-OUTPUT_RESOLUTION_FILE = 'resolutionMap'
-FN_FILTERED_MAP = 'filteredMap'
-FN_MEAN_VOL = 'meanvol'
-FN_MASK_WEDGE = 'maskwedge'
-METADATA_MASK_FILE = 'metadataresolutions'
-FN_METADATA_HISTOGRAM = 'mdhist'
-BINARY_MASK = 'binarymask'
-FN_GAUSSIAN_MAP = 'gaussianfilter'
-
-
-class XmippProtMonoTomo(ProtAnalysis3D):
-    """
-    Given a map the protocol assigns local resolutions to each voxel of the map.
-    """
-    _label = 'local MonoTomo'
-    _lastUpdateVersion = VERSION_2_0
-
-    def __init__(self, **args):
-        ProtAnalysis3D.__init__(self, **args)
-        self.min_res_init = Float()
-        self.max_res_init = Float()
-
-    # --------------------------- DEFINE param functions ----------------------
-    def _defineParams(self, form):
-        form.addSection(label='Input')
-
-        form.addParam('inputVolume', PointerParam, pointerClass='Volume',
-                      label="Odd tomogram", important=True,
-                      help='Select a volume for determining its '
-                           'local resolution.')
-
-        form.addParam('inputVolume2', PointerParam, pointerClass='Volume',
-                      label="Even Tomogram", important=True,
-                      help='Select a second volume for determining a '
-                           'local resolution.')
-
-        form.addParam('useMask', BooleanParam, default=False,
-                      label="Use mask?",
-                      help='The mask determines which points are specimen'
-                           ' and which are not.')
-
-        form.addParam('Mask', PointerParam, pointerClass='VolumeMask',
-                      condition='useMask', allowsNull=True,
-                      label="Binary Mask",
-                      help='The mask determines which points are specimen'
-                           ' and which are not')
-
-        group = form.addGroup('Extra parameters')
-        line = group.addLine('Resolution Range (Å)',
-                             help="Resolution range (and step in expert mode) "
-                                  "to evaluate the local resolution.")
-
-        group.addParam('significance', FloatParam, default=0.95,
-                       expertLevel=LEVEL_ADVANCED,
-                       label="Significance",
-                       help='Relution is computed using hipothesis tests, '
-                            'this value determines the significance of that test')
-
-        line.addParam('minRes', FloatParam, default=0, label='High')
-        line.addParam('maxRes', FloatParam, allowsNull=True, label='Low')
-        line.addParam('stepSize', FloatParam, allowsNull=True, default=0.25,
-                      expertLevel=LEVEL_ADVANCED, label='Step')
-
-        form.addParallelSection(threads=4, mpi=0)
-
-    # --------------------------- INSERT steps functions --------------------------------------------
-
-    def _createFilenameTemplates(self):
-        """ Centralize how files are called """
-        myDict = {
-            FN_MEAN_VOL: self._getExtraPath('mean_volume.mrc'),
-            FN_MASK_WEDGE: self._getExtraPath('wedgeMask.mrc'),
-            FN_FILTERED_MAP: self._getExtraPath('filteredMap.vol'),
-            OUTPUT_RESOLUTION_FILE: self._getExtraPath('mgresolution.mrc'),
-            METADATA_MASK_FILE: self._getExtraPath('mask_data.xmd'),
-            FN_METADATA_HISTOGRAM: self._getExtraPath('hist.xmd'),
-            BINARY_MASK: self._getExtraPath('binarized_mask.mrc'),
-            FN_GAUSSIAN_MAP: self._getExtraPath('gaussianfilted.mrc'),
-        }
-        self._updateFilenamesDict(myDict)
-
-    def _insertAllSteps(self):
-        # Convert input into xmipp Metadata format
-        self._createFilenameTemplates()
-        self._insertFunctionStep('convertInputStep')
-        self._insertFunctionStep('resolutionMonoTomoStep')
-        self._insertFunctionStep('createOutputStep')
-        self._insertFunctionStep("createHistrogram")
-
-    def convertInputStep(self):
-        """ Read the input volume.
-        """
-
-        self.vol1Fn = self.inputVolume.get().getFileName()
-        self.vol2Fn = self.inputVolume2.get().getFileName()
-        extVol1 = getExt(self.vol1Fn)
-        extVol2 = getExt(self.vol2Fn)
-        if (extVol1 == '.mrc') or (extVol1 == '.map'):
-            self.vol1Fn = self.vol1Fn + ':mrc'
-        if (extVol2 == '.mrc') or (extVol2 == '.map'):
-            self.vol2Fn = self.vol2Fn + ':mrc'
-
-        if self.useMask.get():
-            if (not self.Mask.hasValue()):
-                self.ifNomask(self.vol1Fn)
-            else:
-                self.maskFn = self.Mask.get().getFileName()
-
-            extMask = getExt(self.maskFn)
-
-            if (extMask == '.mrc') or (extMask == '.map'):
-                self.maskFn = self.maskFn + ':mrc'
-
-            if self.Mask.hasValue():
-                params = ' -i %s' % self.maskFn
-                params += ' -o %s' % self._getFileName(BINARY_MASK)
-                params += ' --select below %f' % 0.5  # Mask threshold = 0.5 self.maskthreshold.get()
-                params += ' --substitute binarize'
-
-                self.runJob('xmipp_transform_threshold', params)
-
-    def ifNomask(self, fnVol):
-        xdim, _ydim, _zdim = self.inputVolume.get().getDim()
-        params = ' -i %s' % fnVol
-        params += ' -o %s' % self._getFileName(FN_GAUSSIAN_MAP)
-        setsize = 0.02 * xdim
-        params += ' --fourier real_gaussian %f' % (setsize)
-
-        self.runJob('xmipp_transform_filter', params)
-        img = ImageHandler().read(self._getFileName(FN_GAUSSIAN_MAP))
-        imgData = img.getData()
-        max_val = np.amax(imgData) * 0.05
-
-        params = ' -i %s' % self._getFileName(FN_GAUSSIAN_MAP)
-        params += ' --select below %f' % max_val
-        params += ' --substitute binarize'
-        params += ' -o %s' % self._getFileName(BINARY_MASK)
-
-        self.runJob('xmipp_transform_threshold', params)
-
-        self.maskFn = self._getFileName(BINARY_MASK)
-
-    def maskRadius(self):
-
-        xdim, _ydim, _zdim = self.inputVolume.get().getDim()
-        xdim = xdim * 0.5
-
-        return xdim
-
-    def resolutionMonoTomoStep(self):
-        # Number of frequencies
-        max_ = self.maxRes.get()
-
-        if self.stepSize.hasValue():
-            freq_step = self.stepSize.get()
-        else:
-            freq_step = 0.25
-
-        xdim = self.maskRadius()
-
-        params = ' --vol %s' % self.vol1Fn
-        params += ' --vol2 %s' % self.vol2Fn
-        params += ' --meanVol %s' % self._getFileName(FN_MEAN_VOL)
-
-        if self.useMask.get():
-            params += ' --mask %s' % self._getFileName(BINARY_MASK)
-
-        params += ' --sampling_rate %f' % self.inputVolume.get().getSamplingRate()
-        params += ' --minRes %f' % self.minRes.get()
-        params += ' --maxRes %f' % max_
-        params += ' --step %f' % freq_step
-        params += ' -o %s' % self._getFileName(OUTPUT_RESOLUTION_FILE)
-        params += ' --significance %f' % self.significance.get()
-
-        self.runJob('xmipp_resolution_monotomo', params)
-
-    def createHistrogram(self):
-
-        freq_step = self.stepSize.get() if self.stepSize.hasValue() else 10
-
-        M = float(self.max_res_init)
-        m = float(self.min_res_init)
-        range_res = round((M - m) / freq_step)
-
-        params = ' -i %s' % self._getFileName(OUTPUT_RESOLUTION_FILE)
-
-        if self.useMask.get():
-            params += ' --mask binary_file %s' % self._getFileName(BINARY_MASK)
-
-        params += ' --steps %f' % range_res
-        params += ' --range %f %f' % (self.min_res_init.get(),
-                                      (float(self.max_res_init.get()) - float(freq_step)))
-        params += ' -o %s' % self._getFileName(FN_METADATA_HISTOGRAM)
-
-        self.runJob('xmipp_image_histogram', params)
-
-    def readMetaDataOutput(self):
-        mData = md.MetaData(self._getFileName(METADATA_MASK_FILE))
-        NvoxelsOriginalMask = float(mData.getValue(md.MDL_COUNT, mData.firstObject()))
-        NvoxelsOutputMask = float(mData.getValue(md.MDL_COUNT2, mData.firstObject()))
-        nvox = int(round(
-            ((NvoxelsOriginalMask - NvoxelsOutputMask) / NvoxelsOriginalMask) * 100))
-        return nvox
-
-    def getMinMax(self, imageFile):
-        img = ImageHandler().read(imageFile)
-        imgData = img.getData()
-        min_res = round(np.amin(imgData) * 100) / 100
-        max_res = round(np.amax(imgData) * 100) / 100
-        return min_res, max_res
-
-    def createOutputStep(self):
-        volume = Volume()
-        volume.setFileName(self._getFileName(OUTPUT_RESOLUTION_FILE))
-
-        volume.setSamplingRate(self.inputVolume.get().getSamplingRate())
-        self._defineOutputs(resolution_Volume=volume)
-        self._defineSourceRelation(self.inputVolume, volume)
-
-        # Setting the min max for the summary
-        imageFile = self._getFileName(OUTPUT_RESOLUTION_FILE)
-        min_, max_ = self.getMinMax(imageFile)
-        self.min_res_init.set(round(min_ * 100) / 100)
-        self.max_res_init.set(round(max_ * 100) / 100)
-        self._store(self.min_res_init)
-        self._store(self.max_res_init)
-
-    # --------------------------- INFO functions ------------------------------
-
-    def _methods(self):
-        messages = []
-        if hasattr(self, 'resolution_Volume'):
-            messages.append(
-                'Information about the method/article in ' + MONORES_METHOD_URL)
-        return messages
-
-    def _summary(self):
-        summary = []
-        summary.append("Highest resolution %.2f Å,   "
-                       "Lowest resolution %.2f Å. \n" % (self.min_res_init,
-                                                         self.max_res_init))
-        return summary
-
-    def _citations(self):
-        return ['Vilas2018']
diff --git a/xmipp3/protocols/protocol_screen_deepConsensus.py b/xmipp3/protocols/protocol_screen_deepConsensus.py
index 37667382b..a502e2623 100644
--- a/xmipp3/protocols/protocol_screen_deepConsensus.py
+++ b/xmipp3/protocols/protocol_screen_deepConsensus.py
@@ -2,6 +2,7 @@
 # *
 # * Authors:    Ruben Sanchez Garcia (rsanchez@cnb.csic.es)
 # *             David Maluenda (dmaluenda@cnb.csic.es)
+# *             Daniel Del Hoyo (daniel.delhoyo.gomez@alumnos.upm.es)
 # *
 # * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
 # *
@@ -27,31 +28,30 @@
 """
 Deep Consensus picking protocol
 """
-import os
+import os, sys
 from glob import glob
 import six
-import json
+import json, shutil, pickle
 
 from pyworkflow import VERSION_2_0
 from pyworkflow.utils.path import (makePath, cleanPattern, cleanPath, copyTree,
                                    createLink)
 from pwem.constants import RELATION_CTF, ALIGN_NONE
 from pwem.emlib.image import ImageHandler
-from pwem.objects import SetOfParticles
+from pwem.objects import SetOfParticles, Micrograph, Particle, Coordinate
 from pwem.protocols import ProtParticlePicking, ProtUserSubSet
 
 import pyworkflow.utils as pwutils
-import pyworkflow.protocol.params as params
+from pyworkflow.protocol import params, STATUS_NEW
 import pwem.emlib.metadata as md
 
 from pwem import emlib
 import xmipp3
 from xmipp3 import XmippProtocol
-from xmipp3.protocols.protocol_pick_noise import pickNoise_prepareInput
+from xmipp3.protocols.protocol_pick_noise import pickNoise_prepareInput, IN_COORDS_POS_DIR_BASENAME
 from xmipp3.convert import (readSetOfParticles, setXmippAttributes,
                             micrographToCTFParam, writeSetOfParticles,
-                            writeSetOfCoordinates, readSetOfCoordsFromPosFnames)
-
+                            writeSetOfCoordinates, readSetOfCoordsFromPosFnames, readSetOfCoordinates)
 
 MIN_NUM_CONSENSUS_COORDS = 256
 
@@ -68,10 +68,15 @@ class XmippProtScreenDeepConsensus(ProtParticlePicking, XmippProtocol):
         using different subset of picked and not picked cooridantes. Finally,
         a coordinate is considered to be a correct particle according to the
         neural network predictions.
+        In streaming, the network is trained and used to predict in batches.
+        The network is trained until the number of particles set is reached,
+        meanwhile, a preliminary output is generated. Once the threshold is reached,
+        the final output is produced by batches.
     """
     _label = 'deep consensus picking'
     _lastUpdateVersion = VERSION_2_0
     _conda_env = 'xmipp_DLTK_v0.3'
+    _stepsCheckSecs = 5              # time in seconds to check the steps
 
     CONSENSUS_COOR_PATH_TEMPLATE="consensus_coords_%s"
     CONSENSUS_PARTS_PATH_TEMPLATE="consensus_parts_%s"
@@ -91,9 +96,29 @@ class XmippProtScreenDeepConsensus(ProtParticlePicking, XmippProtocol):
     ADD_MODEL_TRAIN_PRETRAIN = 1
     ADD_MODEL_TRAIN_PREVRUN = 2
 
+    #Streaming parameters
+    PREPROCESSING = False
+    TO_EXTRACT_MICFNS = {'OR': [],
+                         'NOISE': [],
+                         'AND': [],
+                         'ADDITIONAL_COORDS_TRUE':[],
+                         'ADDITIONAL_COORDS_FALSE':[]}
+    EXTRACTING = {'OR': False,
+                  'NOISE': False,
+                  'AND': False}
+
+    TRAIN_BATCH_MAX = 20
+    TO_TRAIN_MICFNS = []
+    TRAINED_PARAMS_PATH = 'trainedParams.pickle'
+    TRAINING = False
+    PREDICTING = False
+
+    LAST_ROUND = False
+    ENDED = False
+
     def __init__(self, **args):
         ProtParticlePicking.__init__(self, **args)
-        self.stepsExecutionMode = params.STEPS_SERIAL
+        self.stepsExecutionMode = params.STEPS_PARALLEL
 
     def _defineParams(self, form):
         # GPU settings
@@ -108,7 +133,7 @@ def _defineParams(self, form):
                             " if you do not know what we are talking about."
                             " First core index is 0, second 1 and so on.")
                             
-        form.addParallelSection(threads=2, mpi=0)
+        form.addParallelSection(threads=1, mpi=1)
         
         form.addSection(label='Input')
         
@@ -193,7 +218,6 @@ def _defineParams(self, form):
                            'want to do phase flipping or you want to '
                            'associate CTF information to the particles.')
         
-
         form.addSection(label='Training')
         
         form.addParam('nEpochs', params.FloatParam,
@@ -231,6 +255,13 @@ def _defineParams(self, form):
                            'Tipical values are 1 to 5. The more the better '
                            'until a point where no gain is obtained. '
                            'Each model increases running time linearly')
+        form.addParam('toTrainDataSize', params.IntParam,
+                      label="Expected number of particles to use for training", default=20000,
+                      help='Number of particles for training the CNN. Once surpassed, there will not be more training\n'
+                           'Set to -1 to use all the particles found\n'
+                           'It will determine the size of the CNN'
+                           'Usually, the bigger the better, but more training data is needed\n'
+                           'Three CNN sizes: n < 1500 | 1500 <= n < 20000 | n >= 20000')
                            
         form.addParam('doTesting', params.BooleanParam, default=False,
                       label='Perform testing after training?', expertLevel=params.LEVEL_ADVANCED,
@@ -367,6 +398,21 @@ def _defineParams(self, form):
                            'additional data is equal to the contribution of '
                            'internal particles')
 
+        form.addSection(label='Streaming')
+        form.addParam('doPreliminarPredictions', params.BooleanParam, default=False,
+                      label="Perform preliminar predictions with on training CNN",
+                      help='The protocol will make preliminar preedictions with the network before it is fully trained\n'
+                           'These preliminar results will be stored in a different output set')
+        form.addParam('extractingBatch', params.IntParam, default='5',
+                      label="Extraction batch size",
+                      help='Size of the extraction batches (in number of micrographs)')
+        form.addParam('trainingBatch', params.IntParam, default='5',
+                      label="Training batch size",
+                      help='Size of the training batches (in number of micrographs).'
+                           'The CNN needs a minimum number of particles to train for each batch, if there are not'
+                           'enough particles, the batch size must be increased')
+
+
     def _validate(self):
         errorMsg = []
         if self._getBoxSize()< DEEP_PARTICLE_SIZE:
@@ -409,44 +455,124 @@ def _usePretrainedModel(self):
         return self.modelInitialization.get()== self.ADD_MODEL_TRAIN_PRETRAIN
 
     def _insertAllSteps(self):
-
         self.inputMicrographs = None
         self.boxSize = None
         self.coordinatesDict = {}
-        deps = []
-        deps.append(self._insertFunctionStep("initializeStep", prerequisites=deps))
-        depsPrepMic = self._insertFunctionStep("preprocessMicsStep", prerequisites=deps)
-        depsPrepMic=[depsPrepMic]
-        # OR before noise always, as noise needs to need the number of OR coords
-        depsOr_cons = self.insertCaculateConsensusSteps('OR', prerequisites=depsPrepMic)
-        depsOr = self.insertExtractPartSteps('OR', prerequisites=depsOr_cons)
-        depsTrain = depsOr
-
-        if len(self.inputCoordinates)>1 and  not self.skipTraining.get():
-          depNoise = self._insertFunctionStep('pickNoise', prerequisites=depsOr)
-          depsNoise = self.insertExtractPartSteps('NOISE', prerequisites=[depNoise])
-
-          depsAnd = self.insertCaculateConsensusSteps('AND', prerequisites=depsOr)
-          depsAnd = self.insertExtractPartSteps('AND', prerequisites=depsAnd)
-
-          depsTrain = depsTrain + depsNoise + depsAnd
-
-          if self.addTrainingData.get() == self.ADD_DATA_TRAIN_CUST and self.trainingDataType == self.ADD_DATA_TRAIN_CUSTOM_OPT_COORS:
-            if self.trainTrueSetOfCoords.get() is not None:
-              depsTrain+= self.insertExtractPartSteps('ADDITIONAL_COORDS_TRUE', prerequisites=depsTrain)
-            if self.trainFalseSetOfCoords.get() is not None:
-              depsTrain+= self.insertExtractPartSteps('ADDITIONAL_COORDS_FALSE', prerequisites=depsTrain)
-          depTrain = self._insertFunctionStep('trainCNN', prerequisites=depsTrain)
-        else:
-          depTrain=None
-        depPredict = self._insertFunctionStep('predictCNN', prerequisites= [depTrain] if not depTrain is None else [])
-        self._insertFunctionStep('createOutputStep', prerequisites=[depPredict])
+
+        self.initDeps = [self._insertFunctionStep("initializeStep")]
+        self.lastStep = self._insertFunctionStep('lastRoundStep', wait=True, prerequisites=self.initDeps)
+        self.endStep = self._insertFunctionStep('endProtocolStep', wait=True, prerequisites=[self.lastStep])
+
+    def _stepsCheck(self):
+        '''Checks if new steps can be executed'''
+        newSteps = []
+        if not self.ENDED:
+          # Functions streamed. Input is processed as soon as it is generated
+          if not self.checkIfPrevRunIsCompatible("mics_"):
+            if len(self.readyToPreprocessMics(shared = False)) > 0 and not self.PREPROCESSING:
+              self.PREPROCESSING = True
+              self.lastDeps = [self._insertFunctionStep("preprocessMicsStep", prerequisites=self.initDeps)]
+
+          if len(self.readyToExtractMicFns('OR')) >= self.extractingBatch.get() and not self.EXTRACTING['OR']:
+            self.EXTRACTING['OR'] = True
+            newSteps += [self.insertCaculateConsensusSteps('OR', prerequisites=self.initDeps)]
+            newSteps += self.insertExtractPartSteps('OR', prerequisites=newSteps)
+
+
+          trainedParams = self.loadTrainedParams()
+          toTrainSize = self.toTrainDataSize.get() if self.toTrainDataSize.get() != -1 else 1e10
+          if not self.skipTraining.get() and trainedParams['posParticlesTrained'] < toTrainSize:
+            if len(self.readyToExtractMicFns('NOISE')) >= self.extractingBatch.get() and not self.EXTRACTING['NOISE']:
+              self.EXTRACTING['NOISE'] = True
+              depNoise = self._insertFunctionStep('pickNoise', prerequisites=self.initDeps)
+              depsNoise = self.insertExtractPartSteps('NOISE', prerequisites=[depNoise])
+              newSteps += depsNoise
+
+            if len(self.readyToExtractMicFns('AND')) >= self.extractingBatch.get() and not self.EXTRACTING['AND']:
+              self.EXTRACTING['AND'] = True
+              depsAnd = self.insertCaculateConsensusSteps('AND', prerequisites=self.initDeps)
+              depsAnd = self.insertExtractPartSteps('AND', prerequisites=[depsAnd])
+              newSteps += depsAnd
+
+            trainedParams = self.loadTrainedParams()
+            if self.addTrainingData.get() == self.ADD_DATA_TRAIN_CUST and\
+                    self.trainingDataType == self.ADD_DATA_TRAIN_CUSTOM_OPT_COORS:
+              if self.trainTrueSetOfCoords.get() is not None and \
+                      len(self.readyToExtractMicFns('ADDITIONAL_COORDS_TRUE')) >= self.extractingBatch.get():
+                self.TO_EXTRACT_MICFNS['ADDITIONAL_COORDS_TRUE'] = self.readyToExtractMicFns('ADDITIONAL_COORDS_TRUE')
+                newSteps += self.insertExtractPartSteps('ADDITIONAL_COORDS_TRUE', prerequisites=self.initDeps)
+              if self.trainFalseSetOfCoords.get() is not None and \
+                      len(self.readyToExtractMicFns('ADDITIONAL_COORDS_FALSE')) >= self.extractingBatch.get():
+                self.TO_EXTRACT_MICFNS['ADDITIONAL_COORDS_FALSE'] = self.readyToExtractMicFns('ADDITIONAL_COORDS_FALSE')
+                newSteps += self.insertExtractPartSteps('ADDITIONAL_COORDS_FALSE', prerequisites=self.initDeps)
+
+            if self.cnnFree():
+              self.TO_TRAIN_MICFNS = self.readyToTrainMicFns()
+              if len(self.TO_TRAIN_MICFNS) >= self.trainingBatch.get():
+                self.TRAINING = True
+                self.curTrainedParams = trainedParams
+                self.depsTrain = [self._insertFunctionStep('trainCNN', self.TO_TRAIN_MICFNS, prerequisites=self.initDeps)]
+                self.depsTrain = [self._insertFunctionStep('endTrainingStep', prerequisites=self.depsTrain)]
+                newSteps += self.depsTrain
+
+          elif trainedParams['posParticlesTrained'] >= toTrainSize and trainedParams['trainingPass'] != '':
+            lastTrainingPass = trainedParams['trainingPass']
+            self.retrievePreviousPassModel('', lastTrainingPass)
+            trainedParams['trainingPass'] = ''
+            self.saveTrainedParams(trainedParams)
+
+          if self.networkReadyToPredict() and self.cnnFree() and self.predictionsOn():
+              self.PREDICTING = True
+              depPredict = self._insertFunctionStep('predictCNN', prerequisites= newSteps)
+              newSteps += [self._insertFunctionStep('endPredictingStep', prerequisites=[depPredict])]
+              newSteps += [self._insertFunctionStep('createOutputStep', prerequisites=[depPredict])]
+
+          trainedParams = self.loadTrainedParams()
+          if self.allFree() and not self.LAST_ROUND and self.checkIfParentsFinished():
+            protLast = self._steps[self.lastStep - 1]
+            protLast.addPrerequisites(*newSteps)
+            protLast.setStatus(STATUS_NEW)
+            self.updateSteps()
+
+          protEnd = self._steps[self.endStep-1]
+          protEnd.addPrerequisites(*newSteps)
+          if self.LAST_ROUND and self.allFree():
+            protEnd.setStatus(STATUS_NEW)
+          self.updateSteps()
+          sys.stdout.flush()
+
+
+    def endPredictingStep(self):
+      self.PREDICTING = False
+
+    def endTrainingStep(self):
+      self.saveTrainedParams(self.curTrainedParams)
+      self.TRAINING = False
+
+    def lastRoundStep(self):
+      '''Starts the last round of training and predictions with the remainign microgrpahs
+      when all the inputs have arrived'''
+      self.extractingBatch.set(1)
+      self.trainingBatch.set(1)
+      self.LAST_ROUND = True
+
+    def endProtocolStep(self):
+      '''Finish the protocol with a final prediction using the final CNN'''
+      lastTrainingPass = self.loadTrainedParams()['trainingPass']
+      if lastTrainingPass != '':
+        self.retrievePreviousPassModel('', lastTrainingPass)
+        self.uploadTrainedParam('trainingPass', '')
+        self.ENDED = True
+        self.depLastPredict = self._insertFunctionStep('predictCNN', prerequisites=[self.endStep])
+        self._insertFunctionStep('createOutputStep', True, prerequisites=[self.depLastPredict])
+      else:
+        self.updateOutput(closeStream=True)
+
 
     def initializeStep(self):
         """
             Create paths where data will be saved
         """
-
         if self.doTesting.get() and self.testTrueSetOfParticles.get() and self.testFalseSetOfParticles.get():
             writeSetOfParticles(self.testTrueSetOfParticles.get(),
                                 self._getExtraPath("testTrueParticlesSet.xmd"))
@@ -480,13 +606,15 @@ def initializeStep(self):
             newDataPath= (self.CONSENSUS_COOR_PATH_TEMPLATE % "OR")
             createLink( self.continueRun.get()._getExtraPath(newDataPath),
                         self._getExtraPath(newDataPath))
+
         else:
           if self.checkIfPrevRunIsCompatible( "mics_"):
-            sourcePath= self.continueRun.get()._getTmpPath(self.PRE_PROC_MICs_PATH )
+            sourcePath= self.continueRun.get()._getExtraPath(self.PRE_PROC_MICs_PATH )
             print("copying mics from %s"%(sourcePath))
-            createLink( sourcePath, self._getTmpPath(self.PRE_PROC_MICs_PATH ))
+            createLink( sourcePath, self._getExtraPath(self.PRE_PROC_MICs_PATH ))
+
           else:
-            makePath(self._getTmpPath(self.PRE_PROC_MICs_PATH))
+            makePath(self._getExtraPath(self.PRE_PROC_MICs_PATH))
 
           if self.checkIfPrevRunIsCompatible( "coords_"):
             for mode in ["OR", "AND", "NOISE"]:
@@ -499,390 +627,306 @@ def initializeStep(self):
                 consensusCoordsPath = self.CONSENSUS_COOR_PATH_TEMPLATE % mode
                 makePath(self._getExtraPath(consensusCoordsPath))
 
+        if self._doContinue():
+          if self.skipTraining.get():
+            trPass=''
+          else:
+            trPass=1
+          self.retrievePreviousRunModel(self.continueRun.get(), trPass)
+          self.uploadTrainedParam('trainingPass', trPass)
+
         preprocessParamsFname= self._getExtraPath("preprocess_params.json")
         preprocParams= self.getPreProcParamsFromForm()
         with open(preprocessParamsFname, "w") as f:
           json.dump(preprocParams, f)
 
-    def retrieveTrainSets(self):
-        """ Retrieve, link and return a setOfParticles
-            corresponding to the NegativeTrain DeepConsensus trainning set
-            with certain extraction conditions (phaseFlip/invContrast)
-        """
-        prefixYES = ''
-        prefixNO = 'no'
-        #We always work with inverted contrast particles
-        modelType = "negativeTrain_%sPhaseFlip_Invert.mrcs" % (
-                    prefixNO if self.ignoreCTF.get() else prefixYES) # mics will be always internally inverted if not done before
-        modelPath = self.getModel("deepConsensus", modelType)
-        modelFn = self._getTmpPath(modelType)
-        pwutils.createLink(modelPath, modelFn)
-
-        tmpSqliteSuff = "AddTrain"
-        partSet = self._createSetOfParticles(tmpSqliteSuff)
-        img = SetOfParticles.ITEM_TYPE()
-
-        imgh = ImageHandler()
-        _, _, _, n = imgh.getDimensions(modelFn)
-        if n > 1:
-            for index in range(1, n + 1):
-                img.cleanObjId()
-                img.setMicId(9999)
-                img.setFileName(modelFn)
-                img.setIndex(index)
-                partSet.append(img)
-        partSet.setAlignment(ALIGN_NONE)
-
-        cleanPath(self._getPath("particles%s.sqlite"%tmpSqliteSuff))
-        return partSet
+        #Initializing outputs
+        self.preCorrectedParSet, self.preCoordSet = [], []
 
-    def _getInputMicrographs(self):
-        if not hasattr(self, "inputMicrographs") or not self.inputMicrographs:
-            if len(self.inputCoordinates)==0:
-              print("WARNING. PROVIDE MICROGRAPHS FIRST")
-            else:
-              inputMicrographs = self.inputCoordinates[0].get().getMicrographs()
-              if inputMicrographs is None:
-                raise ValueError("there are problems with your coordiantes, they do not have associated micrographs ")
-              self.inputMicrographs= inputMicrographs
-        return self.inputMicrographs
 
-    def _getBoxSize(self):
-        if not hasattr(self, "boxSize") or not self.boxSize:
-            firstCoords = self.inputCoordinates[0].get()
-            self.boxSize = firstCoords.getBoxSize()
-            self.downFactor = self.boxSize / float(DEEP_PARTICLE_SIZE)
-        return self.boxSize
-
-    def _getDownFactor(self):
-
-        if not hasattr(self, "downFactor") or not self.downFactor:
-          firstCoords = self._getInputMicrographs()
-          self.boxSize= firstCoords.getBoxSize()
-          self.downFactor = self.boxSize /float(DEEP_PARTICLE_SIZE)
-          assert self.downFactor >= 1, \
-              "Error, the particle box size must be greater or equal than 128."
-
-        return self.downFactor
+    def preprocessMicsStep(self):
+        '''Step which preprocesses the input micrographs'''
+        micIds = self.getMicsIds(filterOutNoCoords=False)
+        if len(micIds) > 0:
+          samplingRate = self._getInputMicrographs().getSamplingRate()
+          mics_ = self._getInputMicrographs()
+          micsFnameSet = {mics_[micId].getMicName(): mics_[micId].getFileName() for micId in micIds
+                          if mics_[micId] is not None}  # to skip failed mics
+          toPreprocessMicFns = self.readyToPreprocessMics(shared=False)
+          print('New mics being preprocessed: ', toPreprocessMicFns)
+          if self.ignoreCTF.get():
+            preproMicsContent="#mics\n"
+            for micName in micsFnameSet:
+              preproMicsContent+= "%s\n"%(micsFnameSet[micName])
+          else:
+            preproMicsContent="#mics ctfs\n"
+            setOfMicCtf= self.ctfRelations.get()
+            assert setOfMicCtf is not None, "Error, CTFs must be provided to compute phase flip"
+            for ctf in setOfMicCtf:
+              ctf_mic = ctf.getMicrograph()
+              ctfMicName = ctf_mic.getMicName()
+              if ctfMicName in micsFnameSet:
+                ctf_mic.setCTF(ctf)
+                ctfMicName= micsFnameSet[ctfMicName]
+                fnCTF = self._getTmpPath("%s.ctfParam" % os.path.basename(ctfMicName))
+                micrographToCTFParam(ctf_mic, fnCTF)
+                preproMicsContent+= "%s %s\n"%(ctfMicName, fnCTF)
+
+
+          inputsFname= self._getTmpPath("preprocMic_inputs.txt")
+          ouputDir= self._getExtraPath(self.PRE_PROC_MICs_PATH)
+          nThrs= self.numberOfThreads.get()
+          with open(inputsFname, "w") as f:
+            f.write(preproMicsContent)
+          downFactor = self._getDownFactor()
+          args= "-i %s -s %s -d %s -o %s -t %d"%(inputsFname, samplingRate, downFactor, ouputDir, nThrs)
+          if not self.skipInvert.get():
+            args+=" --invert_contrast"
+
+          if not self.ignoreCTF.get():
+            args+=" --phase_flip"
+
+          self.runJob('xmipp_preprocess_mics', args, numberOfMpi=1)
+        self.PREPROCESSING = False
 
 
     def insertCaculateConsensusSteps(self, mode, prerequisites):
-
-        deps = []
+        '''Insert the steps neccessary for calculating the consensus coordinates of type "mode"'''
         outCoordsDataPath = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE% mode)
-        if not self.checkIfPrevRunIsCompatible( "coords_"):
-          dep_ = prerequisites
+        if not os.path.exists(outCoordsDataPath):
           makePath(outCoordsDataPath)
-          newDep=self._insertFunctionStep('calculateCoorConsensusStep', outCoordsDataPath, mode, prerequisites=prerequisites)
-          deps = [newDep]
-          newDep = self._insertFunctionStep('loadCoords', outCoordsDataPath,
-                                          mode, prerequisites=deps)
-          deps = [newDep]
-        else:
-          deps= prerequisites
-        return deps
+        newDep = self._insertFunctionStep('calculateCoorConsensusStep', outCoordsDataPath, mode, prerequisites=prerequisites)
+        newDep = self._insertFunctionStep('loadCoords', outCoordsDataPath, mode, prerequisites=[newDep])
+        return newDep
 
     def calculateCoorConsensusStep(self, outCoordsDataPath, mode):
-      Tm = []
-      for coordinatesP in self.inputCoordinates:
-          mics= coordinatesP.get().getMicrographs()
+      '''Calculates the consensus coordinates from micrographs whose particles haven't been extracted yet in "mode"'''
+      #Only calculate consensus for coordinates that has not been extracted yet
+      if self.checkIfNewMics(mode):
+        extractedSetOfCoordsFns = []
+        for micFn in self.getExtractedMicFns(mode):
+          extractedSetOfCoordsFns.append(pwutils.path.replaceBaseExt(micFn,'pos'))
+
+        Tm = []
+        for coordinatesP in self.inputCoordinates:
+          mics = coordinatesP.get().getMicrographs()
           Tm.append(mics.getSamplingRate())
-      nCoordsSets= len(Tm)
-      inputCoordsFnames={}
-      for coord_num, coordinatesP in enumerate(self.inputCoordinates):
-          tmpPosDir= self._getTmpPath("input_coords_%d"%(coord_num))
-          makePath(tmpPosDir)
-          writeSetOfCoordinates(tmpPosDir, coordinatesP.get(), scale=float(Tm[coord_num])/float(Tm[0]))
-          for posFname in os.listdir(tmpPosDir):
-              baseName, extension=os.path.splitext(os.path.basename(posFname))
-              if extension==".pos":
-                if baseName not in inputCoordsFnames:
-                    inputCoordsFnames[baseName]=["None"]*nCoordsSets
-                inputCoordsFnames[baseName][coord_num]= os.path.join(tmpPosDir, posFname)
-      inputFileHeader="#pos_i\n"
-      inputFileStr=inputFileHeader
-      for baseName in inputCoordsFnames:
-         fnames= inputCoordsFnames[baseName]
-         inputFileStr+=" ".join(fnames)+"\n"
-
-      assert len(inputFileStr)>len(inputFileHeader), "Error, no consensus can be computed as there " \
-                                                     "are mismatch in coordinate sets filenames"
-      consensus = -1 if mode=="AND" else 1
-      configFname= self._getTmpPath("consensus_%s_inputs.txt"%(mode) )
-      with open(configFname, "w") as f:
-          f.write(inputFileStr)
-
-      args="-i %s -s %d -c %d -d %f -o %s -t %d"%(configFname, self._getBoxSize(), consensus, self.consensusRadius.get(),
-                                                 outCoordsDataPath, self.numberOfThreads.get())
-      self.runJob('xmipp_coordinates_consensus', args, numberOfMpi=1)
-
-
-    def loadCoords(self, posCoorsPath, mode):
-
-        sqliteName= self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE%mode)+".sqlite"
-        if os.path.isfile(self._getExtraPath(sqliteName)):
-            cleanPath(self._getExtraPath(sqliteName))
-        setOfCoordinates= readSetOfCoordsFromPosFnames(posCoorsPath, setOfInputCoords= self.inputCoordinates[0].get(),
-                                   sqliteOutName= sqliteName, write=True )
-        print("Coordinates %s size: %d"%( mode, setOfCoordinates.getSize()) )
-        assert setOfCoordinates.getSize() > MIN_NUM_CONSENSUS_COORDS, \
-                ("Error, the consensus (%s) of your input coordinates was too small (%s). "+
-                "It must be > %s. Try a different input..."
-                )% (mode, str(setOfCoordinates.getSize()), str(MIN_NUM_CONSENSUS_COORDS))
-
-        self.coordinatesDict[mode]= setOfCoordinates
+        nCoordsSets= len(Tm)
+
+        inputCoordsFnames={}
+        for coord_num, coordinatesP in enumerate(self.inputCoordinates):
+            tmpPosDir= self._getTmpPath("input_coords_%d_%s"%(coord_num, mode))
+            if not os.path.exists(tmpPosDir):
+              makePath(tmpPosDir)
+            writeSetOfCoordinates(tmpPosDir, coordinatesP.get(), scale=float(Tm[coord_num])/float(Tm[0]))
+            for posFname in os.listdir(tmpPosDir):
+                baseName, extension=os.path.splitext(os.path.basename(posFname))
+                if extension==".pos" and not posFname in extractedSetOfCoordsFns:
+                  if baseName not in inputCoordsFnames:
+                      inputCoordsFnames[baseName]=["None"]*nCoordsSets
+                  inputCoordsFnames[baseName][coord_num]= os.path.join(tmpPosDir, posFname)
+        inputFileHeader="#pos_i\n"
+        inputFileStr=inputFileHeader
+        for baseName in inputCoordsFnames:
+          fnames= inputCoordsFnames[baseName]
+          inputFileStr+=" ".join(fnames)+"\n"
+
+        assert len(inputFileStr)>len(inputFileHeader), "Error, no consensus can be computed as there " \
+                                                       "are mismatch in coordinate sets filenames"
+        consensus = -1 if mode=="AND" else 1
+        configFname= self._getTmpPath("consensus_%s_inputs.txt"%(mode) )
+        with open(configFname, "w") as f:
+            f.write(inputFileStr)
+
+        args="-i %s -s %d -c %d -d %f -o %s -t %d"%(configFname, self._getBoxSize(), consensus, self.consensusRadius.get(),
+                                                   outCoordsDataPath, self.numberOfThreads.get())
+        self.runJob('xmipp_coordinates_consensus', args, numberOfMpi=1)
+        self.TO_EXTRACT_MICFNS[mode] = self.readyToExtractMicFns(mode)
 
     def pickNoise(self):
-        if self.checkIfPrevRunIsCompatible("coords_"):
-          print("using previous round noise particles")
-        else:
-          outputPosDir= self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % "NOISE")
-          if not "OR" in self.coordinatesDict:  # fill self.coordinatesDict['OR']
-              self.loadCoords( self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % 'OR'),
-                               'OR')
-
-          argsDict=pickNoise_prepareInput(self.coordinatesDict['OR'], self._getTmpPath())
-          argsDict["outputPosDir"]= outputPosDir
+      '''Find noise coordinates from micrographs in order to use them as negatives in the training process'''
+      if self.checkIfPrevRunIsCompatible("coords_"):
+        print("using previous round noise particles")
+      else:
+        orPosDir = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % "OR")
+        outputPosDir = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % "NOISE")
+        if not "OR" in self.coordinatesDict:  # fill self.coordinatesDict['OR']
+          self.loadCoords(orPosDir, 'OR')
+        # Getting the extracted mics where noise have not been picked yet
+        micsDir = self._getExtraPath(self.PRE_PROC_MICs_PATH)
+        toPickNoiseFns = list(set(self.getExtractedMicFns('OR')) -
+                              (set(self.getExtractedMicFns('NOISE')) | set(self.TO_EXTRACT_MICFNS['NOISE'])))
+        if len(toPickNoiseFns) > 0:
+          micNoiseSet = self.loadMicSetFromFns(micsDir, toPickNoiseFns)
+          toNoiseSetOfCoords = self._createSetOfCoordinates(micNoiseSet)
+          toNoiseSetOfCoords.setBoxSize(self._getBoxSize())
+          readSetOfCoordinates(orPosDir, micSet=micNoiseSet, coordSet=toNoiseSetOfCoords)
+
+          # Write the tonoise mic files into a tmp directory
+          coordsDir = self._getTmpPath(IN_COORDS_POS_DIR_BASENAME)
+          toPickMicsDir = micsDir + '_toPickNoise'
+          toPickCoorsDir = coordsDir + '_toPickNoise'
+          if not os.path.exists(coordsDir):
+            makePath(coordsDir)
+          if os.path.exists(toPickMicsDir):
+            cleanPath(toPickMicsDir)
+          if os.path.exists(toPickCoorsDir):
+            cleanPath(toPickCoorsDir)
+          makePath(toPickMicsDir)
+          makePath(toPickCoorsDir)
+          writeSetOfCoordinates(coordsDir, toNoiseSetOfCoords)
+
+          for micFn in toPickNoiseFns:
+            posFn = pwutils.path.replaceBaseExt(micFn, 'pos')
+            shutil.copyfile(micsDir + '/' + micFn, toPickMicsDir + '/' + micFn)
+            shutil.copyfile(coordsDir + '/' + posFn, toPickCoorsDir + '/' + posFn)
+
+          argsDict = pickNoise_prepareInput(toNoiseSetOfCoords, self._getTmpPath())
+          argsDict['toPickMicsDir'] = toPickMicsDir
+          argsDict['toPickCoorsDir'] = toPickCoorsDir
+          argsDict["outputPosDir"] = outputPosDir
           argsDict["nThrs"] = self.numberOfThreads.get()
-          argsDict["nToPick"]=-1
-          args=(" -i %(mics_dir)s -c %(inCoordsPosDir)s -o %(outputPosDir)s -s %(boxSize)s "+
-                "-n %(nToPick)s -t %(nThrs)s")%argsDict
-
-          if not self.checkIfPrevRunIsCompatible( "coords_"):
-              self.runJob('xmipp_pick_noise', args, numberOfMpi=1)
-
-          self.loadCoords(outputPosDir, 'NOISE')
-
-
-    def getPreProcParamsFromForm(self):
-        mics_= self._getInputMicrographs()
-        mic = mics_.getFirstItem()
-        fnMic = mic.getFileName()
-        pathToMics= os.path.split(fnMic)[0]
-        pathToCtfs= "None"
-
-        if not self.ignoreCTF.get():
-          pathToCtfs=  os.path.split(self.ctfRelations.get().getFileName())[0]
-
-        paramsInfo={"mics_ignoreCTF": self.ignoreCTF.get(),
-                    "mics_skipInvert": self.skipInvert.get(),
-                    "mics_pathToMics": pathToMics,
-                    "mics_pathToCtfs": pathToCtfs}
-        coordsNames=[]
-        for inputCoords in self.inputCoordinates:
-          coordsNames.append( inputCoords.get().getFileName() )
-        coordsNames= tuple(sorted(coordsNames))
-        paramsInfo["coords_pathCoords"]= coordsNames
-
-        return paramsInfo
-
-    def checkIfPrevRunIsCompatible(self, inputType=""):
-        '''
-        inputType can be mics_ or coords_ or ""
-        '''
-        def _makeTupleIfList(candidateList):
-          if isinstance(candidateList, list):
-            return tuple(candidateList)
-          elif isinstance(candidateList, six.string_types):
-            return tuple([candidateList])
+          argsDict["nToPick"] = -1
+          args = (" -i %(toPickMicsDir)s -c %(toPickCoorsDir)s -o %(outputPosDir)s -s %(boxSize)s " +
+                  "-n %(nToPick)s -t %(nThrs)s") % argsDict
+
+          if not self.checkIfPrevRunIsCompatible("coords_"):
+            self.runJob('xmipp_pick_noise', args, numberOfMpi=1)
+
+          self.loadCoords(outputPosDir, 'NOISE', micSet=micNoiseSet)
+          self.TO_EXTRACT_MICFNS['NOISE'] = toPickNoiseFns
+          print('Adding to extract {} {} micrographs'.format(len(toPickNoiseFns), 'NOISE'))
+
+    def loadCoords(self, posCoorsPath, mode, micSet=[]):
+        #Upload coords sqlite
+        if not micSet == []:
+          #Load coordinates from an specific set of mics
+          batchSetOfCoordinates = self._createSetOfCoordinates(micSet)
+          batchSetOfCoordinates.setBoxSize(self._getBoxSize())
+          readSetOfCoordinates(posCoorsPath, micSet=micSet, coordSet = batchSetOfCoordinates)
+          if mode in self.coordinatesDict:
+            for newCoord in batchSetOfCoordinates:
+              apCoord = Coordinate()
+              apCoord.copy(newCoord, copyId=False)
+              self.coordinatesDict[mode].append(apCoord)
           else:
-            return candidateList
-        try:
-          preprocParams= self.getPreProcParamsFromForm()
-        except Exception:
-          return False
-        preprocParams= { k:_makeTupleIfList(preprocParams[k]) for k in preprocParams if k.startswith(inputType) }
-        if self._doContinue():
-            preprocessParamsFname = self.continueRun.get()._getExtraPath("preprocess_params.json")
-            with open(preprocessParamsFname) as f:
-              preprocParams_loaded = json.load(f)
-
-            preprocParams_loaded= {k: _makeTupleIfList(preprocParams_loaded[k])
-                                   for k in preprocParams_loaded
-                                   if k.startswith(inputType)}
-
-            for key in preprocParams_loaded:
-              if "path" in key:
-                for val in preprocParams_loaded[key]:
-                  if not val=="None" and not os.path.exists(val):
-                    return False
-
-            shared_items = {k: preprocParams_loaded[k]
-                            for k in preprocParams_loaded
-                            if k in preprocParams and preprocParams_loaded[k] == preprocParams[k]}
-
-            return (len(shared_items) == len(preprocParams) and
-                    len(shared_items) == len(preprocParams_loaded) and
-                    len(shared_items) > 0)
-
-        return False
-
-    def getMicsIds(self, filterOutNoCoords=False):
-
-        if not filterOutNoCoords:
-          return self._getInputMicrographs().getIdSet()
-        micIds= set([])
-        micFnames= set([])
-        for coordinatesP in self.inputCoordinates:
-            for coord in coordinatesP.get():
-              micIds.add( coord.getMicId())
-              micFnames.add( coord.getMicName() )
-        return sorted( micIds )
+            self.coordinatesDict[mode] = batchSetOfCoordinates
+        else:
+          sqliteName = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % mode) + ".sqlite"
+          if os.path.isfile(self._getExtraPath(sqliteName)):
+            cleanPath(self._getExtraPath(sqliteName))
+          totalSetOfCoordinates = readSetOfCoordsFromPosFnames(posCoorsPath,
+                                                               setOfInputCoords=self.inputCoordinates[0].get(),
+                                                               sqliteOutName=sqliteName, write=True)
+          print("Coordinates %s size: %d" % (mode, totalSetOfCoordinates.getSize()))
+          assert totalSetOfCoordinates.getSize() > MIN_NUM_CONSENSUS_COORDS, \
+            ("Error, the consensus (%s) of your input coordinates was too small (%s). " +
+             "It must be > %s. Try a different input..."
+             ) % (mode, str(totalSetOfCoordinates.getSize()), str(MIN_NUM_CONSENSUS_COORDS))
+          self.coordinatesDict[mode] = totalSetOfCoordinates
 
-    def preprocessMicsStep(self):
-        if not self.checkIfPrevRunIsCompatible( "mics_"): #or not os.path.isdir(self._getTmpPath(self.PRE_PROC_MICs_PATH)):
-            micIds = self.getMicsIds(filterOutNoCoords=True)
-            samplingRate = self._getInputMicrographs().getSamplingRate()
-            mics_ = self._getInputMicrographs()
-            micsFnameSet = {mics_[micId].getMicName(): mics_[micId].getFileName() for micId in micIds
-                            if mics_[micId] is not None}  # to skip failed mics
-            if self.ignoreCTF.get():
-              preproMicsContent="#mics\n"
-              for micName in micsFnameSet:
-                preproMicsContent+= "%s\n"%(micsFnameSet[micName])
-            else:
-              preproMicsContent="#mics ctfs\n"
-              setOfMicCtf= self.ctfRelations.get()
-              if setOfMicCtf.getSize() != len(micsFnameSet):
-                raise ValueError("Error, there are different number of CTFs compared to "+
-                                  "the number of micrographs where particles were picked")
-              else:
-                assert setOfMicCtf is not None, "Error, CTFs must be provided to compute phase flip"
-                for ctf in setOfMicCtf:
-                  ctf_mic = ctf.getMicrograph()
-                  ctfMicName = ctf_mic.getMicName()
-                  if ctfMicName in micsFnameSet:
-                    ctf_mic.setCTF(ctf)
-                    ctfMicName= micsFnameSet[ctfMicName]
-                    fnCTF = self._getTmpPath("%s.ctfParam" % os.path.basename(ctfMicName))
-                    micrographToCTFParam(ctf_mic, fnCTF)
-                    preproMicsContent+= "%s %s\n"%(ctfMicName, fnCTF)
-
-
-            inputsFname= self._getTmpPath("preprocMic_inputs.txt")
-            ouputDir= self._getTmpPath(self.PRE_PROC_MICs_PATH)
-            nThrs= self.numberOfThreads.get()
-            with open(inputsFname, "w") as f:
-              f.write(preproMicsContent)
-            downFactor = self._getDownFactor()
-            args= "-i %s -s %s -d %s -o %s -t %d"%(inputsFname, samplingRate, downFactor, ouputDir, nThrs)
-            if not self.skipInvert.get():
-              args+=" --invert_contrast"
-
-            if not self.ignoreCTF.get():
-              args+=" --phase_flip"
-
-            self.runJob('xmipp_preprocess_mics', args, numberOfMpi=1)
 
     def insertExtractPartSteps(self, mode, prerequisites):
-        deps = []
+        '''Inserts the steps necessary for extracting the particles from the micrographs'''
+        newSteps = []
         if not self.checkIfPrevRunIsCompatible(""):
-          newDep = self._insertFunctionStep("extractParticles", mode, prerequisites= prerequisites)
-          deps.append(newDep)
-          newDep= self._insertFunctionStep("joinSetOfParticlesStep", mode, prerequisites= deps)
-          deps.append(newDep)
-        else:
-          deps = prerequisites
-
-        return deps
-
-    def _getConsensusParticlesDir(self, mode):
-      pathFun = self._getTmpPath if mode != "OR" else self._getExtraPath
-      return pathFun(self.CONSENSUS_PARTS_PATH_TEMPLATE% mode)
+          newSteps.append(self._insertFunctionStep("extractParticles", mode, prerequisites= prerequisites))
+          newSteps.append(self._insertFunctionStep("joinSetOfParticlesStep", mode, prerequisites= newSteps))
+        return newSteps
 
     def extractParticles(self, mode):
-
+        '''Extract the particles from a set of micrographs with their corresponding coordinates'''
         micsFnameSet = {}
-        preprocMicsPath= self._getTmpPath(self.PRE_PROC_MICs_PATH)
-        for micFname in os.listdir(preprocMicsPath):
-          micFnameBase= pwutils.removeExt(micFname)
-          micFname= os.path.join(preprocMicsPath, micFname)
-          micsFnameSet[micFnameBase]= micFname
-        extractCoordsContent="#mics coords\n"
-        posDir= self._getExtraPath( self.CONSENSUS_COOR_PATH_TEMPLATE%mode )
-        if mode.startswith("ADDITIONAL_COORDS"):
-          os.mkdir(posDir)
-          if mode.endswith("TRUE"):
-            coordSet= self.trainTrueSetOfCoords.get()
-          elif mode.endswith("FALSE"):
-            coordSet= self.trainFalseSetOfCoords.get()
-          writeSetOfCoordinates(posDir, coordSet)
-
-        for posFname in os.listdir(posDir):
-          posNameBase=  pwutils.removeExt(posFname)
-          posFname= os.path.join(posDir, posFname)
-          if posNameBase in micsFnameSet:
-            extractCoordsContent+= "%s particles@%s\n"%(micsFnameSet[posNameBase], posFname)
-          else:
-            print("WARNING, no micFn for coords %s"%(posFname))
-
-        inputsFname= self._getTmpPath("extractParticles_inputs_%s.txt"%mode)
-        ouputDir= self._getConsensusParticlesDir(mode)
-        makePath(ouputDir)
-        nThrs= self.numberOfThreads.get()
-        with open(inputsFname, "w") as f:
-          f.write(extractCoordsContent)
-        downFactor= self._getDownFactor()
-        args= "-i %s -s %s -d %s -o %s -t %d"%(inputsFname, DEEP_PARTICLE_SIZE, downFactor, ouputDir, nThrs)
-
-        self.runJob('xmipp_extract_particles', args, numberOfMpi=1)
-
-    def joinSetOfParticlesStep( self, mode):
-        #Create images.xmd metadata joining from different .stk
-        fnImages = self._getExtraPath("particles_%s.xmd" % mode)
-        imgsXmd = md.MetaData()
-        posFiles = glob(self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE%mode, '*.pos'))
+        posDir = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % mode)
+        preprocMicsPath = self._getExtraPath(self.PRE_PROC_MICs_PATH)
+        toExtractMicFns = self.TO_EXTRACT_MICFNS[mode]
+        print('To extract in mode {}: {}'.format(mode, toExtractMicFns))
+        if len(toExtractMicFns) > 0:
+          for micFname in toExtractMicFns:
+            micFnameBase= pwutils.removeExt(micFname)
+            micFname= os.path.join(preprocMicsPath, micFname)
+            micsFnameSet[micFnameBase]= micFname
+          extractCoordsContent="#mics coords\n"
+
+          if mode.startswith("ADDITIONAL_COORDS"):
+            if not os.path.exists(posDir):
+              os.mkdir(posDir)
+            if mode.endswith("TRUE"):
+              coordSet= self.trainTrueSetOfCoords.get()
+            elif mode.endswith("FALSE"):
+              coordSet= self.trainFalseSetOfCoords.get()
+            writeSetOfCoordinates(posDir, coordSet)
+
+          for posFname in os.listdir(posDir):
+            posNameBase=  pwutils.removeExt(posFname)
+            posFname= os.path.join(posDir, posFname)
+            if posNameBase in micsFnameSet:
+              extractCoordsContent+= "%s particles@%s\n"%(micsFnameSet[posNameBase], posFname)
+
+          inputsFname= self._getTmpPath("extractParticles_inputs_%s.txt"%mode)
+          outputDir= self._getConsensusParticlesDir(mode)
+          if not os.path.exists(outputDir):
+            makePath(outputDir)
+          nThrs= self.numberOfThreads.get()
+          with open(inputsFname, "w") as f:
+            f.write(extractCoordsContent)
+          downFactor= self._getDownFactor()
+          args= "-i %s -s %s -d %s -o %s -t %d"%(inputsFname, DEEP_PARTICLE_SIZE, downFactor, outputDir, nThrs)
+
+          self.runJob('xmipp_extract_particles', args, numberOfMpi=1)
+
+    def joinSetOfParticlesStep(self, mode, micFns='', trainingPass='', clean=False):
+        '''Stores the particles extracted from a set of micrographs in a images.xmd metadata file'''
+        fnImages = self._getExtraPath("particles_{}{}.xmd".format(mode, trainingPass))
+        posDir = self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % mode)
+        if micFns == '':
+          micFns = self.TO_EXTRACT_MICFNS[mode]
+          self.TO_EXTRACT_MICFNS[mode] = []
+          print('Updating set of particles written in {}'.format(fnImages))
+        else:
+          print('Creating set of particles in {}'.format(fnImages))
 
-        for posFn in posFiles:
+        imgsXmd = md.MetaData()
+        for micFn in micFns:
+          posFn = os.path.join(posDir, pwutils.replaceBaseExt(micFn, "pos"))
           xmdFn = os.path.join(self._getConsensusParticlesDir(mode),
-                                     pwutils.replaceBaseExt(posFn, "xmd"))
+                               pwutils.replaceBaseExt(posFn, "xmd"))
           if os.path.exists(xmdFn):
             mdFn = md.MetaData(xmdFn)
             mdPos = md.MetaData('particles@%s' % posFn)
-            mdPos.merge(mdFn) 
+            mdPos.merge(mdFn)
             imgsXmd.unionAll(mdPos)
           else:
             self.warning("The coord file %s wasn't used for extraction! "
                          % os.path.basename(posFn))
-        imgsXmd.write(fnImages)
-
-    def __dataDict_toStrs(self, dataDict):
-        fnamesStr=[]
-        weightsStr=[]
-        for fname in dataDict:
-          fnamesStr.append(fname)
-          weightsStr.append(str(dataDict[fname]) )
-        return ":".join(fnamesStr), ":".join(weightsStr)
-
-    def _getEffectiveNumPartsTrain(self, dictTrueData):
-        nParts=0
-        for mdPath in dictTrueData:
-          mdObject = md.MetaData(mdPath)
-          nParts+= mdObject.size()
-        return nParts
-
-    def __retrievePreTrainedModel(self, netDataPath, effectiveSize=-1):
-        if effectiveSize==-1:
-          effectiveSize=int(5e4)
-        modelTypeDir= "keras_models/%sPhaseFlip_Invert/nnetData_%d/tfchkpoints_0" % (
-                            "no" if self.ignoreCTF.get() else "", effectiveSize)
-        modelTypeDir= self.getModel("deepConsensus", modelTypeDir)
-
-        for i in range(self.nModels.get()):
-          targetPath= os.path.join(netDataPath, "tfchkpoints_%d"%(i))
-          print(targetPath, modelTypeDir)
-          copyTree(modelTypeDir, targetPath)
-
-    def trainCNN(self):
-
-        netDataPath = self._getExtraPath("nnetData")
-        makePath(netDataPath)
+        if not os.path.exists(fnImages) or clean:
+          imgsXmd.write(fnImages)
+        else:
+          imgsXmd.append(fnImages)
+        self.EXTRACTING[mode] = False
+
+
+    def trainCNN(self, toTrainMicFns):
+        '''Trains the CNN with the particles from the ready to train micrographs'''
+        trainedParams = self.curTrainedParams
+        trPass = trainedParams['trainingPass']
+        if not trPass == '':
+          trPass += 1
+          trainedParams['trainingPass'] = trPass
+
+        #Writting the inputs in xmd
+        for mode in ['AND', 'NOISE']:
+          self.joinSetOfParticlesStep(mode, toTrainMicFns, trPass, clean=True)
+        #Creatting the training pass directory
+        netDataPath = self._getExtraPath("nnetData{}".format(trPass))
+        if not os.path.exists(netDataPath):
+          makePath(netDataPath)
         nEpochs = self.nEpochs.get()
 
-        posTrainDict = {self._getExtraPath("particles_AND.xmd"):  1}
-        negTrainDict = {self._getExtraPath("particles_NOISE.xmd"):  1}
-
-        if self.addTrainingData.get() == self.ADD_DATA_TRAIN_PRECOMP:
+        #Setting the input and input weights
+        posTrainDict = {self._getExtraPath("particles_AND{}.xmd".format(trPass)):  1}
+        negTrainDict = {self._getExtraPath("particles_NOISE{}.xmd".format(trPass)):  1}
+        if self.addTrainingData.get() == self.ADD_DATA_TRAIN_PRECOMP and trainedParams['firstTraining']:
           negTrainDict[self._getTmpPath("addNegTrainParticles.xmd")]= 1
 
         if self.usesGpu():
@@ -892,33 +936,29 @@ def trainCNN(self):
           numberOfThreads = self.numberOfThreads.get()
           gpuToUse = None
 
-        print(self.trainTrueSetOfParticles.get(), self.trainFalseSetOfParticles.get(), self.trainTrueSetOfCoords.get(),
-              self.trainFalseSetOfCoords.get() )
-
-        if self.trainingDataType.get() == self.ADD_DATA_TRAIN_CUSTOM_OPT_PARTS:
-          if self.trainTrueSetOfParticles.get():
-            posTrainFn = self._getExtraPath("trainTrueParticlesSet.xmd")
-            posTrainDict[posTrainFn] = self.trainPosWeight.get()
-          if self.trainFalseSetOfParticles.get():
-            negTrainFn = self._getExtraPath("trainFalseParticlesSet.xmd")
-            negTrainDict[negTrainFn] = self.trainNegWeight.get()
-
-        elif self.trainingDataType.get() == self.ADD_DATA_TRAIN_CUSTOM_OPT_COORS:
-          if self.trainTrueSetOfCoords.get():
-            posTrainFn = self._getExtraPath("particles_ADDITIONAL_COORDS_TRUE.xmd")
-            posTrainDict[posTrainFn] = self.trainPosWeight.get()
-          if self.trainFalseSetOfCoords.get():
-            negTrainFn = self._getExtraPath("particles_ADDITIONAL_COORDS_FALSE.xmd")
-            negTrainDict[negTrainFn] = self.trainNegWeight.get()
+
+        if self.addTrainingData.get() == self.ADD_DATA_TRAIN_CUST:
+          if self.trainingDataType.get() == self.ADD_DATA_TRAIN_CUSTOM_OPT_PARTS and trainedParams['firstTraining']:
+            if self.trainTrueSetOfParticles.get():
+              posTrainFn = self._getExtraPath("trainTrueParticlesSet.xmd")
+              posTrainDict[posTrainFn] = self.trainPosWeight.get()
+            if self.trainFalseSetOfParticles.get():
+              negTrainFn = self._getExtraPath("trainFalseParticlesSet.xmd")
+              negTrainDict[negTrainFn] = self.trainNegWeight.get()
+
+          elif self.trainingDataType.get() == self.ADD_DATA_TRAIN_CUSTOM_OPT_COORS:
+            if self.trainTrueSetOfCoords.get():
+              self.joinSetOfParticlesStep('ADDITIONAL_COORDS_TRUE', toTrainMicFns, trPass, clean=True)
+              posTrainFn = self._getExtraPath("particles_ADDITIONAL_COORDS_TRUE{}.xmd".format(trPass))
+              posTrainDict[posTrainFn] = self.trainPosWeight.get()
+            if self.trainFalseSetOfCoords.get():
+              self.joinSetOfParticlesStep('ADDITIONAL_COORDS_FALSE', toTrainMicFns, trPass, clean=True)
+              negTrainFn = self._getExtraPath("particles_ADDITIONAL_COORDS_FALSE{}.xmd".format(trPass))
+              negTrainDict[negTrainFn] = self.trainNegWeight.get()
 
         effectiveSize=-1
-        if self._doContinue():
-          prevRunPath = self.continueRun.get()._getExtraPath('nnetData')
-          copyTree(prevRunPath, netDataPath)
-          if self.skipTraining.get():
-              nEpochs = 0
-        elif self._usePretrainedModel():
-          nTrueParticles=  self._getEffectiveNumPartsTrain(posTrainDict)
+        nTrueParticles = self.toTrainDataSize.get() if self.toTrainDataSize.get() != -1 else 1e10
+        if self._usePretrainedModel():
           if nTrueParticles<1500:
             effectiveSize=1000
           elif 1500<=nTrueParticles<20000:
@@ -928,30 +968,46 @@ def trainCNN(self):
           self.__retrievePreTrainedModel(netDataPath, effectiveSize)
           if self.skipTraining.get():
               nEpochs = 0
+        elif not trPass == '' and trPass > 1:
+          #Starting from model in previous trainingPass
+          self.retrievePreviousPassModel(trPass)
 
-
+        trainedParams['posParticlesTrained'] += self._getEffectiveNumPartsTrain(posTrainDict)
         fnamesPos, weightsPos= self.__dataDict_toStrs(posTrainDict)
         fnamesNeg, weightsNeg= self.__dataDict_toStrs(negTrainDict)
-        args= " -n %s --mode train -p %s -f %s --trueW %s --falseW %s"%(netDataPath,
-                      fnamesPos, fnamesNeg, weightsPos, weightsNeg)
+        args= " -n %s --mode train -p %s -f %s --trueW %s --falseW %s --effective_data_size %s"%(netDataPath,
+                      fnamesPos, fnamesNeg, weightsPos, weightsNeg, nTrueParticles)
         args+= " -e %s -l %s -r %s -m %s "%(nEpochs, self.learningRate.get(), self.l2RegStrength.get(),
                                           self.nModels.get())
-
         if not self.auto_stopping.get():
           args+=" -s"
-
         if not gpuToUse is None:
           args+= " -g %s"%(gpuToUse)
         if not numberOfThreads is None:
           args+= " -t %s"%(numberOfThreads)
-        self.runJob('xmipp_deep_consensus', args, numberOfMpi=1,
-                    env=self.getCondaEnv())
+
+        trainedParams['trainedMicFns'] += self.TO_TRAIN_MICFNS
+        trainedParams['firstTraining'] = False
+        self.curTrainedParams = trainedParams
+        self.runJob('xmipp_deep_consensus', args, numberOfMpi=1, env=self.getCondaEnv())
         
     def predictCNN(self):
+        '''Predict the particles from the micrographs and calificates the consensus coordinates'''
+        trainedParams = self.loadTrainedParams()
+        trPass = trainedParams['trainingPass']
 
-        netDataPath = self._getExtraPath('nnetData')
+        if trPass != '':
+          predExten = trPass
+          mdORPath = self._getExtraPath("particles_OR.xmd")
+        else:
+          toPredictMicFns = self.readyToPredictMicFns()
+          predExten = '_partial'
+          self.joinSetOfParticlesStep(mode='OR', micFns=toPredictMicFns, trainingPass=predExten, clean=True)
+          mdORPath = self._getExtraPath("particles_OR{}.xmd".format(predExten))
+
+        netDataPath = self._getExtraPath("nnetData{}".format(trPass))
         if not os.path.isdir(netDataPath) and self._doContinue():
-            prevRunPath = self.continueRun.get()._getExtraPath('nnetData')
+            prevRunPath = self.continueRun.get()._getExtraPath("nnetData{}".format(trPass))
             copyTree(prevRunPath, netDataPath)
         elif self.skipTraining.get() and self._usePretrainedModel():
           self.__retrievePreTrainedModel(netDataPath)
@@ -963,15 +1019,20 @@ def predictCNN(self):
             numberOfThreads = self.numberOfThreads.get()
             gpuToUse = None
 
-        predictDict = {self._getExtraPath("particles_OR.xmd"): 1}
-        if self.doTesting.get() and self.testTrueSetOfParticles.get() and self.testFalseSetOfParticles.get():
+        mdObject = md.MetaData(mdORPath)
+        print('Predicting on {} true particles'.format(mdObject.size()))
+        predictDict = {mdORPath: 1}
+
+        if self.doTesting.get() and self.testTrueSetOfParticles.get() and self.testFalseSetOfParticles.get() and not\
+                self.loadTrainedParams()['doneExtraTesting']:
+            self.uploadTrainedParam('doneExtraTesting', True)
             posTestDict = {self._getExtraPath("testTrueParticlesSet.xmd"): 1}
             negTestDict = {self._getExtraPath("testFalseParticlesSet.xmd"): 1}
         else:
             posTestDict = None
             negTestDict = None
-        outParticlesPath = self._getPath("particles.xmd")
 
+        outParticlesPath = self._getPath("particles{}.xmd".format(predExten))
         fnamesPred, weightsPred= self.__dataDict_toStrs(predictDict)
 
         args= " -n %s --mode score -i %s -o %s "%(netDataPath, fnamesPred, outParticlesPath)
@@ -987,49 +1048,141 @@ def predictCNN(self):
           args+= " -t %s"%(numberOfThreads)
         self.runJob('xmipp_deep_consensus', args, numberOfMpi=1,
                     env=self.getCondaEnv())
-                
-    def createOutputStep(self):
-        # PARTICLES
-        cleanPattern(self._getPath("*.sqlite"))
-        partSet = self._createSetOfParticles("outputParts_tmp")
-        readSetOfParticles(self._getPath("particles.xmd"), partSet)
-        inputSampling = self.inputCoordinates[0].get().getMicrographs().getSamplingRate()
-        partSet.setSamplingRate(self._getDownFactor() * inputSampling)
-        boxSize = self._getBoxSize()
-
-        parSetCorrected= self._createSetOfParticles()
-        parSetCorrected.copyInfo(partSet)
-        # COORDINATES
-        if not "OR" in self.coordinatesDict:
-            self.loadCoords(self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % 'OR'),'OR')
-
-        coordSet = self._createSetOfCoordinates(
-            self.coordinatesDict['OR'].getMicrographs())
-        coordSet.copyInfo(self.coordinatesDict['OR'])
-        coordSet.setBoxSize(boxSize)
-
-        downFactor = self._getDownFactor()
-        for part in partSet:
-            coord = part.getCoordinate().clone()
-            coord.scale(downFactor)
-            deepZscoreLabel = '_xmipp_%s' % emlib.label2Str(md.MDL_ZSCORE_DEEPLEARNING1)
-            setattr(coord, deepZscoreLabel, getattr(part, deepZscoreLabel))
-            part = part.clone()
-            part.scaleCoordinate(downFactor)
-            if (self.threshold.get() < 0 or
-                    getattr(part, deepZscoreLabel) > self.threshold.get()):
-                coordSet.append(coord)
-            parSetCorrected.append(part)
-
-        coordSet.write()
-        parSetCorrected.write()
-        cleanPattern(self._getPath("particles.xmd"))
-        cleanPattern(self._getPath("*outputParts_tmp.sqlite"))
-        writeSetOfParticles(parSetCorrected, self._getPath("particles.xmd") )
-        self._defineOutputs(outputCoordinates=coordSet, outputParticles=parSetCorrected)
 
+        trainedParams = self.loadTrainedParams()
+        if trPass != '':
+          trainedParams['predictionPasses'].append(trPass)
+        else:
+          trainedParams['predictedMicFns'] += toPredictMicFns
+        self.saveTrainedParams(trainedParams)
+
+    def createOutputStep(self, closeStream=False):
+      trPass = self.loadTrainedParams()['trainingPass']
+      if not "OR" in self.coordinatesDict:
+        self.loadCoords(self._getExtraPath(self.CONSENSUS_COOR_PATH_TEMPLATE % 'OR'), 'OR')
+
+      if trPass == '':
+        self.createFinalOutput(closeStream)
+      else:
+        self.createPreliminarOutput(trPass)
+
+    def createFinalOutput(self, closeStream=False):
+      predExten = '_partial'
+      partSet = self._createSetOfParticles("outputParts_tmp{}".format(predExten))
+      readSetOfParticles(self._getPath("particles{}.xmd".format(predExten)), partSet)
+      inputSampling = self.inputCoordinates[0].get().getMicrographs().getSamplingRate()
+      partSet.setSamplingRate(self._getDownFactor() * inputSampling)
+
+      self.outputParticles, self.outputCoordinates = self.getParticlesOutput(partSet), self.getCoordinatesOutput()
+
+      downFactor = self._getDownFactor()
+      for part in partSet:
+        coord = part.getCoordinate().clone()
+        coord.scale(downFactor)
+        deepZscoreLabel = '_xmipp_%s' % emlib.label2Str(md.MDL_ZSCORE_DEEPLEARNING1)
+        setattr(coord, deepZscoreLabel, getattr(part, deepZscoreLabel))
+        newPart = Particle()
+        newPart.copy(part, copyId=False)
+        newPart.scaleCoordinate(downFactor)
+        if (self.threshold.get() < 0 or
+                getattr(newPart, deepZscoreLabel) > self.threshold.get()):
+          self.outputCoordinates.append(coord)
+          self.outputParticles.append(newPart)
+
+      cleanPattern(self._getPath("particles{}.xmd".format(predExten)))
+      cleanPattern(self._getPath("*outputParts_tmp{}.sqlite".format(predExten)))
+      writeSetOfParticles(self.outputParticles, self._getPath("particles.xmd"))
+      self.updateOutput(closeStream)
+
+    def createPreliminarOutput(self, trPass):
+      partSet = self._createSetOfParticles("outputParts_tmp{}".format(trPass))
+      readSetOfParticles(self._getPath("particles{}.xmd".format(trPass)), partSet)
+      inputSampling = self.inputCoordinates[0].get().getMicrographs().getSamplingRate()
+      partSet.setSamplingRate(self._getDownFactor() * inputSampling)
+      boxSize = self._getBoxSize()
+
+      self.preliminarOutputParticles = self.getPreParticlesOutput(partSet)
+      self.preliminarOutputCoordinates = self.getPreCoordinatesOutput()
+
+      downFactor = self._getDownFactor()
+      for part in partSet:
+        coord = part.getCoordinate().clone()
+        coord.scale(downFactor)
+        deepZscoreLabel = '_xmipp_%s' % emlib.label2Str(md.MDL_ZSCORE_DEEPLEARNING1)
+        setattr(coord, deepZscoreLabel, getattr(part, deepZscoreLabel))
+        part = part.clone()
+        part.scaleCoordinate(downFactor)
+        if (self.threshold.get() < 0 or
+                getattr(part, deepZscoreLabel) > self.threshold.get()):
+          self.preliminarOutputCoordinates.append(coord)
+          self.preliminarOutputParticles.append(part)
+
+      cleanPattern(self._getPath("particles{}.xmd".format(trPass)))
+      cleanPattern(self._getPath("*outputParts_tmp{}.sqlite".format(trPass)))
+      writeSetOfParticles(self.preliminarOutputParticles, self._getPath("particles{}.xmd".format(trPass)))
+      self.updatePreOutput(closeStream=True)
+
+    def getPreCoordinatesOutput(self):
+      print('Creating new preliminarOutputCoordinates set')
+      self.preliminarOutputCoordinates = self._createSetOfCoordinates(self.coordinatesDict['OR'].getMicrographs())
+      self.preliminarOutputCoordinates.copyInfo(self.coordinatesDict['OR'])
+      self.preliminarOutputCoordinates.setBoxSize(self._getBoxSize())
+      self.preliminarOutputCoordinates.setStreamState(SetOfParticles.STREAM_OPEN)
+      self._defineOutputs(preliminarOutputCoordinates=self.preliminarOutputCoordinates)
+      for inSetOfCoords in self.inputCoordinates:
+        self._defineSourceRelation(inSetOfCoords.get(), self.preliminarOutputCoordinates)
+      return self.preliminarOutputCoordinates
+
+    def getCoordinatesOutput(self):
+      if not hasattr(self, "outputCoordinates"):
+        print('Creating new outputCoordinates set')
+        self.outputCoordinates = self._createSetOfCoordinates(self.coordinatesDict['OR'].getMicrographs())
+        self.outputCoordinates.copyInfo(self.coordinatesDict['OR'])
+        self.outputCoordinates.setBoxSize(self._getBoxSize())
+        self.outputCoordinates.setStreamState(SetOfParticles.STREAM_OPEN)
+        self._defineOutputs(outputCoordinates=self.outputCoordinates)
         for inSetOfCoords in self.inputCoordinates:
-            self._defineSourceRelation(inSetOfCoords.get(), coordSet)
+          self._defineSourceRelation(inSetOfCoords.get(), self.outputCoordinates)
+      else:
+        # Micrographs of the set removed because there might be new ones in streaming
+        self.outputCoordinates.setMicrographs(self.coordinatesDict['OR'].getMicrographs())
+      return self.outputCoordinates
+
+    def getPreParticlesOutput(self, partSet):
+      print('Creating new preliminarOutputParticles set')
+      self.preliminarOutputParticles = self._createSetOfParticles()
+      self.preliminarOutputParticles.copyInfo(partSet)
+      self.preliminarOutputParticles.setStreamState(SetOfParticles.STREAM_OPEN)
+      self._defineOutputs(preliminarOutputParticles=self.preliminarOutputParticles)
+      return self.preliminarOutputParticles
+
+    def getParticlesOutput(self, partSet):
+      if not hasattr(self, "outputParticles"):
+        print('Creating new outputParticles set')
+        self.outputParticles = self._createSetOfParticles()
+        self.outputParticles.copyInfo(partSet)
+        self.outputParticles.setStreamState(SetOfParticles.STREAM_OPEN)
+        self._defineOutputs(outputParticles=self.outputParticles)
+      return self.outputParticles
+
+    def updatePreOutput(self, closeStream=False):
+      if closeStream:
+        self.preliminarOutputCoordinates.setStreamState(SetOfParticles.STREAM_CLOSED)
+        self.preliminarOutputParticles.setStreamState(SetOfParticles.STREAM_CLOSED)
+
+      self.preliminarOutputCoordinates.write()
+      self.preliminarOutputParticles.write()
+      self._store(self.preliminarOutputCoordinates, self.preliminarOutputParticles)
+
+    def updateOutput(self, closeStream=False):
+      if closeStream:
+        self.outputCoordinates.setStreamState(SetOfParticles.STREAM_CLOSED)
+        self.outputParticles.setStreamState(SetOfParticles.STREAM_CLOSED)
+
+      self.outputCoordinates.write()
+      self.outputParticles.write()
+      self._store(self.outputCoordinates, self.outputParticles)
+
 
     def _summary(self):
         message = []
@@ -1054,6 +1207,402 @@ def _updateParticle(self, item, row):
         else:
             item._appendItem = True
 
+    def retrieveTrainSets(self):
+      """ Retrieve, link and return a setOfParticles
+          corresponding to the NegativeTrain DeepConsensus trainning set
+          with certain extraction conditions (phaseFlip/invContrast)
+      """
+      prefixYES = ''
+      prefixNO = 'no'
+      # We always work with inverted contrast particles
+      modelType = "negativeTrain_%sPhaseFlip_Invert.mrcs" % (
+        prefixNO if self.ignoreCTF.get() else prefixYES)  # mics will be always internally inverted if not done before
+      modelPath = self.getModel("deepConsensus", modelType)
+      modelFn = self._getTmpPath(modelType)
+      pwutils.createLink(modelPath, modelFn)
+
+      tmpSqliteSuff = "AddTrain"
+      partSet = self._createSetOfParticles(tmpSqliteSuff)
+      img = SetOfParticles.ITEM_TYPE()
+
+      imgh = ImageHandler()
+      _, _, _, n = imgh.getDimensions(modelFn)
+      if n > 1:
+        for index in range(1, n + 1):
+          img.cleanObjId()
+          img.setMicId(9999)
+          img.setFileName(modelFn)
+          img.setIndex(index)
+          partSet.append(img)
+      partSet.setAlignment(ALIGN_NONE)
+
+      cleanPath(self._getPath("particles%s.sqlite" % tmpSqliteSuff))
+      return partSet
+
+    def getMicrographFnsWithCoordinates(self, shared=True):
+      '''Return a list with the filenames of those microgrpahs which already have coordinates associated in the input
+      sets. If shared, it must be in all the sets, if not shared, at least in one'''
+      sharedMics = self.getAllCoordsInputMicrographs(shared)
+      micPaths = []
+      for mic in sharedMics:
+        coordsInMic = []
+        for coordSet in self.inputCoordinates:
+          for coord in coordSet.get().iterCoordinates(mic):
+            coordsInMic.append(True)
+            break
+
+        if len(coordsInMic) == len(self.inputCoordinates):
+          micPaths.append(mic.getFileName())
+
+      micFns = self.prunePaths(micPaths)
+      return micFns
+
+    def getAllCoordsInputMicrographs(self, shared=False):
+      '''Returns a set with the input micrographs present associated with all the input coordinates sets.
+      If shared, the list contains only those micrographs present in all input coordinates sets, else the list contains
+      all microgrpah present in any set (Intersection vs Union)'''
+      micFns = set([])
+      micDict = {}
+      for inputCoord in self.inputCoordinates:
+        newMics = inputCoord.get().getMicrographs()
+        newMicFns = []
+        for mic in newMics:
+          micFn = self.prunePaths([mic.getFileName()])[0]
+          micDict[micFn] = mic.clone()
+          newMicFns.append(micFn)
+
+        if micFns == set([]) or not shared:
+          micFns = micFns | set(newMicFns)
+        else:
+          micFns = micFns & set(newMicFns)
+
+      mics = self._createSetOfMicrographs()
+      mics.copyInfo(self.inputCoordinates[0].get().getMicrographs())
+      for micFn in micFns:
+        mics.append(micDict[micFn])
+
+      return mics
+
+    def _getInputMicrographs(self):
+      '''Return a list with the micrographs corresponding the input coordinates'''
+      if not hasattr(self, "inputMicrographs") or not self.inputMicrographs:
+        if len(self.inputCoordinates) == 0:
+          print("WARNING. PROVIDE MICROGRAPHS FIRST")
+        else:
+          inputMicrographs = self.inputCoordinates[0].get().getMicrographs()
+          if inputMicrographs is None:
+            raise ValueError("there are problems with your coordiantes, they do not have associated micrographs ")
+          self.inputMicrographs = inputMicrographs
+      return self.inputMicrographs
+
+    def _getBoxSize(self):
+      '''Returns the box size of the input coordinates'''
+      if not hasattr(self, "boxSize") or not self.boxSize:
+        firstCoords = self.inputCoordinates[0].get()
+        self.boxSize = firstCoords.getBoxSize()
+        self.downFactor = self.boxSize / float(DEEP_PARTICLE_SIZE)
+      return self.boxSize
+
+    def _getDownFactor(self):
+      if not hasattr(self, "downFactor") or not self.downFactor:
+        firstCoords = self._getInputMicrographs()
+        self.boxSize = firstCoords.getBoxSize()
+        self.downFactor = self.boxSize / float(DEEP_PARTICLE_SIZE)
+        assert self.downFactor >= 1, \
+          "Error, the particle box size must be greater or equal than 128."
+
+      return self.downFactor
+
+    def _getConsensusParticlesDir(self, mode):
+      pathFun = self._getTmpPath if mode != "OR" else self._getExtraPath
+      return pathFun(self.CONSENSUS_PARTS_PATH_TEMPLATE% mode)
+
+    def loadMicSetFromFns(self, inputDir, micFns):
+      '''Returns a set of Micrographs from their filenames'''
+      micSet = self._createSetOfMicrographs()
+      for micFn in micFns:
+        micPath = os.path.join(inputDir, micFn)
+        micSet.append(Micrograph(micPath))
+      micSet.copyInfo(self._getInputMicrographs())
+      return micSet
+
+    def getPreProcParamsFromForm(self):
+        mics_= self._getInputMicrographs()
+        mic = mics_.getFirstItem()
+        fnMic = mic.getFileName()
+        pathToMics= os.path.split(fnMic)[0]
+        pathToCtfs= "None"
+
+        if not self.ignoreCTF.get():
+          pathToCtfs=  os.path.split(self.ctfRelations.get().getFileName())[0]
+
+        paramsInfo={"mics_ignoreCTF": self.ignoreCTF.get(),
+                    "mics_skipInvert": self.skipInvert.get(),
+                    "mics_pathToMics": pathToMics,
+                    "mics_pathToCtfs": pathToCtfs}
+        coordsNames=[]
+        for inputCoords in self.inputCoordinates:
+          coordsNames.append( inputCoords.get().getFileName() )
+        coordsNames= tuple(sorted(coordsNames))
+        paramsInfo["coords_pathCoords"]= coordsNames
+
+        return paramsInfo
+
+    def checkIfPrevRunIsCompatible(self, inputType=""):
+        '''
+        inputType can be mics_ or coords_ or ""
+        '''
+        def _makeTupleIfList(candidateList):
+          if isinstance(candidateList, list):
+            return tuple(candidateList)
+          elif isinstance(candidateList, six.string_types):
+            return tuple([candidateList])
+          else:
+            return candidateList
+        try:
+          preprocParams= self.getPreProcParamsFromForm()
+        except Exception:
+          return False
+        preprocParams= { k:_makeTupleIfList(preprocParams[k]) for k in preprocParams if k.startswith(inputType) }
+        if self._doContinue():
+            preprocessParamsFname = self.continueRun.get()._getExtraPath("preprocess_params.json")
+            with open(preprocessParamsFname) as f:
+              preprocParams_loaded = json.load(f)
+
+            preprocParams_loaded= {k: _makeTupleIfList(preprocParams_loaded[k])
+                                   for k in preprocParams_loaded
+                                   if k.startswith(inputType)}
+
+            for key in preprocParams_loaded:
+              if "path" in key:
+                for val in preprocParams_loaded[key]:
+                  if not val=="None" and not os.path.exists(val):
+                    return False
+
+            shared_items = {k: preprocParams_loaded[k]
+                            for k in preprocParams_loaded
+                            if k in preprocParams and preprocParams_loaded[k] == preprocParams[k]}
+
+            return (len(shared_items) == len(preprocParams) and
+                    len(shared_items) == len(preprocParams_loaded) and
+                    len(shared_items) > 0)
+
+        return False
+
+    def __dataDict_toStrs(self, dataDict):
+        fnamesStr=[]
+        weightsStr=[]
+        for fname in dataDict:
+          fnamesStr.append(fname)
+          weightsStr.append(str(dataDict[fname]) )
+        return ":".join(fnamesStr), ":".join(weightsStr)
+
+    def _getEffectiveNumPartsTrain(self, dictTrueData):
+        '''Returns the number of particles being used for training'''
+        nParts=0
+        for mdPath in dictTrueData:
+          mdObject = md.MetaData(mdPath)
+          nParts+= mdObject.size()
+        return nParts
+
+    #STREAMING and state checks
+    def predictionsOn(self):
+      '''Return a boolean for whether to perform a prediction. True if there must be a preliminar prediction or
+      if the training process has finished (trainingPass=='') '''
+      trainedParams = self.loadTrainedParams()
+      return (self.readyPreliminarPrediction() or trainedParams['trainingPass'] == '')
+
+    def readyPreliminarPrediction(self):
+      '''Return a boolean for whether to perform a preliminar predition. True if the user set it and the current
+      trained network has not been used yet'''
+      if self.networkReadyToPredict:
+        trainedParams = self.loadTrainedParams()
+        if not trainedParams['trainingPass'] in trainedParams[
+          'predictionPasses'] and self.doPreliminarPredictions.get():
+          return True
+      return False
+
+    def networkReadyToPredict(self):
+      '''Returns true if the CNN is trained or the user specified it does not need to be trained'''
+      trainedParams = self.loadTrainedParams()
+      return self.skipTraining.get() or len(trainedParams['trainedMicFns']) > 0
+
+    def cnnFree(self):
+      return not self.PREDICTING and not self.TRAINING
+
+    def allFree(self):
+      '''Kind of "traficlight" that specifies if there is not extraction, training or prediction going on, which would
+      alterate the states of the protocol'''
+      gExtracting = False
+      for mode in ['OR', 'NOISE', 'AND']:
+        if self.EXTRACTING[mode]:
+          gExtracting = True
+      return not self.PREDICTING and not self.TRAINING and not gExtracting and not self.PREPROCESSING
+
+    def checkIfParentsFinished(self):
+      '''Check the streamState of the coordinates input to check if the parent protocols are finsihed'''
+      for coords in self.inputCoordinates:
+        coords = coords.get()
+        coords.loadAllProperties()
+        if coords.isStreamOpen():
+          return False
+      return True
+
+    def checkIfNewMics(self, mode=''):
+      '''Check if the are new micrographs ready for extracting particles'''
+      if mode == '':
+        for mode in ['OR', 'NOISE', 'AND']:
+          if len(self.readyToExtractMicFns(mode)) > 0:
+            return True
+      else:
+        if len(self.readyToExtractMicFns(mode)) > 0:
+          return True
+      return False
+
+    #Get data attributes
+    def getMicsIds(self, filterOutNoCoords=False):
+        '''Returns the input micrographs Ids'''
+        if not filterOutNoCoords:
+          return self._getInputMicrographs().getIdSet()
+        micIds= set([])
+        micFnames= set([])
+        for coordinatesP in self.inputCoordinates:
+            for coord in coordinatesP.get():
+              micIds.add( coord.getMicId())
+              micFnames.add( coord.getMicName() )
+        return sorted( micIds )
+
+    def getInputMicsFns(self, shared):
+      '''Returns the input micrographs filenames'''
+      micFnames = []
+      mics = self.getAllCoordsInputMicrographs(shared)
+      micPaths = mics.getFiles()
+      micFnames = self.prunePaths(micPaths)
+      return sorted(micFnames)
+
+    def prunePaths(self, paths):
+      fns = []
+      for path in paths:
+        fns.append(path.split('/')[-1])
+      return fns
+
+    def getPreprocessedMicFns(self):
+      '''Return the list of preprocessed micrograph filenames'''
+      prepDir = self._getExtraPath(self.PRE_PROC_MICs_PATH)
+      if not os.path.exists(prepDir):
+        return []
+      return os.listdir(prepDir)
+
+    def getExtractedMicFns(self, mode):
+      '''Return the list of extracted micrograph filenames
+      (micrographs where particles of type "mode" have been extracted)'''
+      outputDir = self._getConsensusParticlesDir(mode)
+      if not os.path.exists(outputDir):
+        return []
+      fns, micFns = os.listdir(outputDir), []
+      for fn in fns:
+        micFns.append(pwutils.path.replaceBaseExt(fn, 'mrc'))
+      return list(set(micFns))
+
+    def getTrainedMicFns(self):
+      '''Return the list of microgrpahs whose particles have been used for training'''
+      trainedParams = self.loadTrainedParams()
+      return trainedParams['trainedMicFns']
+
+    def getPredictedMicFns(self):
+      '''Return the list of microgrpahs whose particles have been used for prediction'''
+      trainedParams = self.loadTrainedParams()
+      return trainedParams['predictedMicFns']
+
+    #Get ready sets
+    def readyToPreprocessMics(self, shared):
+      '''Return the list of micrograph filenames which are ready to be preprocessed and have not been preprocessed yet'''
+      micFns = self.getInputMicsFns(shared)
+      return list(set(micFns) - set(self.getPreprocessedMicFns()))
+
+    def readyToExtractMicFns(self, mode):
+      '''Return the list of micrograph filenames which are ready to be extracted (preprocessed and common for all inputs)
+      and have not or are not being extracted yet'''
+      return list((set(self.getPreprocessedMicFns()) & set(self.getMicrographFnsWithCoordinates(shared=True))) -
+                  (set(self.getExtractedMicFns(mode)) | set(self.TO_EXTRACT_MICFNS[mode])))
+
+    def readyToTrainMicFns(self):
+      '''Return the list of micrograph filenames which are ready to be used for training and
+      have not or are not being trained yet'''
+      extractedMicFns = set(self.getExtractedMicFns('OR')) & set(self.getExtractedMicFns('NOISE')) & \
+                        set(self.getExtractedMicFns('AND'))
+      readyToTrain = list(extractedMicFns - set(self.getTrainedMicFns()))
+      return readyToTrain[:min(len(readyToTrain), self.TRAIN_BATCH_MAX)]
+
+    def readyToPredictMicFns(self):
+      '''Return the list of micrograph filenames which are ready to be used for prediction and
+      have not or are not being predicted yet'''
+      extractedMicFns = set(self.getExtractedMicFns('OR'))
+      readyToPredict = list(extractedMicFns - set(self.getPredictedMicFns()))
+      return readyToPredict
+
+    #Training params utils
+    def loadTrainedParams(self):
+      '''Load the dictionary stored in pickle format which stores the trained parameters.
+      Creates a initial one if it does not exist yet'''
+      paramsFile = self._getExtraPath(self.TRAINED_PARAMS_PATH)
+      if os.path.exists(paramsFile):
+        with open(paramsFile, 'rb') as handle:
+          params = pickle.load(handle)
+      else:
+        params = {'trainedMicFns': [],
+                  'predictedMicFns': [],
+                  'posParticlesTrained': 0,
+                  'trainingPass': 0,
+                  'predictionPasses': [],
+                  'doneExtraTesting': False,
+                  'firstTraining': True
+                  }
+      return params
+
+    def uploadTrainedParam(self, keyParam, newValue):
+      '''Upload the value of a parameter from the trained parameters'''
+      params = self.loadTrainedParams()
+      params[keyParam] = newValue
+      self.saveTrainedParams(params)
+
+    def saveTrainedParams(self, params):
+      '''Save the trained parameters dictionary'''
+      paramsFile = self._getExtraPath(self.TRAINED_PARAMS_PATH)
+      with open(paramsFile, 'wb') as handle:
+        pickle.dump(params, handle)
+
+    #CNN models utils
+    def retrievePreviousPassModel(self, trPass, lastTrPass=''):
+      '''Retrieves a previous CNN model and copies its folders to used the network in a new location'''
+      curNetDataPath = self._getExtraPath("nnetData{}".format(trPass))
+      if trPass == '':
+        prevNetDataPath = self._getExtraPath("nnetData{}".format(lastTrPass))
+      else:
+        prevNetDataPath = self._getExtraPath("nnetData{}".format(trPass - 1))
+      if prevNetDataPath != curNetDataPath:
+        copyTree(prevNetDataPath, curNetDataPath)
+
+    def retrievePreviousRunModel(self, prevProt, trPass=''):
+      '''Retrieves a CNN model from other protocol and copies its folders to used the network in a new location'''
+      curNetDataPath = self._getExtraPath("nnetData{}".format(trPass))
+      prevNetDataPath = prevProt._getExtraPath("nnetData")
+      if prevNetDataPath != curNetDataPath:
+        copyTree(prevNetDataPath, curNetDataPath)
+
+    def __retrievePreTrainedModel(self, netDataPath, effectiveSize=-1):
+        '''Retrieves a previously trained CNN model'''
+        if effectiveSize==-1:
+          effectiveSize=int(5e4)
+        modelTypeDir= "keras_models/%sPhaseFlip_Invert/nnetData_%d/tfchkpoints_0" % (
+                            "no" if self.ignoreCTF.get() else "", effectiveSize)
+        modelTypeDir= self.getModel("deepConsensus", modelTypeDir)
+
+        for i in range(self.nModels.get()):
+          targetPath= os.path.join(netDataPath, "tfchkpoints_%d"%(i))
+          print(targetPath, modelTypeDir)
+          copyTree(modelTypeDir, targetPath)
+
 
 class XmippProtDeepConsSubSet(ProtUserSubSet):
     """ Create subsets from the GUI for the Deep Consensus protocol.
diff --git a/xmipp3/protocols/protocol_screen_deeplearning.py b/xmipp3/protocols/protocol_screen_deeplearning.py
index 894494bbb..4ba311dff 100644
--- a/xmipp3/protocols/protocol_screen_deeplearning.py
+++ b/xmipp3/protocols/protocol_screen_deeplearning.py
@@ -223,7 +223,7 @@ def __getSetOfParticlesFromFname(fname):
           elif fname== self._getExtraPath("testFalseParticlesSet.xmd"):
             return self.testNegSetOfParticles.get()
           else:
-            matchOjb= re.match( self._getExtraPath("negativeSet_(\d+).xmd"), fname)
+            matchOjb= re.match( self._getExtraPath(r"negativeSet_(\d+).xmd"), fname)
             if matchOjb:
               num= matchOjb.group(1)
               return self.__dict__["negativeSet_%s"%num].get()
diff --git a/xmipp3/protocols/protocol_shift_particles.py b/xmipp3/protocols/protocol_shift_particles.py
new file mode 100644
index 000000000..249e8f4e8
--- /dev/null
+++ b/xmipp3/protocols/protocol_shift_particles.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# *
+# * Authors:     Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
+# *
+# *  BCU, Centro Nacional de Biotecnologia, CSIC
+# *
+# *
+# * 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'
+# *
+# **************************************************************************
+
+from pyworkflow.protocol.params import PointerParam, BooleanParam, IntParam, EnumParam
+from pyworkflow.protocol.constants import LEVEL_ADVANCED
+import pyworkflow.object as pwobj
+from pwem import ALIGN_3D, ALIGN_2D
+import pwem.emlib.metadata as md
+from pwem.protocols import EMProtocol
+from xmipp3.convert import xmippToLocation, writeSetOfParticles
+
+
+class XmippProtShiftParticles(EMProtocol):
+    """ This protocol shifts particles to center them into a point selected in a volume. To do so, it generates new
+    shifted images and modify the transformation matrix according to the shift performed."""
+
+    _label = 'shift particles'
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    def _defineParams(self, form):
+        form.addSection(label='Input')
+        form.addParam('inputParticles', PointerParam, pointerClass='SetOfParticles', label="Particles",
+                      help='Select the SetOfParticles with transformation matrix to be shifted.')
+        form.addParam('inputVol', PointerParam, pointerClass='Volume', label="Volume", allowsNull=True,
+                      help='Volume to select the point (by clicking in the wizard for selecting the new center) that '
+                           'will be the new center of the particles.')
+        form.addParam('x', IntParam, label="x", help='Use the wizard to select by clicking in the volume the new '
+                                                     'center for the shifted particles')
+        form.addParam('y', IntParam, label="y")
+        form.addParam('z', IntParam, label="z")
+        form.addParam('boxSizeBool', BooleanParam, label='Use original box size for the shifted particles?',
+                      default='True', help='Use input particles box size for the shifted particles.')
+        form.addParam('boxSize', IntParam, label='Final box size', condition='not boxSizeBool',
+                      help='Box size for the shifted particles.')
+        form.addParam('inv', BooleanParam, label='Inverse', expertLevel=LEVEL_ADVANCED, default='True',
+                      help='Use inverse transformation matrix')
+        form.addParam('interp', EnumParam, default=0, choices=['Linear', 'Spline'], expertLevel=LEVEL_ADVANCED,
+                      display=EnumParam.DISPLAY_HLIST , label='Interpolation',
+                      help='Linear: Use bilinear/trilinear interpolation\nSpline: Use spline interpolation')
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAllSteps(self):
+        self._insertFunctionStep('convertStep')
+        self._insertFunctionStep('shiftStep')
+        self._insertFunctionStep('createOutputStep')
+
+    # --------------------------- STEPS functions --------------------------------------------
+    def convertStep(self):
+        """convert input particles into .xmd file """
+        writeSetOfParticles(self.inputParticles.get(), self._getExtraPath("input_particles.xmd"),
+                            alignType=ALIGN_3D)
+
+    def shiftStep(self):
+        """call xmipp program to shift the particles"""
+        program = "xmipp_transform_geometry"
+        centermd = self._getExtraPath("center_particles.xmd")
+        if not self.interp.get():
+            interp = 'linear'
+        else:
+            interp = 'spline'
+        args = '-i "%s" -o "%s" --shift_to %f %f %f --apply_transform --dont_wrap --interp %s' % \
+               (self._getExtraPath("input_particles.xmd"), centermd, self.x.get(), self.y.get(), self.z.get(), interp)
+        if self.inv.get():
+            args += ' --inverse'
+        self.runJob(program, args)
+
+        if not self.boxSizeBool.get():
+            box = self.boxSize.get()
+            self.runJob('xmipp_transform_window', '-i "%s" -o "%s" --size %d %d %d --save_metadata_stack' %
+                        (centermd, self._getExtraPath("crop_particles.stk"), box, box, 1))
+
+    def createOutputStep(self):
+        """create output with the new particles"""
+        self.ix = 0
+        inputParticles = self.inputParticles.get()
+        outputSet = self._createSetOfParticles()
+        outputSet.copyInfo(inputParticles)
+        if self.boxSizeBool.get():
+            outputmd = self._getExtraPath("center_particles.xmd")
+        else:
+            outputmd = self._getExtraPath("crop_particles.xmd")
+        outputSet.copyItems(inputParticles, updateItemCallback=self._updateItem,
+                            itemDataIterator=md.iterRows(outputmd))
+        self._defineOutputs(outputParticles=outputSet)
+        self._defineOutputs(shiftX=pwobj.Float(self.x.get()),
+                            shiftY=pwobj.Float(self.y.get()),
+                            shiftZ=pwobj.Float(self.z.get()))
+        self._defineSourceRelation(inputParticles, outputSet)
+
+    # --------------------------- INFO functions --------------------------------------------
+    def _summary(self):
+        summary = []
+        if not hasattr(self, 'outputParticles'):
+            summary.append("Output particles not ready yet.")
+        else:
+            if not self.interp.get():
+                interp = 'linear'
+            else:
+                interp = 'spline'
+            summary.append("%d particles shifted\ninterpolation: %s" % (self.inputParticles.get().getSize(), interp))
+            if self.inv.get():
+                summary.append("inverse matrix applied")
+        return summary
+
+    def _methods(self):
+        return ["%d particles shifted to x = %d, y = %d, z = %d." % (self.inputParticles.get().getSize(),
+                                                                     self.x.get(), self.y.get(), self.z.get())]
+
+    def _validate(self):
+        for part in self.inputParticles.get().iterItems():
+            if not part.hasTransform():
+                validatemsg = ['Please provide particles which have transformation matrix.']
+                return validatemsg
+
+    # --------------------------- UTLIS functions --------------------------------------------
+    def _updateItem(self, item, row):
+        newFn = row.getValue(md.MDL_IMAGE)
+        newLoc = xmippToLocation(newFn)
+        item.setLocation(newLoc)
diff --git a/xmipp3/protocols/protocol_simulate_ctf.py b/xmipp3/protocols/protocol_simulate_ctf.py
new file mode 100644
index 000000000..3c0f1adf9
--- /dev/null
+++ b/xmipp3/protocols/protocol_simulate_ctf.py
@@ -0,0 +1,159 @@
+# **************************************************************************
+# *
+# * Authors:     C.O.S. Sorzano (coss@cnb.csic.es)
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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 random
+
+import pyworkflow.protocol.params as params
+from pyworkflow import VERSION_3_0
+from pwem.protocols import Prot2D
+from pwem.objects import CTFModel
+
+from xmippLib import createEmptyFile
+from xmipp3.convert import locationToXmipp
+
+from pwem import emlib
+from xmipp3.convert import (setXmippAttributes, writeSetOfParticles)
+
+class XmippProtSimulateCTF(Prot2D):
+    """    
+    Simulate the effect of the CTF (no amplitude decay).
+    A random defocus is chosen between the lower and upper defocus for each projection.
+    """
+
+    _label = 'simulate ctf'
+    _lastUpdateVersion = VERSION_3_0
+    
+    def __init__(self, *args, **kwargs):
+        Prot2D.__init__(self, *args, **kwargs)
+        
+    #--------------------------- DEFINE param functions ------------------------
+    def _defineParams(self, form):
+        form.addSection(label='Input')
+        
+        form.addParam('inputParticles', params.PointerParam,
+                      allowsNull=False,
+                      pointerClass='SetOfParticles',
+                      label="Input particles")
+
+        form.addParam('voltage', params.FloatParam, default=300,
+                      label="Voltage (kV)")
+        form.addParam('cs', params.FloatParam, default=2.7,
+                      label="Spherical aberration Cs (mm)")
+        form.addParam('Q0', params.FloatParam, default=0.07,
+                      label="Fraction inelastic scattering",
+                      help="Between 0 and 1")
+        form.addParam('Defocus0', params.FloatParam, default=5000,
+                      label="Lower defocus (A)")
+        form.addParam('DefocusF', params.FloatParam, default=25000,
+                      label="Upper defocus (A)")
+
+    
+    #--------------------------- INSERT steps functions ------------------------
+
+    def _insertAllSteps(self):        
+        self._insertFunctionStep('convertInputStep',
+                                 self.inputParticles.get().getObjId())
+
+        self._insertFunctionStep('simulateStep',
+                                 self.inputParticles.get().getObjId())
+
+        self._insertFunctionStep('createOutputStep')
+
+    #--------------------------- STEPS functions -------------------------------
+
+    def convertInputStep(self, particlesId):
+        x, y, _ = self.inputParticles.get().getDimensions()
+        n = self.inputParticles.get().getSize()
+        createEmptyFile(self._getPath("images.stk"),x,y,1,n)
+
+    def simulateStep(self, particlesId):
+        n=1
+        fnStk = self._getPath("images.stk")
+        Ts = self.inputParticles.get().getSamplingRate()
+
+        imgSetOut = self._createSetOfParticles()
+        imgSetOut.copyInfo(self.inputParticles.get())
+        imgSetOut.setHasCTF(True)
+        acquisition = imgSetOut.getAcquisition()
+        acquisition.setVoltage(self.voltage.get())
+        acquisition.setAmplitudeContrast(self.Q0.get())
+        acquisition.setSphericalAberration(self.cs.get())
+        acquisition.setMagnification(1)
+
+        for particle in self.inputParticles.get():
+
+            location = particle.getLocation()
+            fnIn = str(location[0])+"@"+location[1]
+            fnOut = str(n)+"@"+fnStk
+            defocus = random.uniform(self.Defocus0.get(),self.DefocusF.get())
+            args = "-i %s -o %s"%(fnIn, fnOut)
+            args += " --fourier ctfdef %f %f %f %f --sampling %f -v 0"%(self.voltage, self.cs, self.Q0, defocus, Ts)
+            self.runJob("xmipp_transform_filter", args)
+
+            newCTF = CTFModel()
+            newCTF.setDefocusU(defocus)
+            newCTF.setDefocusV(defocus)
+            newCTF.setDefocusAngle(0.0)
+
+            newParticle = particle.clone()
+            acquisition = newParticle.getAcquisition()
+            acquisition.setVoltage(self.voltage.get())
+            acquisition.setAmplitudeContrast(self.Q0.get())
+            acquisition.setSphericalAberration(self.cs.get())
+            acquisition.setMagnification(1)
+
+            newParticle.setCTF(newCTF)
+
+            imgSetOut.append(newParticle)
+
+            n+=1
+
+        self._defineOutputs(outputParticles=imgSetOut)
+        self._defineSourceRelation(self.inputParticles.get(), imgSetOut)
+
+    def createOutputStep(self):
+        # imgSet1 = self.inputParticles1.get()
+        # imgSetOut = self._createSetOfParticles()
+        # imgSetOut.copyInfo(imgSet1)
+        # imgSetOut.setAlignmentProj()
+        # self.iterMd = md.iterRows(self._getExtraPath("angles1_common.xmd"), md.MDL_ITEM_ID)
+        # self.lastRow = next(self.iterMd)
+        # imgSetOut.copyItems(imgSet1,
+        #                     updateItemCallback=self._updateItem)
+        # self._defineOutputs(outputParticles=imgSetOut)
+        # self._defineSourceRelation(self.inputParticles1, imgSetOut)
+        # self._defineSourceRelation(self.inputParticles2, imgSetOut)
+        pass
+
+    # --------------------------- INFO functions -------------------------------
+    def _summary(self):
+        summary = []
+        summary.append("Voltage=%f kV"%self.voltage)
+        summary.append("Cs=%f mm"%self.cs)
+        summary.append("Q0=%f"%self.Q0)
+        summary.append("Defocus range=[%f,%f] A"%(self.Defocus0,self.DefocusF))
+        return summary
+
diff --git a/xmipp3/protocols/protocol_structure_map_sph.py b/xmipp3/protocols/protocol_structure_map_sph.py
index 205059441..c58d9de2c 100644
--- a/xmipp3/protocols/protocol_structure_map_sph.py
+++ b/xmipp3/protocols/protocol_structure_map_sph.py
@@ -1,3 +1,12 @@
+########
+
+# This protocol has been removed from Scipion as there is a newer version being developed that
+# will solve some of the problems of this current implementation. The code will be available
+# again in a newer release.
+
+########
+
+
 
 # **************************************************************************
 # *
@@ -24,165 +33,165 @@
 # *  e-mail address 'scipion@cnb.csic.es'
 # *
 # **************************************************************************
-from pwem.protocols import ProtAnalysis3D
-import pyworkflow.protocol.params as params
-from pwem.emlib.image import ImageHandler
-from pwem.objects import SetOfVolumes, Volume
-from pyworkflow import VERSION_2_0
-import numpy as np
-
-
-def mds(d, dimensions=2):
-    """
-    Multidimensional Scaling - Given a matrix of interpoint distances,
-    find a set of low dimensional points that have similar interpoint
-    distances.
-    """
-
-    (n, n) = d.shape
-    E = (-0.5 * d ** 2)
-
-    # Use mat to get column and row means to act as column and row means.
-    Er = np.mat(np.mean(E, 1))
-    Es = np.mat(np.mean(E, 0))
-
-    # From Principles of Multivariate Analysis: A User's Perspective (page 107).
-    F = np.array(E - np.transpose(Er) - Es + np.mean(E))
-
-    [U, S, V] = np.linalg.svd(F)
-
-    Y = U * np.sqrt(S)
-
-    return (Y[:, 0:dimensions], S)
-
-
-class XmippProtStructureMapSPH(ProtAnalysis3D):
-    """ Protocol for structure mapping based on spherical harmonics. """
-    _label = 'sph struct map'
-    _lastUpdateVersion = VERSION_2_0
-
-    # --------------------------- DEFINE param functions --------------------------------------------
-    def _defineParams(self, form):
-        form.addSection(label='Input')
-        form.addParam('inputVolumes', params.MultiPointerParam,
-                      pointerClass='SetOfVolumes,Volume',
-                      label="Input volume(s)", important=True,
-                      help='Select one or more volumes (Volume or SetOfVolumes)\n'
-                           'for structure mapping.')
-        form.addParam('targetResolution', params.FloatParam, label="Target resolution",
-                      default=8.0,
-                      help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
-                           "2/3 of the Fourier spectrum.")
-        form.addParam('depth', params.IntParam, default=3,
-                      label='Harmonical depth',
-                      expertLevel=params.LEVEL_ADVANCED,
-                      help='Harmonical depth of the deformation=1,2,3,...')
-
-    # --------------------------- INSERT steps functions --------------------------------------------
-    def _insertAllSteps(self):
-
-        volList, dimList, srList = self._iterInputVolumes()
-        self.distanceMatrix = np.zeros((len(volList), len(volList)))
-
-        nVoli = 1
-        for voli in volList:
-            self._insertFunctionStep('convertStep', volList[nVoli - 1],
-                                     dimList[nVoli - 1], srList[nVoli - 1],
-                                     min(dimList), max(srList))
-            nVolj = 1
-            for volj in volList:
-                if nVolj != nVoli:
-                    self._insertFunctionStep('deformStep', volList[nVoli - 1],
-                                             volList[nVolj - 1], nVoli - 1,
-                                             nVolj - 1)
-                else:
-                    self.distanceMatrix[nVoli - 1][nVolj - 1] = 0.0
-                nVolj += 1
-            nVoli += 1
-
-        self._insertFunctionStep('gatherResultsStep', volList)
-
-    # --------------------------- STEPS functions ---------------------------------------------------
-    def convertStep(self, volFn, volDim, volSr, minDim, maxSr):
-        Xdim = volDim
-        Ts = volSr
-        newTs = self.targetResolution.get() * 1.0 / 3.0
-        newTs = max(maxSr, newTs)
-        newXdim = int(Xdim * Ts / newTs)
-
-        ih = ImageHandler()
-        if Xdim != newXdim:
-            self.runJob("xmipp_image_resize",
-                        "-i %s --dim %d" % (volFn, newXdim))
-
-        if newXdim>minDim:
-            self.runJob("xmipp_transform_window", " -i %s --crop %d" %
-                        (volFn, (newXdim - minDim)))
-
-    def deformStep(self, inputVolFn, refVolFn, i, j):
-        fnOut = self._getExtraPath('vol%dDeformedTo%d.vol' % (i, j))
-
-        params = ' --i1 %s --i2 %s --apply %s --least_squares --local ' % \
-                 (refVolFn, inputVolFn, fnOut)
-        self.runJob("xmipp_volume_align", params)
-
-        params = ' -i %s -r %s -o %s --depth %d ' %\
-                 (fnOut, refVolFn, fnOut, self.depth.get())
-
-        self.runJob("xmipp_volume_deform_sph", params)
-        distanceValue = np.loadtxt('./deformation.txt')
-        self.distanceMatrix[i][j] = distanceValue
-
-
-    def gatherResultsStep(self, volList):
-        fnRoot = self._getExtraPath("DistanceMatrix.txt")
-        nVoli = 1
-        for i in volList:
-            nVolj = 1
-            for j in volList:
-                fh = open(fnRoot, "a")
-                fh.write("%f\t" % self.distanceMatrix[(nVoli - 1)][(nVolj - 1)])
-                fh.close()
-                nVolj += 1
-            fh = open(fnRoot, "a")
-            fh.write("\n")
-            fh.close()
-            nVoli += 1
-
-        distance = np.asarray(self.distanceMatrix)
-        for i in range(1, 4):
-            embed, _ = mds(distance, i)
-            embedExtended = np.pad(embed, ((0, 0), (0, i - embed.shape[1])),
-                                   "constant", constant_values=0)
-            np.savetxt(self._defineResultsName(i), embedExtended)
-
-    # --------------------------- UTILS functions --------------------------------------------
-
-
-    def _iterInputVolumes(self):
-        """ Iterate over all the input volumes. """
-        volList = []
-        dimList = []
-        srList = []
-        for pointer in self.inputVolumes:
-            item = pointer.get()
-            if item is None:
-                break
-            itemId = item.getObjId()
-            if isinstance(item, Volume):
-                volList.append(item.getFileName())
-                dimList.append(item.getDim()[0])
-                srList.append(item.getSamplingRate())
-            elif isinstance(item, SetOfVolumes):
-                for vol in item:
-                    volList.append(vol.getFileName())
-                    dimList.append(vol.getDim()[0])
-                    srList.append(vol.getSamplingRate())
-        return volList, dimList, srList
-
-
-    def _defineResultsName(self, i):
-        return self._getExtraPath('CoordinateMatrix%d.txt' % i)
+# from pwem.protocols import ProtAnalysis3D
+# import pyworkflow.protocol.params as params
+# from pwem.emlib.image import ImageHandler
+# from pwem.objects import SetOfVolumes, Volume
+# from pyworkflow import VERSION_2_0
+# import numpy as np
+#
+#
+# def mds(d, dimensions=2):
+#     """
+#     Multidimensional Scaling - Given a matrix of interpoint distances,
+#     find a set of low dimensional points that have similar interpoint
+#     distances.
+#     """
+#
+#     (n, n) = d.shape
+#     E = (-0.5 * d ** 2)
+#
+#     # Use mat to get column and row means to act as column and row means.
+#     Er = np.mat(np.mean(E, 1))
+#     Es = np.mat(np.mean(E, 0))
+#
+#     # From Principles of Multivariate Analysis: A User's Perspective (page 107).
+#     F = np.array(E - np.transpose(Er) - Es + np.mean(E))
+#
+#     [U, S, V] = np.linalg.svd(F)
+#
+#     Y = U * np.sqrt(S)
+#
+#     return (Y[:, 0:dimensions], S)
+#
+#
+# class XmippProtStructureMapSPH(ProtAnalysis3D):
+#     """ Protocol for structure mapping based on spherical harmonics. """
+#     _label = 'sph struct map'
+#     _lastUpdateVersion = VERSION_2_0
+#
+#     # --------------------------- DEFINE param functions --------------------------------------------
+#     def _defineParams(self, form):
+#         form.addSection(label='Input')
+#         form.addParam('inputVolumes', params.MultiPointerParam,
+#                       pointerClass='SetOfVolumes,Volume',
+#                       label="Input volume(s)", important=True,
+#                       help='Select one or more volumes (Volume or SetOfVolumes)\n'
+#                            'for structure mapping.')
+#         form.addParam('targetResolution', params.FloatParam, label="Target resolution",
+#                       default=8.0,
+#                       help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
+#                            "2/3 of the Fourier spectrum.")
+#         form.addParam('depth', params.IntParam, default=3,
+#                       label='Harmonical depth',
+#                       expertLevel=params.LEVEL_ADVANCED,
+#                       help='Harmonical depth of the deformation=1,2,3,...')
+#
+#     # --------------------------- INSERT steps functions --------------------------------------------
+#     def _insertAllSteps(self):
+#
+#         volList, dimList, srList = self._iterInputVolumes()
+#         self.distanceMatrix = np.zeros((len(volList), len(volList)))
+#
+#         nVoli = 1
+#         for voli in volList:
+#             self._insertFunctionStep('convertStep', volList[nVoli - 1],
+#                                      dimList[nVoli - 1], srList[nVoli - 1],
+#                                      min(dimList), max(srList))
+#             nVolj = 1
+#             for volj in volList:
+#                 if nVolj != nVoli:
+#                     self._insertFunctionStep('deformStep', volList[nVoli - 1],
+#                                              volList[nVolj - 1], nVoli - 1,
+#                                              nVolj - 1)
+#                 else:
+#                     self.distanceMatrix[nVoli - 1][nVolj - 1] = 0.0
+#                 nVolj += 1
+#             nVoli += 1
+#
+#         self._insertFunctionStep('gatherResultsStep', volList)
+#
+#     # --------------------------- STEPS functions ---------------------------------------------------
+#     def convertStep(self, volFn, volDim, volSr, minDim, maxSr):
+#         Xdim = volDim
+#         Ts = volSr
+#         newTs = self.targetResolution.get() * 1.0 / 3.0
+#         newTs = max(maxSr, newTs)
+#         newXdim = int(Xdim * Ts / newTs)
+#
+#         ih = ImageHandler()
+#         if Xdim != newXdim:
+#             self.runJob("xmipp_image_resize",
+#                         "-i %s --dim %d" % (volFn, newXdim))
+#
+#         if newXdim>minDim:
+#             self.runJob("xmipp_transform_window", " -i %s --crop %d" %
+#                         (volFn, (newXdim - minDim)))
+#
+#     def deformStep(self, inputVolFn, refVolFn, i, j):
+#         fnOut = self._getExtraPath('vol%dDeformedTo%d.vol' % (i, j))
+#
+#         params = ' --i1 %s --i2 %s --apply %s --least_squares --local ' % \
+#                  (refVolFn, inputVolFn, fnOut)
+#         self.runJob("xmipp_volume_align", params)
+#
+#         params = ' -i %s -r %s -o %s --depth %d ' %\
+#                  (fnOut, refVolFn, fnOut, self.depth.get())
+#
+#         self.runJob("xmipp_volume_deform_sph", params)
+#         distanceValue = np.loadtxt('./deformation.txt')
+#         self.distanceMatrix[i][j] = distanceValue
+#
+#
+#     def gatherResultsStep(self, volList):
+#         fnRoot = self._getExtraPath("DistanceMatrix.txt")
+#         nVoli = 1
+#         for i in volList:
+#             nVolj = 1
+#             for j in volList:
+#                 fh = open(fnRoot, "a")
+#                 fh.write("%f\t" % self.distanceMatrix[(nVoli - 1)][(nVolj - 1)])
+#                 fh.close()
+#                 nVolj += 1
+#             fh = open(fnRoot, "a")
+#             fh.write("\n")
+#             fh.close()
+#             nVoli += 1
+#
+#         distance = np.asarray(self.distanceMatrix)
+#         for i in range(1, 4):
+#             embed, _ = mds(distance, i)
+#             embedExtended = np.pad(embed, ((0, 0), (0, i - embed.shape[1])),
+#                                    "constant", constant_values=0)
+#             np.savetxt(self._defineResultsName(i), embedExtended)
+#
+#     # --------------------------- UTILS functions --------------------------------------------
+#
+#
+#     def _iterInputVolumes(self):
+#         """ Iterate over all the input volumes. """
+#         volList = []
+#         dimList = []
+#         srList = []
+#         for pointer in self.inputVolumes:
+#             item = pointer.get()
+#             if item is None:
+#                 break
+#             itemId = item.getObjId()
+#             if isinstance(item, Volume):
+#                 volList.append(item.getFileName())
+#                 dimList.append(item.getDim()[0])
+#                 srList.append(item.getSamplingRate())
+#             elif isinstance(item, SetOfVolumes):
+#                 for vol in item:
+#                     volList.append(vol.getFileName())
+#                     dimList.append(vol.getDim()[0])
+#                     srList.append(vol.getSamplingRate())
+#         return volList, dimList, srList
+#
+#
+#     def _defineResultsName(self, i):
+#         return self._getExtraPath('CoordinateMatrix%d.txt' % i)
 
 
 
diff --git a/xmipp3/protocols/protocol_validate_fscq.py b/xmipp3/protocols/protocol_validate_fscq.py
index 76055bfc2..99cd62eda 100644
--- a/xmipp3/protocols/protocol_validate_fscq.py
+++ b/xmipp3/protocols/protocol_validate_fscq.py
@@ -375,7 +375,7 @@ def createOutputStep(self):
         volume=Volume()
         volume.setFileName(self._getFileName(RESTA_FILE_MRC))
         volume.setSamplingRate(self.inputVolume.get().getSamplingRate())
-        volume.setOrigin(self.inputVolume.get().getOrigin(True))
+        volume.setOrigin(self.inputVolume.get().getOrigin(True).clone())
         self._defineOutputs(fscq_Volume=volume)
         self._defineTransformRelation(self.inputVolume, volume)
         
diff --git a/xmipp3/protocols/protocol_volume_adjust_sub.py b/xmipp3/protocols/protocol_volume_adjust_sub.py
new file mode 100644
index 000000000..9ae89cf4c
--- /dev/null
+++ b/xmipp3/protocols/protocol_volume_adjust_sub.py
@@ -0,0 +1,316 @@
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# *
+# * Authors:  Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
+# *
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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'
+# *
+# **************************************************************************
+
+from pyworkflow.protocol.params import PointerParam, BooleanParam, IntParam, FloatParam, EnumParam, StringParam, FileParam
+from pyworkflow.protocol.constants import LEVEL_ADVANCED
+from pyworkflow.utils import replaceBaseExt, removeExt, getExt
+
+from pwem.convert import headers, downloadPdb, cifToPdb
+from pwem.objects import Volume, Transform
+from pwem.protocols import EMProtocol
+
+
+class XmippProtVolAdjBase(EMProtocol):
+    """ Helper class that contains some Protocol utilities methods
+    used by both  XmippProtVolSubtraction and XmippProtVolAdjust."""
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    @classmethod
+    def _defineParams(self, form):
+        form.addSection(label='Input')
+        form.addParam('vol1', PointerParam, pointerClass='Volume', label="Volume 1 ", help='Specify a volume.')
+        form.addParam('masks', BooleanParam, label='Mask volumes?', default=True,
+                      help='The masks are not mandatory but highly recommendable.')
+        form.addParam('mask1', PointerParam, pointerClass='VolumeMask', label="Mask for volume 1",
+                      condition='masks', help='Specify a mask for volume 1.')
+        form.addParam('resol', FloatParam, label="Filter at resolution: ", default=3, allowsNull=True,
+                      expertLevel=LEVEL_ADVANCED,
+                      help='Resolution (A) at which subtraction will be performed, filtering the input volumes.'
+                           'Value 0 implies no filtering.')
+        form.addParam('sigma', FloatParam, label="Decay of the filter (sigma): ", default=3, condition='resol',
+                      help='Decay of the filter (sigma parameter) to smooth the mask transition',
+                      expertLevel=LEVEL_ADVANCED)
+        form.addParam('iter', IntParam, label="Number of iterations: ", default=5, expertLevel=LEVEL_ADVANCED)
+        form.addParam('rfactor', FloatParam, label="Relaxation factor (lambda): ", default=1,
+                      expertLevel=LEVEL_ADVANCED,
+                      help='Relaxation factor for Fourier amplitude projector (POCS), it should be between 0 and 1, '
+                           'being 1 no relaxation and 0 no modification of volume 2 amplitudes')
+        form.addParam('computeE', BooleanParam, label="Compute energy?", default=True, expertLevel=LEVEL_ADVANCED,
+                      help='Compute energy difference between the different adjustment steps and iterations to see if '
+                           'the method reaches convergence')
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAllSteps(self):
+        self._insertAdjSteps()
+        self._insertFunctionStep('createOutputStep')
+
+    # --------------------------- STEPS functions --------------------------------------------
+    def createOutputStep(self):
+        vol1 = self.vol1.get()
+        volume = Volume()
+        volume.setSamplingRate(vol1.getSamplingRate())
+        if vol1.getFileName().endswith('mrc'):
+            origin = Transform()
+            ccp4header = headers.Ccp4Header(vol1.getFileName(), readHeader=True)
+            shifts = ccp4header.getOrigin()
+            origin.setShiftsTuple(shifts)
+            volume.setOrigin(origin)
+        volume.setFileName(self._getExtraPath("output_volume.mrc"))
+        filename = volume.getFileName()
+        if filename.endswith('.mrc') or filename.endswith('.map'):
+            volume.setFileName(filename + ':mrc')
+        self._defineOutputs(outputVolume=volume)
+
+
+class XmippProtVolSubtraction(XmippProtVolAdjBase):
+    """ This protocol scales a volume in order to adjust it to another one. Then, it can calculate the subtraction
+    of the two volumes. Second input can be a pdb. The volumes should be aligned previously and they have to
+    be equal in size"""
+
+    _label = 'volumes subtraction'
+    IMPORT_OBJ = 0
+    IMPORT_FROM_FILES = 1
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    def _defineParams(self, form):
+        XmippProtVolAdjBase._defineParams(form)
+        form.addParam('pdb', BooleanParam, label='Is the second input a PDB?', default=False,
+                      help='If yes, the protocol will generate and store in folder "extra" of this protocol '
+                           'a volume and a mask from the pdb. This is not the recommended option, as the automatic '
+                           'conversion of the PDB into a density map may not be successful due to origin mismatches. '
+                           'We recommend to convert previously the PDB, inspect the converted map and use the map as '
+                           'input. If not, a second volume has to be input and optionally (but highly recommendable), '
+                           'a mask for it.')
+        form.addParam('inputPdbData', EnumParam, choices=['object', 'file'], condition='pdb',
+                      label="Retrieve PDB from", default=self.IMPORT_OBJ,
+                      display=EnumParam.DISPLAY_HLIST,
+                      help='Retrieve PDB data from server, use a pdb Object, or a local file')
+        form.addParam('pdbObj', PointerParam, pointerClass='AtomStruct',
+                      label="Input pdb ", condition='inputPdbData == IMPORT_OBJ and pdb', allowsNull=True,
+                      help='Specify a pdb object. This is not the recommended option, as the automatic conversion of '
+                           'the PDB into a density map may not be successful due to origin mismatches. We recommend'
+                           'to convert previously the PDB, inspect the converted map and use the map as input.')
+        form.addParam('pdbFile', FileParam,
+                      label="File path", condition='inputPdbData == IMPORT_FROM_FILES and pdb', allowsNull=True,
+                      help='Specify a path to desired PDB structure.')
+        form.addParam('vol2', PointerParam, pointerClass='Volume', label="Volume 2 ", condition='pdb == False',
+                      help='Specify a volume.')
+        form.addParam('mask2', PointerParam, pointerClass='VolumeMask', label="Mask for volume 2",
+                      condition='masks and pdb==False', help='Specify a mask for volume 1.')
+        form.addParam('saveFiles', BooleanParam, label='Save intermediate files?', default=False,
+                      expertLevel=LEVEL_ADVANCED, help='Save input volume 1 filtered and input volume 2 adjusted, which'
+                                                       'are the volumes that are really subtracted.')
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAdjSteps(self):
+        if self.pdb:
+            self._insertFunctionStep('convertPdbStep')
+            self._insertFunctionStep('generateMask2Step')
+        self._insertFunctionStep('subtractionStep')
+
+    # --------------------------- STEPS functions --------------------------------------------
+    def convertPdbStep(self):
+        vol1 = self.vol1.get()
+        pdbFn = self._getPdbFileName()
+        self.outFile = self._getVolName()
+        if getExt(pdbFn) == ".cif":
+            pdbFn2 = replaceBaseExt(pdbFn, 'pdb')
+            cifToPdb(pdbFn, pdbFn2)
+            pdbFn = pdbFn2
+        samplingR = vol1.getSamplingRate()
+        size = vol1.getDim()
+        ccp4header = headers.Ccp4Header(vol1.getFileName(), readHeader=True)
+        self.shifts = ccp4header.getOrigin()
+        # convert pdb to volume with the size and origin of the input volume
+        args = ' -i %s --sampling %f -o %s --size %d %d %d --orig %d %d %d' % \
+               (pdbFn, samplingR, removeExt(self.outFile), size[2], size[1], size[0], self.shifts[0]/samplingR,
+                self.shifts[1]/samplingR, self.shifts[2]/samplingR)
+        program = "xmipp_volume_from_pdb"
+        self.runJob(program, args)
+        volpdbmrc = "%s.mrc" % removeExt(self.outFile)
+        # convert volume from pdb to .mrc in order to store the origin in the mrc header
+        args2 = ' -i %s -o %s -t vol' % (self.outFile, volpdbmrc)
+        program2 = "xmipp_image_convert"
+        self.runJob(program2, args2)
+        # write origin in mrc header of volume from pdb
+        ccp4headerOut = headers.Ccp4Header(volpdbmrc, readHeader=True)
+        ccp4headerOut.setOrigin(self.shifts)
+
+    def generateMask2Step(self):
+        args = ' -i %s -o %s --select below 0.010000 --substitute binarize' % (self.outFile,
+                                                                               self._getExtraPath("mask2.mrc"))
+        program = "xmipp_transform_threshold"
+        self.runJob(program, args)
+        args2 = ' -i %s --binaryOperation dilation --size 1' % (self._getExtraPath("mask2.mrc"))
+        program2 = "xmipp_transform_morphology"
+        self.runJob(program2, args2)
+
+    def subtractionStep(self):
+        vol1 = self.vol1.get().clone()
+        fileName1 = vol1.getFileName()
+        if fileName1.endswith('.mrc'):
+            fileName1 += ':mrc'
+        if self.pdb:
+            vol2 = self.outFile
+            mask2 = self._getExtraPath("mask2.mrc")
+        else:
+            vol2 = self.vol2.get().getFileName()
+            if vol2.endswith('.mrc'):
+                vol2 += ':mrc'
+            if self.masks:
+                mask2 = self.mask2.get().getFileName()
+        resol = self.resol.get()
+        iter = self.iter.get()
+        program = "xmipp_volume_subtraction"
+        args = '--i1 %s --i2 %s -o %s --iter %s --lambda %s --sub' % \
+               (vol1.getFileName(), vol2, self._getExtraPath("output_volume.mrc"), iter, self.rfactor.get())
+        if resol:
+            fc = vol1.getSamplingRate()/resol
+            args += ' --cutFreq %f --sigma %d' % (fc, self.sigma.get())
+
+        if self.masks:
+            args += ' --mask1 %s --mask2 %s' % (self.mask1.get().getFileName(), mask2)
+        if self.saveFiles:
+            args += ' --saveV1 %s --saveV2 %s' % (self._getExtraPath('vol1_filtered.mrc'),
+                                                  self._getExtraPath('vol2_adjusted.mrc'))
+        if self.computeE:
+            args += ' --computeEnergy'
+        self.runJob(program, args)
+
+    # --------------------------- INFO functions --------------------------------------------
+    def _summary(self):
+        summary = ["Volume 1: %s" % self.vol1.get().getFileName()]
+        if self.pdb:
+            if self.inputPdbData == self.IMPORT_OBJ:
+                summary.append("Input PDB File: %s" % self.pdbObj.get().getFileName())
+            else:
+                summary.append("Input PDB File: %s" % self.pdbFile.get())
+            summary.append("Mask 2 generated")
+        else:
+            summary.append("Volume 2: %s" % self.vol2.get().getFileName())
+        if self.masks:
+            summary.append("Input mask 1: %s" % self.mask1.get().getFileName())
+            summary.append("Input mask 2: %s" % self.mask2.get().getFileName())
+        if self.resol.get() != 0:
+            summary.append("Subtraction at resolution %f A" % self.resol.get())
+        return summary
+
+    def _methods(self):
+        methods = []
+        if not hasattr(self, 'outputVolume'):
+            methods.append("Output volume not ready yet.")
+        else:
+            methods.append("Volume %s subtracted from volume %s" % (self.vol2.get().getFileName(),
+                                                                    self.vol1.get().getFileName()))
+            if self.resol.get() != 0:
+                methods.append(" at resolution %f A" % self.resol.get())
+        return methods
+
+    def _validate(self):
+        errors = []
+        rfactor = self.rfactor.get()
+        if rfactor < 0 or rfactor > 1:
+            errors.append('Relaxation factor (lambda) must be between 0 and 1')
+        return errors
+
+    # --------------------------- UTLIS functions --------------------------------------------
+    def _getPdbFileName(self):
+        if self.inputPdbData == self.IMPORT_OBJ:
+            return self.pdbObj.get().getFileName()
+        else:
+            return self.pdbFile.get()
+
+    def _getVolName(self):
+        return self._getExtraPath(replaceBaseExt(self._getPdbFileName(), "vol"))
+
+
+class XmippProtVolAdjust(XmippProtVolAdjBase):
+    """ This protocol scales a volume in order to assimilate it to another one.
+    The volume with the best resolution should be the first one.
+    The volumes should be aligned previously and they have to be equal in size"""
+
+    _label = 'volumes adjust'
+    IMPORT_OBJ = 0
+    IMPORT_FROM_FILES = 1
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    def _defineParams(self, form):
+        XmippProtVolAdjBase._defineParams(form)
+        form.addParam('vol2', PointerParam, pointerClass='Volume', label="Volume 2 ", help='Specify a volume.')
+        form.addParam('mask2', PointerParam, pointerClass='VolumeMask', label="Mask for volume 2",
+                      condition='masks', help='Specify a mask for volume 1.')
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAdjSteps(self):
+        self._insertFunctionStep('adjustStep')
+
+    # --------------------------- STEPS functions --------------------------------------------
+    def adjustStep(self):
+        vol1 = self.vol1.get().clone()
+        vol2 = self.vol2.get().getFileName()
+        resol = self.resol.get()
+        iter = self.iter.get()
+        program = "xmipp_volume_subtraction"
+        args = '--i1 %s --i2 %s -o %s --iter %s --lambda %s' % \
+               (vol1.getFileName(), vol2, self._getExtraPath("output_volume.mrc"), iter, self.rfactor.get())
+        if resol:
+            fc = vol1.getSamplingRate()/resol
+            args += ' --cutFreq %f --sigma %d' % (fc, self.sigma.get())
+        if self.masks:
+            args += ' --mask1 %s --mask2 %s' % (self.mask1.get().getFileName(), self.mask2.get().getFileName())
+        if self.computeE:
+            args += ' --computeEnergy'
+        self.runJob(program, args)
+
+    # --------------------------- INFO functions --------------------------------------------
+    def _summary(self):
+        summary = ["Volume 1: %s\nVolume 2: %s" % (self.vol1.get().getFileName(), self.vol2.get().getFileName())]
+        if self.masks:
+            summary.append("Input mask 1: %s" % self.mask1.get().getFileName())
+            summary.append("Input mask 2: %s" % self.mask2.get().getFileName())
+        if self.resol.get() != 0:
+            summary.append("Filter at resolution %f A" % self.resol.get())
+        return summary
+
+    def _methods(self):
+        methods = []
+        if not hasattr(self, 'outputVolume'):
+            methods.append("Output volume not ready yet.")
+        else:
+            methods.append("Volume %s adjusted to volume %s" % (self.vol2.get().getFileName(),
+                                                                self.vol1.get().getFileName()))
+            if self.resol.get() != 0:
+                methods.append(" at resolution %f A" % self.resol.get())
+        return methods
+
+    def _validate(self):
+        errors = []
+        rfactor = self.rfactor.get()
+        if rfactor < 0 or rfactor > 1:
+            errors.append('Relaxation factor (lambda) must be between 0 and 1')
+        return errors
diff --git a/xmipp3/protocols/protocol_volume_consensus.py b/xmipp3/protocols/protocol_volume_consensus.py
new file mode 100644
index 000000000..0fe97acf5
--- /dev/null
+++ b/xmipp3/protocols/protocol_volume_consensus.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# *
+# * Authors:  Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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'
+# *
+# **************************************************************************
+
+from pyworkflow.protocol.params import MultiPointerParam
+from pwem.objects import Volume, Transform
+from pwem.protocols import ProtInitialVolume
+
+
+class XmippProtVolConsensus(ProtInitialVolume):
+    """ This protocol performs a fusion of all the input volumes, which should be preprocessed with protocol 'volume
+    substraction' saving volume 2, in order to be as similar as possible before the fusion. The output of
+    this protocol is the consensus volume and another volume which indicates the maximun difference between input
+    volumes in each voxel."""
+
+    _label = 'volume consensus'
+
+    # --------------------------- DEFINE param functions --------------------------------------------
+    def _defineParams(self, form):
+
+        form.addSection(label='Input')
+        form.addParam('vols', MultiPointerParam, pointerClass='Volume', label="Volumes",
+                      help='Select the volumes for the consensus.')
+
+    # --------------------------- INSERT steps functions --------------------------------------------
+    def _insertAllSteps(self):
+        self._insertFunctionStep('fusionStep')
+        self._insertFunctionStep('createOutputStep')
+        self._insertFunctionStep("createChimeraScript")
+
+    # --------------------------- STEPS functions ---------------------------------------------------
+    def fusionStep(self):
+        inputVols = self._getExtraPath("input_volumes.txt")
+        fhInputVols = open(inputVols, 'w')
+        for i, vol in enumerate(self.vols):
+            fileName = vol.get().getFileName()
+            if fileName.endswith(':mrc'):
+                fileName = fileName[:-4]
+            fhInputVols.write(fileName + '\n')
+        fhInputVols.close()
+        outVolFn = self._getExtraPath("consensus_volume.mrc")
+        args = " -i %s -o %s" % (inputVols, outVolFn)
+        self.runJob("xmipp_volume_consensus", args)
+
+    def createOutputStep(self):
+        outVol = Volume()
+        outVol.setSamplingRate(self.vols[0].get().getSamplingRate())
+        outVol.setFileName(self._getExtraPath("consensus_volume.mrc"))
+        outVol2 = Volume()
+        outVol2.setSamplingRate(self.vols[0].get().getSamplingRate())
+        outVol2.setFileName(self._getExtraPath("consensus_volume_diff.mrc"))
+        self._defineOutputs(outputVolume=outVol)
+        self._defineOutputs(outputVolumeDiff=outVol2)
+
+    def createChimeraScript(self):
+        fnRoot = "extra/"
+        scriptFile = self._getPath('result') + '_fusion_chimera.cxc'
+        fhCmd = open(scriptFile, 'w')
+        fhCmd.write("open %s\n" % (fnRoot+"consensus_volume.mrc"))
+        fhCmd.write("open %s\n" % (fnRoot+"consensus_volume_diff.mrc"))
+        fhCmd.write("vol #2 hide\n")
+        fhCmd.write("color sample #1 map #2 palette rainbow\n")
+        fhCmd.close()
+
+    # --------------------------- INFO functions --------------------------------------------
+    def _summary(self):
+        summary = []
+        if not hasattr(self, 'outputVolume'):
+            summary.append("Output volume not ready yet.")
+        else:
+            for i, vol in enumerate(self.vols):
+                summary.append("Volume %d: %s" % (i+1, vol.get().getFileName()))
+        return summary
+
+    def _methods(self):
+        methods = []
+        if not hasattr(self, 'outputVolume'):
+            methods.append("Output volume not ready yet.")
+        else:
+            methods.append("We compute a consensus volume from %d input volumes at %f A/px" %
+                           (self.vols.getSize(), self.vols[0].get().getSamplingRate()))
+        return methods
diff --git a/xmipp3/protocols/protocol_volume_deform_sph.py b/xmipp3/protocols/protocol_volume_deform_sph.py
index b0be2dbb9..09a0438d7 100644
--- a/xmipp3/protocols/protocol_volume_deform_sph.py
+++ b/xmipp3/protocols/protocol_volume_deform_sph.py
@@ -1,3 +1,12 @@
+########
+
+# This protocol has been removed from Scipion as there is a newer version being developed that
+# will solve some of the problems of this current implementation. The code will be available
+# again in a newer release.
+
+########
+
+
 
 # **************************************************************************
 # *
@@ -24,104 +33,104 @@
 # *  e-mail address 'scipion@cnb.csic.es'
 # *
 # **************************************************************************
-from pwem.protocols import ProtAnalysis3D
-import pyworkflow.protocol.params as params
-from pwem.emlib.image import ImageHandler
-from pwem.objects import Volume
-from pyworkflow import VERSION_2_0
-
-
-class XmippProtVolumeDeformSPH(ProtAnalysis3D):
-    """ Protocol for volume deformation based on spherical harmonics. """
-    _label = 'sph volume deform'
-    _lastUpdateVersion = VERSION_2_0
-
-    # --------------------------- DEFINE param functions --------------------------------------------
-    def _defineParams(self, form):
-        form.addSection(label='Input')
-        form.addParam('inputVolume', params.PointerParam, label="Input volume",
-                      pointerClass='Volume')
-        form.addParam('refVolume', params.PointerParam, label="Reference volume",
-                      pointerClass='Volume')
-        form.addParam('targetResolution', params.FloatParam, label="Target resolution",
-                      default=8.0,
-                      help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
-                           "2/3 of the Fourier spectrum.")
-        form.addParam('depth', params.IntParam, default=3,
-                      label='Harmonical depth',
-                      expertLevel=params.LEVEL_ADVANCED,
-                      help='Harmonical depth of the deformation=1,2,3,...')
-
-
-    def _createFilenameTemplates(self):
-        """ Centralize how files are called """
-        myDict = {
-            'fnRefVol': self._getExtraPath('ref_volume.vol'),
-            'fnInputVol': self._getExtraPath('input_volume.vol'),
-            'fnOutVol': self._getExtraPath('vol1DeformedTo2.vol')
-                 }
-        self._updateFilenamesDict(myDict)
-
-    # --------------------------- INSERT steps functions -----------------------
-    def _insertAllSteps(self):
-        self._createFilenameTemplates()
-        self._insertFunctionStep("convertStep")
-        self._insertFunctionStep("deformStep")
-        self._insertFunctionStep("createOutputStep")
-
-    # --------------------------- STEPS functions ------------------------------
-    def convertStep(self):
-        fnInputVol = self._getFileName('fnInputVol')
-        fnRefVol = self._getFileName('fnRefVol')
-
-        XdimI = self.inputVolume.get().getDim()[0]
-        TsI = self.inputVolume.get().getSamplingRate()
-        XdimR = self.refVolume.get().getDim()[0]
-        TsR = self.refVolume.get().getSamplingRate()
-        self.newTs = self.targetResolution.get() * 1.0 / 3.0
-        self.newTs = max(TsI, TsR, self.newTs)
-        newXdimI = int(XdimI * TsI / self.newTs)
-        newXdimR = int(XdimR * TsR / self.newTs)
-        self.newXdim = min(newXdimI, newXdimR)
-
-        ih = ImageHandler()
-        ih.convert(self.inputVolume.get(), fnInputVol)
-        if XdimI != newXdimI:
-            self.runJob("xmipp_image_resize",
-                        "-i %s --dim %d" % (fnInputVol, newXdimI))
-        if newXdimI>self.newXdim:
-            self.runJob("xmipp_transform_window", " -i %s --crop %d" %
-                        (fnInputVol, (newXdimI-self.newXdim)))
-
-
-        ih.convert(self.refVolume.get(), fnRefVol)
-        if XdimR != newXdimR:
-            self.runJob("xmipp_image_resize",
-                        "-i %s --dim %d" % (fnRefVol, newXdimR))
-        if newXdimR>self.newXdim:
-            self.runJob("xmipp_transform_window", " -i %s --crop %d" %
-                        (fnRefVol, (newXdimR-self.newXdim)))
-
-
-    def deformStep(self):
-        fnInputVol = self._getFileName('fnInputVol')
-        fnRefVol = self._getFileName('fnRefVol')
-        fnOutVol = self._getFileName('fnOutVol')
-
-        params = ' --i1 %s --i2 %s --apply %s --least_squares --local ' % \
-                 (fnRefVol, fnInputVol, fnOutVol)
-        self.runJob("xmipp_volume_align", params)
-
-
-        params = ' -i %s -r %s -o %s --analyzeStrain --depth %d ' % \
-                 (fnOutVol, fnRefVol, fnOutVol, self.depth.get())
-        self.runJob("xmipp_volume_deform_sph", params)
-
-
-    def createOutputStep(self):
-        vol = Volume()
-        vol.setLocation(self._getFileName('fnOutVol'))
-        vol.setSamplingRate(self.newTs)
-        self._defineOutputs(outputVolume=vol)
-        self._defineSourceRelation(self.inputVolume, vol)
-
+# from pwem.protocols import ProtAnalysis3D
+# import pyworkflow.protocol.params as params
+# from pwem.emlib.image import ImageHandler
+# from pwem.objects import Volume
+# from pyworkflow import VERSION_2_0
+#
+#
+# class XmippProtVolumeDeformSPH(ProtAnalysis3D):
+#     """ Protocol for volume deformation based on spherical harmonics. """
+#     _label = 'sph volume deform'
+#     _lastUpdateVersion = VERSION_2_0
+#
+#     # --------------------------- DEFINE param functions --------------------------------------------
+#     def _defineParams(self, form):
+#         form.addSection(label='Input')
+#         form.addParam('inputVolume', params.PointerParam, label="Input volume",
+#                       pointerClass='Volume')
+#         form.addParam('refVolume', params.PointerParam, label="Reference volume",
+#                       pointerClass='Volume')
+#         form.addParam('targetResolution', params.FloatParam, label="Target resolution",
+#                       default=8.0,
+#                       help="In Angstroms, the images and the volume are rescaled so that this resolution is at "
+#                            "2/3 of the Fourier spectrum.")
+#         form.addParam('depth', params.IntParam, default=3,
+#                       label='Harmonical depth',
+#                       expertLevel=params.LEVEL_ADVANCED,
+#                       help='Harmonical depth of the deformation=1,2,3,...')
+#
+#
+#     def _createFilenameTemplates(self):
+#         """ Centralize how files are called """
+#         myDict = {
+#             'fnRefVol': self._getExtraPath('ref_volume.vol'),
+#             'fnInputVol': self._getExtraPath('input_volume.vol'),
+#             'fnOutVol': self._getExtraPath('vol1DeformedTo2.vol')
+#                  }
+#         self._updateFilenamesDict(myDict)
+#
+#     # --------------------------- INSERT steps functions -----------------------
+#     def _insertAllSteps(self):
+#         self._createFilenameTemplates()
+#         self._insertFunctionStep("convertStep")
+#         self._insertFunctionStep("deformStep")
+#         self._insertFunctionStep("createOutputStep")
+#
+#     # --------------------------- STEPS functions ------------------------------
+#     def convertStep(self):
+#         fnInputVol = self._getFileName('fnInputVol')
+#         fnRefVol = self._getFileName('fnRefVol')
+#
+#         XdimI = self.inputVolume.get().getDim()[0]
+#         TsI = self.inputVolume.get().getSamplingRate()
+#         XdimR = self.refVolume.get().getDim()[0]
+#         TsR = self.refVolume.get().getSamplingRate()
+#         self.newTs = self.targetResolution.get() * 1.0 / 3.0
+#         self.newTs = max(TsI, TsR, self.newTs)
+#         newXdimI = int(XdimI * TsI / self.newTs)
+#         newXdimR = int(XdimR * TsR / self.newTs)
+#         self.newXdim = min(newXdimI, newXdimR)
+#
+#         ih = ImageHandler()
+#         ih.convert(self.inputVolume.get(), fnInputVol)
+#         if XdimI != newXdimI:
+#             self.runJob("xmipp_image_resize",
+#                         "-i %s --dim %d" % (fnInputVol, newXdimI))
+#         if newXdimI>self.newXdim:
+#             self.runJob("xmipp_transform_window", " -i %s --crop %d" %
+#                         (fnInputVol, (newXdimI-self.newXdim)))
+#
+#
+#         ih.convert(self.refVolume.get(), fnRefVol)
+#         if XdimR != newXdimR:
+#             self.runJob("xmipp_image_resize",
+#                         "-i %s --dim %d" % (fnRefVol, newXdimR))
+#         if newXdimR>self.newXdim:
+#             self.runJob("xmipp_transform_window", " -i %s --crop %d" %
+#                         (fnRefVol, (newXdimR-self.newXdim)))
+#
+#
+#     def deformStep(self):
+#         fnInputVol = self._getFileName('fnInputVol')
+#         fnRefVol = self._getFileName('fnRefVol')
+#         fnOutVol = self._getFileName('fnOutVol')
+#
+#         params = ' --i1 %s --i2 %s --apply %s --least_squares --local ' % \
+#                  (fnRefVol, fnInputVol, fnOutVol)
+#         self.runJob("xmipp_volume_align", params)
+#
+#
+#         params = ' -i %s -r %s -o %s --analyzeStrain --depth %d ' % \
+#                  (fnOutVol, fnRefVol, fnOutVol, self.depth.get())
+#         self.runJob("xmipp_volume_deform_sph", params)
+#
+#
+#     def createOutputStep(self):
+#         vol = Volume()
+#         vol.setLocation(self._getFileName('fnOutVol'))
+#         vol.setSamplingRate(self.newTs)
+#         self._defineOutputs(outputVolume=vol)
+#         self._defineSourceRelation(self.inputVolume, vol)
+#
diff --git a/xmipp3/protocols/protocol_volume_local_sharpening.py b/xmipp3/protocols/protocol_volume_local_sharpening.py
index 6f2a90487..592067ebd 100644
--- a/xmipp3/protocols/protocol_volume_local_sharpening.py
+++ b/xmipp3/protocols/protocol_volume_local_sharpening.py
@@ -253,10 +253,8 @@ def sharpeningAndMonoResStep(self):
                 nextIter = False
                 break
 
-        # TODO: please copy the file using python not the operating system
-        os.system('cp '  +self._getExtraPath('sharpenedMap_'+str(self.iteration)+'.mrc')+
-                   ' '  +self._getExtraPath('sharpenedMap_last.mrc'))
-
+        os.rename(self._getExtraPath('sharpenedMap_' + str(self.iteration) + '.mrc'),
+                  self._getExtraPath('sharpenedMap_last.mrc'))
         
         resFile = self.resolutionVolume.get().getFileName()        
         pathres=dirname(resFile)
@@ -271,17 +269,17 @@ def sharpeningAndMonoResStep(self):
              
     def createOutputStep(self):
 
-        volume = Volume()
-        volume.setFileName(self._getExtraPath('sharpenedMap_last.mrc'))
-        volume.setSamplingRate(self.inputVolume.get().getSamplingRate())
-        volume.setOrigin(self.inputVolume.get().getOrigin(True))
-
         volumesSet = self._createSetOfVolumes()
         volumesSet.setSamplingRate(self.inputVolume.get().getSamplingRate())
         for i in range(self.iteration):
             vol = Volume()
-            vol.setLocation(i, self._getExtraPath('sharpenedMap_%d.mrc' % (i + 1)))
-            vol.setObjComment("Sharpened Map, \n Epoch %d" % (i + 1))
+            vol.setOrigin(self.inputVolume.get().getOrigin(True))
+            if (self.iteration > (i + 1)):
+                vol.setLocation(i, self._getExtraPath('sharpenedMap_%d.mrc' % (i + 1)))
+                vol.setObjComment("Sharpened Map, \n Epoch %d" % (i + 1))
+            else:
+                vol.setLocation(i, self._getExtraPath('sharpenedMap_last.mrc'))
+                vol.setObjComment("Sharpened Map, \n Epoch last")
             volumesSet.append(vol)
 
         self._defineOutputs(outputVolumes=volumesSet)
diff --git a/xmipp3/utils.py b/xmipp3/utils.py
index 2b0b0e154..f5bdb6e23 100644
--- a/xmipp3/utils.py
+++ b/xmipp3/utils.py
@@ -28,18 +28,20 @@
 This module contains utils functions for Xmipp protocols
 """
 
-from os.path import join
+from os.path import join, basename
 import numpy as np
-
+import math
 from pyworkflow import Config
+import pyworkflow.utils as pwutils
 from pwem import emlib
+from pyworkflow.object import Object
 
 
 def validateXmippGpuBins():
     pass
 
 
-BAD_IMPORT_TENSORFLOW_KERAS_MSG='''
+BAD_IMPORT_TENSORFLOW_KERAS_MSG = '''
 Error, tensorflow/keras is probably not installed. Install it with:\n  ./scipion installb deepLearningToolkit
 If gpu version of tensorflow desired, install cuda 8.0 or cuda 9.0
 We will try to automatically install cudnn, if unsucesfully, install cudnn and add to LD_LIBRARY_PATH
@@ -53,6 +55,63 @@ def validateXmippGpuBins():
 '''.format(Config.SCIPION_CONFIG)
 
 
+def writeImageFromArray(array, fn):
+    img = emlib.Image()
+    img.setData(array)
+    img.write(fn)
+
+
+def readImage(fn):
+    img = emlib.Image()
+    img.read(fn)
+    return img
+
+
+def applyTransform(imag_array, M, shape):
+    ''' Apply a transformation(M) to a np array(imag) and return it in a given shape
+  '''
+    imag = emlib.Image()
+    imag.setData(imag_array)
+    imag = imag.applyWarpAffine(list(M.flatten()), shape, True)
+    return imag.getData()
+
+
+def rotation(imag, angle, shape, P):
+    '''Rotate a np.array and return also the transformation matrix
+  #imag: np.array
+  #angle: angle in degrees
+  #shape: output shape
+  #P: transform matrix (further transformation in addition to the rotation)'''
+    (hsrc, wsrc) = imag.shape
+    angle *= math.pi / 180
+    T = np.asarray([[1, 0, -wsrc / 2], [0, 1, -hsrc / 2], [0, 0, 1]])
+    R = np.asarray([[math.cos(angle), math.sin(angle), 0], [-math.sin(angle), math.cos(angle), 0], [0, 0, 1]])
+    M = np.matmul(np.matmul(np.linalg.inv(T), np.matmul(R, T)), P)
+
+    transformed = applyTransform(imag, M, shape)
+    return transformed, M
+
+
+def flipYImage(inFn, outFn=None, outDir=None):
+    '''Flips an image in the Y axis'''
+    if outFn == None:
+        if not '_flipped' in basename(inFn):
+            ext = pwutils.getExt(inFn)
+            outFn = inFn.replace(ext, '_flipped' + ext)
+        else:
+            outFn = inFn.replace('_flipped', '')
+    if outDir != None:
+        outFn = outDir + '/' + basename(outFn)
+    gainImg = readImage(inFn)
+    imag_array = np.asarray(gainImg.getData(), dtype=np.float64)
+
+    # Flipped Y matrix
+    M, angle = np.asarray([[1, 0, 0], [0, -1, imag_array.shape[0]], [0, 0, 1]]), 0
+    flipped_array, M = rotation(imag_array, angle, imag_array.shape, M)
+    writeImageFromArray(flipped_array, outFn)
+    return outFn
+
+
 def copy_image(imag):
     ''' Return a copy of a xmipp_image
     '''
@@ -63,7 +122,7 @@ def copy_image(imag):
 
 def matmul_serie(mat_list, size=4):
     '''Return the matmul of several numpy arrays'''
-    #Return the identity matrix if te list is empty
+    # Return the identity matrix if te list is empty
     if len(mat_list) > 0:
         res = np.identity(len(mat_list[0]))
         for i in range(len(mat_list)):
@@ -81,13 +140,205 @@ def normalize_array(ar):
     return ar
 
 
-def surrounding_values(a,ii,jj,depth=1):
+def surrounding_values(a, ii, jj, depth=1):
     '''Return a list with the surrounding elements, given the indexs of the center, from an 2D numpy array
     '''
-    values=[]
-    for i in range(ii-depth,ii+depth+1):
-        for j in range(jj-depth,jj+depth+1):
-            if i>=0 and j>=0 and i<a.shape[0] and j<a.shape[1]:
-                if i!=ii or j!=jj:
-                    values+=[a[i][j]]
+    values = []
+    for i in range(ii - depth, ii + depth + 1):
+        for j in range(jj - depth, jj + depth + 1):
+            if i >= 0 and j >= 0 and i < a.shape[0] and j < a.shape[1]:
+                if i != ii or j != jj:
+                    values += [a[i][j]]
     return values
+
+
+class Point:
+    """ Return x, y 2d coordinates and some other properties
+    such as weight and state.
+    """
+    # Selection states
+    DISCARDED = -1
+    NORMAL = 0
+    SELECTED = 1
+
+    def __init__(self, pointId, data, weight, state=0):
+        self._id = pointId
+        self._data = data
+        self._weight = weight
+        self._state = state
+        self._container = None
+
+    def getId(self):
+        return self._id
+
+    def getX(self):
+        return self._data[self._container.XIND]
+
+    def setX(self, value):
+        self._data[self._container.XIND] = value
+
+    def getY(self):
+        return self._data[self._container.YIND]
+
+    def setY(self, value):
+        self._data[self._container.YIND] = value
+
+    def getZ(self):
+        return self._data[self._container.ZIND]
+
+    def setZ(self, value):
+        self._data[self._container.ZIND] = value
+
+    def getWeight(self):
+        return self._weight
+
+    def getState(self):
+        return self._state
+
+    def setState(self, newState):
+        self._state = newState
+
+    def eval(self, expression):
+        localDict = {}
+        for i, x in enumerate(self._data):
+            localDict['x%d' % (i + 1)] = x
+        return eval(expression, {"__builtins__": None}, localDict)
+
+    def setSelected(self):
+        self.setState(Point.SELECTED)
+
+    def isSelected(self):
+        return self.getState() == Point.SELECTED
+
+    def setDiscarded(self):
+        self.setState(Point.DISCARDED)
+
+    def isDiscarded(self):
+        return self.getState() == Point.DISCARDED
+
+    def getData(self):
+        return self._data
+
+
+class Data():
+    """ Store data points. """
+
+    def __init__(self, **kwargs):
+        # Indexes of data
+        self._dim = kwargs.get('dim')  # The points dimensions
+        self.clear()
+
+    def addPoint(self, point, position=None):
+        point._container = self
+        if position is None:
+            self._points.append(point)
+        else:
+            self._points.insert(position, point)
+
+    def getPoint(self, index):
+        return self._points[index]
+
+    def __iter__(self):
+        for point in self._points:
+            if not point.isDiscarded():
+                yield point
+
+    def iterAll(self):
+        """ Iterate over all points, including the discarded ones."""
+        return iter(self._points)
+
+    def getXData(self):
+        return [p.getX() for p in self]
+
+    def getYData(self):
+        return [p.getY() for p in self]
+
+    def getZData(self):
+        return [p.getZ() for p in self]
+
+    def getWeights(self):
+        return [p.getWeight() for p in self]
+
+    def getSize(self):
+        return len(self._points)
+
+    def getSelectedSize(self):
+        return len([p for p in self if p.isSelected()])
+
+    def getDiscardedSize(self):
+        return len([p for p in self.iterAll() if p.isDiscarded()])
+
+    def clear(self):
+        self.XIND = 0
+        self.YIND = 1
+        self.ZIND = 2
+        self._points = []
+
+
+class PathData(Data):
+    """ Just contains two list of x and y coordinates. """
+
+    def __init__(self, **kwargs):
+        Data.__init__(self, **kwargs)
+
+    def splitLongestSegment(self):
+        """ Split the longest segment by adding the midpoint. """
+        maxDist = 0
+        n = self.getSize()
+        # Find the longest segment and its index
+        for i in range(n - 1):
+            p1 = self.getPoint(i)
+            x1, y1 = p1.getX(), p1.getY()
+            p2 = self.getPoint(i + 1)
+            x2, y2 = p2.getX(), p2.getY()
+            dist = (x1 - x2) ** 2 + (y1 - y2) ** 2
+            if dist > maxDist:
+                maxDist = dist
+                maxIndex = i + 1
+                midX = (x1 + x2) / 2
+                midY = (y1 + y2) / 2
+        # Add a midpoint to it
+        point = self.createEmptyPoint()
+        point.setX(midX)
+        point.setY(midY)
+        self.addPoint(point, position=maxIndex)
+
+    def createEmptyPoint(self):
+        data = [0.] * self._dim  # create 0, 0...0 point
+        point = Point(0, data, 0)
+        point._container = self
+
+        return point
+
+    def removeLastPoint(self):
+        del self._points[-1]
+
+
+class DeprecatedParam:
+    """ Deprecated param. To be used when you want to rename an existing param
+    and still be able to recover old param value. It acts like a redirector, passing the
+    value received when its value is set to the new renamed parameter"""
+    def __init__(self, newParamName, prot):
+        """        :param newParamName: Name of the new renamed param
+        :param prot: Protocol hosting this and the renamed param        """
+        self._newParamName = newParamName
+        self.prot = prot
+        # Need to fake being a Object at loading time
+        self._objId = None
+        self._objIsPointer = False
+
+    def set(self, value):
+        if hasattr(self.prot, self._newParamName):
+            newParam = self._getNewParam()
+            newParam.set(value)
+            if newParam.isPointer():
+                self._extended = newParam._extended
+
+    def isPointer(self):
+        return self._getNewParam().isPointer()
+
+    def getObjValue(self):
+        return None
+
+    def _getNewParam(self):
+        return getattr(self.prot, self._newParamName)
diff --git a/xmipp3/viewers/__init__.py b/xmipp3/viewers/__init__.py
index 62f278799..a8586eff5 100644
--- a/xmipp3/viewers/__init__.py
+++ b/xmipp3/viewers/__init__.py
@@ -36,11 +36,11 @@
 from .viewer_movie_alignment import XmippMovieAlignViewer, XmippMovieMaxShiftViewer
 from .viewer_normalize_strain import XmippNormalizeStrainViewer
 from .viewer_resolution3d import XmippResolution3DViewer
+from .viewer_resolution_bfactor import XmippBfactorResolutionViewer
 from .viewer_resolution_directional import XmippMonoDirViewer
 from .viewer_resolution_fso import XmippProtFSOViewer
 from .viewer_resolution_monogenic_signal import XmippMonoResViewer
 from .viewer_resolution_deepres import XmippResDeepResViewer
-from .viewer_resolution_monotomo import XmippMonoTomoViewer
 from .viewer_validate_nontilt import XmippValidateNonTiltViewer
 from .viewer_validate_fscq import XmippProtValFitViewer
 from .viewer_split_volume import XmippViewerSplitVolume
@@ -51,17 +51,19 @@
 from .viewer_extract_asymmetric_unit import viewerXmippProtExtractUnit
 from .viewer_deepEMHancer import viewerXmippProtDeepVolPostProc
 from .viewer_local_sharpening import viewerXmippProtLocSharp
-
+from .viewer_volume_subtraction import XmippProtVolSubtractionViewer
+from .viewer_volume_consensus import XmippVolumeConsensusViewer
 
 #from .viewer_combine_pdb import XmippProtCombinePdbViewer
 
 from .viewer_projmatch import XmippProjMatchViewer
 
 #AJ
-from .viewer_structure_map_sph import XmippProtStructureMapSphViewer
-from .viewer_volume_deform_sph import XmippVolumeDeformSphViewer
+# from .viewer_structure_map_sph import XmippProtStructureMapSphViewer
+# from .viewer_volume_deform_sph import XmippVolumeDeformSphViewer
 from .viewer_metaprotocol_golden_highres import XmippMetaprotocolGoldenHighResViewer
+from .viewer_deep_align import XmippDeepAlignViewer
 
 # TODO: Import from continuousflex the modules needed to create clusters (soft dependency)
-from .viewer_angular_alignment_sph import XmippAngularAlignmentSphViewer
+# from .viewer_angular_alignment_sph import XmippAngularAlignmentSphViewer
 
diff --git a/xmipp3/viewers/viewer.py b/xmipp3/viewers/viewer.py
index 66a9b0794..0013a9a8a 100644
--- a/xmipp3/viewers/viewer.py
+++ b/xmipp3/viewers/viewer.py
@@ -241,7 +241,7 @@ def _visualize(self, obj, **kwargs):
             if mdFn:
                 micsfn = mdFn.get()
             else:  # happens if protocol is not an xmipp one
-                micsfn = self._getExtraPath(micSet.getName() + '_micrographs.xmd')
+                micsfn = obj._getExtraPath(micSet.getName() + '_micrographs.xmd')
                 writeSetOfMicrographs(micSet, micsfn)
                 inTmpFolder = True
 
diff --git a/xmipp3/viewers/viewer_deep_align.py b/xmipp3/viewers/viewer_deep_align.py
new file mode 100644
index 000000000..cb7369b36
--- /dev/null
+++ b/xmipp3/viewers/viewer_deep_align.py
@@ -0,0 +1,121 @@
+# **************************************************************************
+# *
+# * Authors:  Carlos Oscar Sanchez Sorzano (coss@cnb.csic.es), May 2013
+# *           Slavica Jonic                (jonic@impmc.upmc.fr)
+# * Ported to Scipion:
+# *           J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es), Nov 2014
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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 os.path import exists, join
+
+from pyworkflow.protocol.params import (EnumParam, NumericRangeParam,
+                                        LabelParam, IntParam, FloatParam)
+from pyworkflow.protocol.constants import LEVEL_ADVANCED
+from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO
+from pwem.viewers import (ObjectView, showj, EmProtocolViewer, ChimeraAngDist)
+
+from pwem.emlib import (MDL_SAMPLINGRATE, MDL_ANGLE_ROT, MDL_ANGLE_TILT,
+                        MDL_RESOLUTION_FREQ, MDL_RESOLUTION_FRC, MetaData)
+from xmipp3.protocols.protocol_deep_align import XmippProtDeepAlign
+
+ANGDIST_2DPLOT = 0
+ANGDIST_HEATMAP = 1
+
+
+class XmippDeepAlignViewer(EmProtocolViewer):
+    """ Visualize the output of protocol reconstruct highres """
+    _label = 'viewer deep align'
+    _targets = [XmippProtDeepAlign]
+    _environments = [DESKTOP_TKINTER, WEB_DJANGO]
+
+    def _defineParams(self, form):
+        form.addSection(label='Visualization')
+        group = form.addGroup('Particles')
+        group.addParam('showOutputParticles', LabelParam, default=False, label='Display output particles')
+        group.addParam('showAngDist', EnumParam, choices=['2D plot', 'heatmap'],
+                       display=EnumParam.DISPLAY_HLIST, default=ANGDIST_2DPLOT,
+                       label='Display angular distribution',
+                       help='*2D plot*: display angular distribution as interative 2D in matplotlib.')
+        group.addParam('spheresScale', IntParam, default=-1,
+                       expertLevel=LEVEL_ADVANCED,
+                       label='Spheres size')
+
+    def _getVisualizeDict(self):
+        return {
+            'showOutputParticles': self._showOutputParticles,
+            'showAngDist': self._showAngularDistribution
+        }
+
+    def _showOutputParticles(self, paramName=None):
+        views = []
+        if hasattr(self.protocol, "outputParticles"):
+            obj = self.protocol.outputParticles
+            fn = obj.getFileName()
+            labels = 'id enabled _filename '
+            labels += '_ctfModel._defocusU _ctfModel._defocusV _xmipp_shiftX _xmipp_shiftY _xmipp_angleTilt '
+            labels += ' _xmipp_angleRot  _xmipp_anglePsi _xmipp_maxCC _xmipp_weight '
+            views.append(ObjectView(self._project, obj.strId(), fn,
+                                    viewParams={showj.ORDER: labels,
+                                                showj.VISIBLE: labels,
+                                                showj.MODE: showj.MODE_MD,
+                                                showj.RENDER: '_filename'}))
+        return views
+
+    # ===============================================================================
+    # showAngularDistribution
+    # ===============================================================================
+    def _showAngularDistribution(self, paramName=None):
+        views = []
+        angDist = self._createAngDist2D(heatmap=self.showAngDist == ANGDIST_HEATMAP)
+        if angDist is not None:
+            views.append(angDist)
+        return views
+
+    def _iterAngles(self, fnAngles):
+        md = MetaData(fnAngles)
+        for objId in md:
+            rot = md.getValue(MDL_ANGLE_ROT, objId)
+            tilt = md.getValue(MDL_ANGLE_TILT, objId)
+            yield rot, tilt
+
+    def _createAngDist2D(self, heatmap):
+        fnDir = self.protocol._getExtraPath()
+        fnAngles = join(fnDir, "outConesParticles.xmd")
+        view = None
+        if not exists(fnAngles):
+            fnAngles = join(fnDir,"outputParticles.xmd")
+        if exists(fnAngles):
+            fnAnglesSqLite = join(fnDir, "outConesParticles.sqlite")
+            from pwem.viewers import EmPlotter
+            if not exists(fnAnglesSqLite):
+                from pwem.emlib.metadata import getSize
+                self.createAngDistributionSqlite(fnAnglesSqLite, getSize(fnAngles),
+                                                 itemDataIterator=self._iterAngles(fnAngles))
+            view = EmPlotter(x=1, y=1, windowTitle="Angular distribution")
+            if heatmap:
+                axis = view.plotAngularDistributionFromMd(fnAnglesSqLite, '', histogram=True)
+                view.getFigure().colorbar(axis)
+            else:
+                view.plotAngularDistributionFromMd(fnAnglesSqLite, '')
+        return view
diff --git a/xmipp3/viewers/viewer_ml2d.py b/xmipp3/viewers/viewer_ml2d.py
index 62a04e094..1c009b94a 100644
--- a/xmipp3/viewers/viewer_ml2d.py
+++ b/xmipp3/viewers/viewer_ml2d.py
@@ -80,7 +80,7 @@ def _defineParams(self, form):
                        help='Should approach to zero when convergence.')
         group.addParam('doShowMirror', LabelParam,
                        label="Show mirror fraction for last iteration?",
-                       help='he the mirror fraction of each class in last iteration.')
+                       help='the mirror fraction of each class in last iteration.')
         
     
     def _getVisualizeDict(self):
@@ -132,7 +132,7 @@ def createPlots(protML, selectedPlots):
     lastIter = protML._lastIteration()
     if lastIter == 0:
         return
-    refs = protML._getIterClasses(it=lastIter, block='classes')
+    refs = protML._getIterMdClasses(it=lastIter, block='classes')
 #    if not exists(refs):
 #        return 
 #    blocks = getBlocksInMetaDataFile(refs)
@@ -160,11 +160,11 @@ def doPlot(plotName):
     xplotter = XmippPlotter(x=gridsize[0], y=gridsize[1])
         
     # Create data to plot
-    iters = range(1, lastIter+1)
+    iters = range(0, lastIter+1, 1)
     ll = []
     pmax = []
     for iter in iters:
-        logs = protML._getIterClasses(it=iter, block='info')
+        logs = protML._getIterMdImages(it=iter, block='info')
         md = emlib.MetaData(logs)
         id = md.firstObject()
         ll.append(md.getValue(emlib.MDL_LL, id))
@@ -188,7 +188,7 @@ def doPlot(plotName):
         a.xaxis.set_major_formatter(FormatStrFormatter('%1.0f'))
         a.bar(ind, mirrors, width, color='b')
         a.set_ylim([0, 1.])
-        a.set_xlim([0.8, nrefs + 1])
+        #a.set_xlim([0.3, nrefs + 1])
         
     if doPlot('doShowPmax'):
         a = xplotter.createSubPlot('Probabilities distribution', 'iterations', 'Pmax/Psum') 
@@ -197,7 +197,7 @@ def doPlot(plotName):
     if doPlot('doShowSignalChange'):
         md = emlib.MetaData()
         for iter in iters:
-            fn = protML._getIterClasses(it=iter, block='classes')
+            fn = protML._getIterMdClasses(it=iter, block='classes')
             md2 = emlib.MetaData(fn)
             md2.fillConstant(emlib.MDL_ITER, str(iter))
             md.unionAll(md2)
@@ -210,5 +210,4 @@ def doPlot(plotName):
         xplotter.createSubPlot('Maximum signal change', 'iterations', 'signal change')
         xplotter.plot(iters, signal_change, color='green')
     
-    return [xplotter]
-    
\ No newline at end of file
+    return [xplotter]
\ No newline at end of file
diff --git a/xmipp3/viewers/viewer_resolution3d.py b/xmipp3/viewers/viewer_resolution3d.py
index 5e1033be7..f8243b70b 100644
--- a/xmipp3/viewers/viewer_resolution3d.py
+++ b/xmipp3/viewers/viewer_resolution3d.py
@@ -41,6 +41,8 @@
 from pwem import emlib
 from xmipp3.convert import getImageLocation
 from xmipp3.protocols.protocol_resolution3d import XmippProtResolution3D
+from xmipp3.utils import PathData
+
 from .plotter import XmippPlotter
 from os.path import exists
 
@@ -136,7 +138,6 @@ def _adjustPoints(self, data):
         f.close()
             
     def _loadData(self):
-        from xmipp3.protocols.nma.data import PathData
         data = PathData(dim=2)
         bfactorFile = self.protocol._getPath('bfactor.txt')
         if os.path.exists(bfactorFile):
diff --git a/xmipp3/viewers/viewer_resolution_bfactor.py b/xmipp3/viewers/viewer_resolution_bfactor.py
new file mode 100644
index 000000000..3bb6895da
--- /dev/null
+++ b/xmipp3/viewers/viewer_resolution_bfactor.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# **************************************************************************
+# *
+# * Authors:     J.L. Vilas (jlvilas@cnb.csic.es)
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+from pyworkflow.protocol.params import LabelParam
+from pyworkflow.viewer import ProtocolViewer, DESKTOP_TKINTER
+
+from pwem import emlib
+
+from xmipp3.protocols.protocol_resolution_bfactor import (XmippProtbfactorResolution,
+                                                          FN_METADATA_BFACTOR_RESOLUTION)
+
+
+class XmippBfactorResolutionViewer(ProtocolViewer):
+    """
+    The local resolution and local b-factor should present a correlation.
+    This viewer provides the matching between them per residue.
+    """
+    _label = 'viewer resolution bfactor'
+    _targets = [XmippProtbfactorResolution]
+    _environments = [DESKTOP_TKINTER]
+
+    def __init__(self, *args, **kwargs):
+        ProtocolViewer.__init__(self, *args, **kwargs)
+
+    def _defineParams(self, form):
+        form.addSection(label='Visualization')
+
+        line = form.addLine("Color scale options:",
+                            help="Options to define the color scale limits and color set")
+
+        line.addParam('highest', FloatParam, default=self.maxNormRes,
+                      label="Highest",
+                      help="Highest value for the scale")
+
+        line.addParam('lowest', FloatParam, default=self.minNormRes,
+                      label="Lowest",
+                      help="lowest value of the scale.")
+
+        line.addParam('doShowColorBands', LabelParam,
+                      label="Show bfactor-local resolution comparison")
+
+    def _getVisualizeDict(self):
+        return {'doShowColorBands': self._showColorBands,
+                }
+
+    def _showColorBands(self, param=None):
+        """
+        The local resolution and local b-factor are represented as color bands.
+        """
+        md = emlib.MetaData()
+        md.read(self.protocol._getExtraPath(FN_METADATA_BFACTOR_RESOLUTION))
+        lr = []
+        bf = []
+        r = []
+
+        for idx in md:
+            lr.append(md.getValue(emlib.MDL_RESOLUTION_LOCAL_RESIDUE, idx))
+            bf.append(md.getValue(emlib.MDL_BFACTOR, idx))
+            r.append(md.getValue(emlib.MDL_RESIDUE, idx))
+
+        lr = np.array(lr)
+        bf = np.array(bf)
+        r = np.array(r)
+        plt.subplot(211)
+        #The magic numbers of 0 and 40 define the size of the vertical bands, they provide good visualization aspect
+        plt.imshow(lr.reshape(1, len(lr)), cmap=plt.cm.viridis, extent=[r[0], r[-1], 0, 40])
+        plt.xlabel('Residue')
+        plt.title('Normalized Local Resolution')
+        plt.colorbar()
+
+        plt.subplot(212)
+        plt.imshow(bf.reshape(1, len(bf)), cmap=plt.cm.viridis, extent=[r[0], r[-1], 0, 40])
+        plt.xlabel('Residue')
+        plt.title('B-factor')
+        plt.colorbar()
+
+        plt.show()
+
diff --git a/xmipp3/viewers/viewer_resolution_deepres.py b/xmipp3/viewers/viewer_resolution_deepres.py
index aec5cf893..ba921b1e8 100644
--- a/xmipp3/viewers/viewer_resolution_deepres.py
+++ b/xmipp3/viewers/viewer_resolution_deepres.py
@@ -176,6 +176,13 @@ def _plotHistogram(self, param=None):
 
         return [plotter]
 
+    def _getStepColors(self, minRes, maxRes, numberOfColors=13):
+        inter = (maxRes - minRes) / (numberOfColors - 1)
+        rangeList = []
+        for step in range(0, numberOfColors):
+            rangeList.append(round(minRes + step * inter, 2))
+        return rangeList
+
     def _getAxis(self):
         return self.getEnumText('sliceAxis')
 
diff --git a/xmipp3/viewers/viewer_resolution_directional.py b/xmipp3/viewers/viewer_resolution_directional.py
index 76f291f97..80f5d963d 100644
--- a/xmipp3/viewers/viewer_resolution_directional.py
+++ b/xmipp3/viewers/viewer_resolution_directional.py
@@ -143,31 +143,13 @@ def _defineParams(self, form):
         
         groupRadAzim.addParam('doShowAzimuthalColorSlices', LabelParam,
                label="Show azimuthal resolution colored slices")
-        
-        groupRadAzim.addParam('doShowRadialHistogram', LabelParam,
-               label="Show radial resolution histogram")
-        
-        groupRadAzim.addParam('doShowAzimuthalHistogram', LabelParam,
-               label="Show azimuthal resolution histogram")
-        
-        groupRadAzim.addParam('doShowHighestResolutionMap', LabelParam,
-               label="Show highest Resolution Map")
-        
-        groupRadAzim.addParam('doShowLowestResolutionMap', LabelParam,
-               label="Show lowest Resolution Map")        
-        
-#        groupRadAzim.addParam('doshowAnisotropyResolution', LabelParam,
-#               label="Anisotropy and resolution")  
-        
+
         groupRadAzim.addParam('doShowDirectionsHistogram', LabelParam,
                label="Show directions histogram")
         
         groupRadAzim.addParam('doShowDirectionsSphere', LabelParam,
                label="Show directions sphere")
         
-#        groupRadAzim.addParam('doshowZscoreMap', LabelParam,
-#               label="Show zscore map")
-        
         groupRadAzim.addParam('doShowRadialAverages', LabelParam,
                label="Show radial averages")
 
@@ -195,16 +177,10 @@ def _getVisualizeDict(self):
                 'doShowDoAColorPol': self._showHalfInterQuartile,
                 'doShowRadialColorSlices': self._showRadialColorSlices,
                 'doShowAzimuthalColorSlices': self._showAzimuthalColorSlices,
-                'doShowHighestResolutionMap': self._showHighestResolutionColorSlices,
-                'doShowLowestResolutionMap': self._showLowestResolutionColorSlices,
-                #'doShowChimera': self._showChimera,
-                #'doShowDoAHistogram': self._plotHistogramDoA,
-                'doShowRadialHistogram': self._plotHistogramRadial,
-                'doShowAzimuthalHistogram': self._plotHistogramAzimuthal,
+                #'doShowRadialHistogram': self._plotHistogramRadial,
+                #'doShowAzimuthalHistogram': self._plotHistogramAzimuthal,
                 'doShowDirectionsHistogram': self._plotHistogramDirections,
                 'doShowDirectionsSphere': self._show2DDistribution,
-                #'doshowAnisotropyResolution': self._showAnisotropyResolution,
-                #'doshowZscoreMap': self._showZscoreMap,
                 'doShowRadialAverages': self._showRadialAverages
      }
 
@@ -220,22 +196,10 @@ def _showColorMeanResADR(self, param=None):
         
     def _showRadialColorSlices(self, param=None):
         self._showColorSlices(OUTPUT_RADIAL_FILE, False, 'Radial Resolution', -1, -1)
-        
-#    def _showZscoreMap(self, param=None):
-#        self._showColorSlices(OUTPUT_ZSCOREMAP_FILE, True, 'Zscore map', 0, 5)
-        
-    def _showHighestResolutionColorSlices(self, param=None):
-        self._showColorSlices(OUTPUT_RESOLUTION_HIGHEST_FILE, False, 'Highest Resolution', -1, -1)
-        
-    def _showLowestResolutionColorSlices(self, param=None):
-        self._showColorSlices(OUTPUT_RESOLUTION_LOWEST_FILE, False, 'Lowest Resolution', -1, -1)
-        
+
     def _showAzimuthalColorSlices(self, param=None):
         self._showColorSlices(OUTPUT_AZIMUHTAL_FILE, False, 'Tangential Resolution', -1, -1)
-        
-#    def _showAnisotropyResolution(self, param=None):
-#        self.plotAnisotropyResolution(self.protocol._getExtraPath('anires.xmd'))
-        
+
     def _show2DDistribution(self, param=None):
         self._createAngDist2D(self.protocol._getExtraPath('hist_prefdir.xmd'))
         
@@ -323,33 +287,18 @@ def _showColorSlices(self, fileName, setrangelimits, titleFigure, lowlim, highli
         cbar = fig.colorbar(im, cax=cax)
         cbar.ax.invert_yaxis()
 
-        return plt.show(fig)
+        return plt.show()
 
     def _plotHistogramDoA(self, param=None):
         self._plotHistogram('hist_DoA.xmd', 'DoA', 'DoA')
-        
-    def _plotHistogramRadial(self, param=None):
-        self._plotHistogram('hist_radial.xmd', 'Azimuthal Resolution', 'Resolution')
-        
-    def _plotHistogramAzimuthal(self, param=None):
-        self._plotHistogram('hist_azimuthal.xmd', 'Azimuthal Resolution', 'Resolution')
-        
+
     def _plotHistogramDirections(self, param=None):
         self._plotHistogram('hist_prefdir.xmd', 'Highest Resolution per Direction', 'Direction')
         
     def _createAngDist2D(self, path):
         view = XmippPlotter(x=1, y=1, mainTitle="Highest Resolution per Direction", windowTitle="Angular distribution")
         
-        #md = MetaData(path)
-        #wmax=-1
-        #for objId in md:
-        #    w=md.getValue(MDL_WEIGHT,objId)
-        #    w=sqrt(w)
-        #    wmax=max(wmax,w)
-        #    md.setValue(MDL_WEIGHT,w,objId)
-        #md.write(path)
-        #print(w)
-        view.plotAngularDistributionFromMd(path, 'directional resolution distribution',  min_w=0)
+        view.plotAngularDistributionFromMd(path, 'directional resolution distribution')#,  min_w=0)
         return view.show()
 
     def _plotHistogram(self, fnhist, titlename, xname):
diff --git a/xmipp3/viewers/viewer_resolution_fso.py b/xmipp3/viewers/viewer_resolution_fso.py
index 6c003051a..0d05b3bb1 100644
--- a/xmipp3/viewers/viewer_resolution_fso.py
+++ b/xmipp3/viewers/viewer_resolution_fso.py
@@ -25,238 +25,209 @@
 # **************************************************************************
 
 from pyworkflow.gui.plotter import Plotter
-from xmipp3.viewers.plotter import XmippPlotter
-from pyworkflow.protocol.params import LabelParam, StringParam, EnumParam, IntParam
+from pyworkflow.protocol.params import LabelParam, StringParam, EnumParam, IntParam, LEVEL_ADVANCED
 from pyworkflow.viewer import ProtocolViewer, DESKTOP_TKINTER
-from pwem.viewers import ChimeraView, DataView, LocalResolutionViewer
-from matplotlib.ticker import FuncFormatter
-from matplotlib.pyplot import hlines
-from xmipp3.protocols.protocol_resolution_fso import \
-        XmippProtFSO, OUTPUT_3DFSC, OUTPUT_SPHERE, OUTPUT_DIRECTIONAL_FILTER
+from pyworkflow.utils import getExt, removeExt
+
 from pwem.emlib.metadata import MetaData
-from xmippLib import *
+from pwem.wizards import ColorScaleWizardBase
 from pwem.emlib.image import ImageHandler
-import numpy as np
-import matplotlib.pyplot as plt
-from math import sqrt
-from matplotlib import cm
-import matplotlib.colors as mcolors
-from pyworkflow.utils import getExt, removeExt
-from os.path import abspath, exists
-from collections import OrderedDict
-import xmippLib
+from pwem.viewers import ChimeraView, DataView, LocalResolutionViewer
+from pwem import emlib
 
-# Color maps
-COLOR_JET = 0
-COLOR_TERRAIN = 1
-COLOR_GIST_EARTH = 2
-COLOR_GIST_NCAR = 3
-COLOR_GNU_PLOT = 4
-COLOR_GNU_PLOT2 = 5
-COLOR_OTHER = 6
 
-COLOR_CHOICES = OrderedDict() #[-1]*(OP_RESET+1)
+import numpy as np
 
-COLOR_CHOICES[COLOR_JET]  = 'jet'
-COLOR_CHOICES[COLOR_TERRAIN] = 'terrain'
-COLOR_CHOICES[COLOR_GIST_EARTH] = 'gist_earth'
-COLOR_CHOICES[COLOR_GIST_NCAR] = 'gist_ncar'
-COLOR_CHOICES[COLOR_GNU_PLOT] = 'gnuplot'
-COLOR_CHOICES[COLOR_GNU_PLOT2] = 'gnuplot2'
-COLOR_CHOICES[COLOR_OTHER] = 'other'
+import matplotlib.pyplot as plt
+from matplotlib import cm
+from matplotlib.ticker import FuncFormatter
 
-binaryCondition = ('(colorMap == %d) ' % (COLOR_OTHER))
+from xmipp3.protocols.protocol_resolution_fso import \
+    XmippProtFSO, OUTPUT_3DFSC, OUTPUT_DIRECTIONAL_FILTER
+from xmipp3.viewers.plotter import XmippPlotter
+import xmippLib
 
-#Axis code
+# Axis code
 AX_X = 0
 AX_Y = 1
 AX_Z = 2
 
-class XmippProtFSOViewer(ProtocolViewer):
+
+class XmippProtFSOViewer(LocalResolutionViewer):
     """
     Visualization tools for the FSO, FSC, and 3DFSC.
     
     """
     _label = 'viewer FSO'
-    _targets = [XmippProtFSO]      
+    _targets = [XmippProtFSO]
     _environments = [DESKTOP_TKINTER]
-    
+
     @staticmethod
     def getColorMapChoices():
         return plt.colormaps()
-   
-    def __init__(self, *args, **kwargs):
-        ProtocolViewer.__init__(self, *args, **kwargs)
-
 
     def _defineParams(self, form):
         form.addSection(label='Visualization')
-        
-        form.addParam('doShowOriginalVolumeSlices', LabelParam,
-              label="Original Half Maps slices")
-
-        form.addParam('doShow3DFSC', LabelParam, label="3DFSC map")
-
-        form.addParam('doShowDirectionalFilter', LabelParam, 
-              label="Directionally filtered map")
-        
-        form.addParam('doShowFSC', LabelParam, label="Global FSC")
 
-        form.addParam('doShowDirectionalResolution', LabelParam, 
-              label="Show Directional Resolution on sphere")
-
-        form.addParam('doCrossValidation', LabelParam, 
-              label="Show Cross validation curve")
+        form.addParam('doShowOriginalVolumeSlices', LabelParam,
+                      label="Original Half Maps slices")
 
-        groupDirFSC = form.addGroup('FSC Anisotropy')
-        
-        groupDirFSC.addParam('doShow3DFSCcolorSlices', LabelParam,
-               label="Show 3DFSC Color slices")
-        
-        groupDirFSC.addParam('doShowChimera3DFSC', LabelParam,
-               label="Show 3DFSC in Chimera")
-        
-        groupDirFSC.addParam('doShowDirectionalFSCCurve', LabelParam,
-               label="Show directional FSC curve")
+        if self.protocol.estimate3DFSC:
+            form.addParam('doShowDirectionalFilter', LabelParam,
+                          label="Directionally filtered map")
 
-        groupDirFSC.addParam('fscNumber', IntParam, default=0,
-               label='Show FSC direction')
+        groupDirFSC = form.addGroup('FSO and Resolution Analysis')
 
         groupDirFSC.addParam('doShowAnisotropyCurve', LabelParam,
-               label="Show FSO curve")
-
-        group = form.addGroup('Choose a Color Map')
-
-        group.addParam('colorMap', EnumParam, choices=list(COLOR_CHOICES.values()),
-               default=COLOR_JET, label='Color map', 
-               help='Select the color map to be applied'
-                    'http://matplotlib.org/1.3.0/examples/color/colormaps_reference.html.')
-
-        group.addParam('otherColorMap', StringParam, default='jet',
-                      condition = binaryCondition, label='Customized Color map',
-                      help='Name of a color map to apply to be applied. Valid names can be found at '
-                            'http://matplotlib.org/1.3.0/examples/color/colormaps_reference.html')
-
-        group.addParam('sliceAxis', EnumParam, default=AX_Z,
-                       choices=['x', 'y', 'z'],
-                       display=EnumParam.DISPLAY_HLIST,
-                       label='Slice axis')
-        group.addParam('doShowOneColorslice', LabelParam,  
-                      label='Show selected slice')
-        group.addParam('sliceNumber', IntParam, default=-1,
-               label='Show slice number')
-
-        
+                             label="Show FSO curve")
+
+        groupDirFSC.addParam('doShowFSC', LabelParam, label="Global FSC")
+
+        if self.protocol.estimate3DFSC:
+            groupDirFSC.addParam('doShow3DFSC', LabelParam, label="3DFSC map")
+            groupDirFSC.addParam('doShow3DFSCcolorSlices', LabelParam,
+                                 label="Show 3DFSC Color slices")
+
+        groupDirFSC.addParam('doShowDirectionalResolution', LabelParam,
+                             label="Show Directional Resolution on sphere")
+
+        if self.protocol.estimate3DFSC:
+            group = form.addGroup('Choose a Color Map')
+
+            group.addParam('sliceAxis', EnumParam, default=AX_Z,
+                           choices=['x', 'y', 'z'],
+                           display=EnumParam.DISPLAY_HLIST,
+                           label='Slice axis')
+            group.addParam('doShowOneColorslice', LabelParam,
+                           expertLevel=LEVEL_ADVANCED,
+                           label='Show selected slice')
+            group.addParam('sliceNumber', IntParam, default=-1,
+                           expertLevel=LEVEL_ADVANCED,
+                           label='Show slice number')
+            ColorScaleWizardBase.defineColorScaleParams(group, defaultLowest=0.0,
+                                                        defaultHighest=1.0)
+
     def _getVisualizeDict(self):
         self.protocol._createFilenameTemplates()
-        return {'doShowOriginalVolumeSlices': self._showOriginalVolumeSlices,
-                'doShow3DFSC': self._show3DFSC,
-		'doShowFSC': self._showFSCCurve,
-		'doShowDirectionalResolution': self._showDirectionalResolution,
-		'doCrossValidation': self._showCrossValidationCurve,
-                'doShow3DFSCcolorSlices': self._show3DFSCcolorSlices,
-		'doShowOneColorslice': self._showOneColorslice,
-                'doShowChimera3DFSC': self._showChimera3DFSC,
-                'doShowDirectionalFSCCurve': self._showDirectionalFSCCurve,
-		'doShowAnisotropyCurve': self._showAnisotropyCurve,
-		'doShowDirectionalFilter': self._showDirectionalFilter,
-     }
-
+        if self.protocol.estimate3DFSC:
+            return {'doShowOriginalVolumeSlices': self._showOriginalVolumeSlices,
+                    'doShow3DFSC': self._show3DFSC,
+                    'doShowFSC': self._showFSCCurve,
+                    'doShow3DFSCcolorSlices': self._show3DFSCcolorSlices,
+                    'doShowOneColorslice': self._showOneColorslice,
+                    'doShowAnisotropyCurve': self._showAnisotropyCurve,
+                    'doShowDirectionalFilter': self._showDirectionalFilter,
+                    'doShowDirectionalResolution': self._showDirectionalResolution,
+                    }
+        else:
+            return {'doShowOriginalVolumeSlices': self._showOriginalVolumeSlices,
+                    'doShowFSC': self._showFSCCurve,
+                    'doShowAnisotropyCurve': self._showAnisotropyCurve,
+                    'doShowDirectionalResolution': self._showDirectionalResolution,
+                    }
 
     def _showOriginalVolumeSlices(self, param=None):
+        """
+        This function opens the two half maps to visualize the slices 
+        """
         cm = DataView(self.protocol.half1.get().getFileName())
         cm2 = DataView(self.protocol.half2.get().getFileName())
         return [cm, cm2]
 
     def _show3DFSC(self, param=None):
+        """
+        This function opens the slices of the 3DFSC
+        """
         cm = DataView(self.protocol._getExtraPath(OUTPUT_3DFSC))
         return [cm]
 
+
+
     def _showFSCCurve(self, paramName=None):
-        fnmd = self.protocol._getExtraPath('fsc/'+'GlobalFSC.xmd')
+        """
+        It shows the FSC curve in terms of the resolution
+        The horizontal axis is linear in this plot. Note
+        That this is not the normal representation of hte FSC
+        """
+        fnmd = self.protocol._getExtraPath('GlobalFSC.xmd')
         title = 'Global FSC'
         xTitle = 'Resolution (1/A)'
         yTitle = 'FSC (a.u.)'
-        mdLabelX = xmippLib.MDL_RESOLUTION_FREQ
-        mdLabelY = xmippLib.MDL_RESOLUTION_FRC
+        mdLabelX = emlib.MDL_RESOLUTION_FREQ
+        mdLabelY = emlib.MDL_RESOLUTION_FRC
         self._plotCurveFSC(fnmd, title, xTitle, yTitle, mdLabelX, mdLabelY)
 
-    def _showCrossValidationCurve(self, paramName=None):
-        fnmd = self.protocol._getExtraPath('fsc/'+'crossValidation.xmd')
-        title = 'Cross Validation'
-        xTitle = 'Angle (degrees)'
-        yTitle = 'Score'
-        mdLabelX = xmippLib.MDL_ANGLE_Y
-        mdLabelY = xmippLib.MDL_SUM
-
-        md = xmippLib.MetaData(fnmd)
-        xplotter = XmippPlotter(figure=None)
-        xplotter.plot_title_fontsize = 11
-	
-        a = xplotter.createSubPlot(title, xTitle, yTitle, 1, 1)
-        xplotter.plotMdFile(md, mdLabelX, mdLabelY, 'g')
-        a.grid(True)
-	
-        return plt.show(xplotter)
-
     def _show3DFSCcolorSlices(self, param=None):
-        self._showColorSlices(OUTPUT_3DFSC, 1, '3DFSC Color Slices', 1, 1)
-
-    def _showChimera3DFSC(self, param=None):
-        fnmap = abspath(self.protocol._getFileName(OUTPUT_3DFSC)) #'extra/'+
-        fnsphere = abspath(self.protocol._getFileName(OUTPUT_SPHERE)) #'extra/fsc/'
-        self.createChimeraScript(fnmap, fnsphere)
-        cmdFile = self.protocol._getExtraPath('chimeraVisualization.cmd')
-        view = ChimeraView(cmdFile)
-        return [view]
-
-    def _showDirectionalFSCCurve(self, paramName=None):
-        fnmd = self.protocol._getExtraPath('fsc/'+'fscDirection_%i.xmd'% self.fscNumber.get())
-        title = 'Directional FSC'
-        xTitle = 'Resolution (1/A)'
-        yTitle = 'FSC (a.u.)'
-        mdLabelX = xmippLib.MDL_RESOLUTION_FREQ
-        mdLabelY = xmippLib.MDL_RESOLUTION_FRC
-        self._plotCurveFSC(fnmd, title, xTitle, yTitle, mdLabelX, mdLabelY)
+        """
+        It opens 4 colores slices of the 3DFSC
+        """
+        errors = []
+        if self.protocol.estimate3DFSC:
+            img = ImageHandler().read(self.protocol._getExtraPath(OUTPUT_3DFSC))
+            imgData = img.getData()
+
+            xplotter = XmippPlotter(x=2, y=2, mainTitle="3DFSC Color Slices"
+                                                        "along %s-axis."
+                                                        % self._getAxis())
+            # The slices to be shown are close to the center. Volume size is divided in
+            # 9 segments, the fouth central ones are selected i.e. 3,4,5,6
+            for i in range(3, 7):
+                sliceNumber = self.getSlice(i, imgData)
+                a = xplotter.createSubPlot("Slice %s" % (sliceNumber + 1), '', '')
+                matrix = self.getSliceImage(imgData, sliceNumber, self._getAxis())
+                plot = xplotter.plotMatrix(a, matrix, 0, 1,
+                                           cmap=self.getColorMap(),
+                                           interpolation="nearest")
+            xplotter.getColorBar(plot)
+            return [xplotter]
+        else:
+            errors.append("The 3dFSC estimation of the 3dFSC was not selected"
+                          "in the protocol form.")
+            return errors
 
     def _showAnisotropyCurve(self, paramName=None):
+        """
+        It shows the FSO curve in terms of the resolution
+        The horizontal axis is linear in this plot.
+        """
         fnmd = self.protocol._getExtraPath('fso.xmd')
         title = 'Anisotropy Curve'
         xTitle = 'Resolution (1/A)'
         yTitle = 'Anisotropy (a.u.)'
-        mdLabelX = xmippLib.MDL_RESOLUTION_FREQ
-        mdLabelY = xmippLib.MDL_RESOLUTION_FRC
+        mdLabelX = emlib.MDL_RESOLUTION_FREQ
+        mdLabelY = emlib.MDL_RESOLUTION_FSO
         self._plotCurveAnisotropy(fnmd, title, xTitle, yTitle, mdLabelX, mdLabelY)
 
-
     def _showDirectionalFilter(self, param=None):
+        """
+        The directionally filtered map using the 3DFSC as low pass filter
+        """
         cm = DataView(self.protocol._getExtraPath(OUTPUT_DIRECTIONAL_FILTER))
-        return [cm, cm2]
+        return [cm]
 
     def _showOneColorslice(self, param=None):
-        img = ImageHandler().read(self.protocol._getExtraPath(OUTPUT_3DFSC))
+        """
+        It shows a single colored slice of the 3DFSC
+        """
+        imageFile = self.protocol._getExtraPath(OUTPUT_3DFSC)
+        img = ImageHandler().read(imageFile)
         imgData = img.getData()
-        #imgData2 = np.ma.masked_where(imgData < 0.001, imgData, copy=True)
-        max_Res = np.nanmax(imgData)
-        min_Res = np.nanmin(imgData)
-	#imageFile = self.protocol._getExtraPath(OUTPUT_3DFSC)
-        #imgData, min_Res, max_Res = self.getImgData(imageFile)
 
         xplotter = XmippPlotter(x=1, y=1, mainTitle="3DFSC Slices "
-                                                     "along %s-axis."
-                                                     %self._getAxis())
+                                                    "along %s-axis."
+                                                    % self._getAxis())
         sliceNumber = self.sliceNumber.get()
         if sliceNumber < 0:
-            x ,_ ,_ ,_ = ImageHandler().getDimensions(imageFile)
-            sliceNumber = x/2
+            x, _, _, _ = ImageHandler().getDimensions(imageFile)
+            sliceNumber = int(x / 2)
         else:
             sliceNumber -= 1
-        #sliceNumber has no sense to start in zero 
-        a = xplotter.createSubPlot("Slice %s" % (sliceNumber+1), '', '')
+        # sliceNumber has no sense to start in zero
+        a = xplotter.createSubPlot("Slice %s" % (sliceNumber + 1), '', '')
         matrix = self.getSliceImage(imgData, sliceNumber, self._getAxis())
-        plot = xplotter.plotMatrix(a, matrix, min_Res, max_Res,
-                                       cmap=self.getColorMap(),
-                                       interpolation="nearest")
+        plot = xplotter.plotMatrix(a, matrix, 0, 1,
+                                   cmap=self.getColorMap(),
+                                   interpolation="nearest")
         xplotter.getColorBar(plot)
 
         return [plt.show(xplotter)]
@@ -269,205 +240,150 @@ def _formatFreq(self, value, pos):
         return "1/%0.2f" % inv
 
     def _plotCurveFSC(self, fnmd, title, xTitle, yTitle, mdLabelX, mdLabelY):
+        """
+        This function is called by _showFSCCurve.
+        It shows the FSC curve in terms of the resolution
+        That this is not the normal representation of the FSC
+        """
         md = xmippLib.MetaData(fnmd)
         xplotter = XmippPlotter(figure=None)
         xplotter.plot_title_fontsize = 11
-	
+
         a = xplotter.createSubPlot(title, xTitle, yTitle, 1, 1)
         xplotter.plotMdFile(md, mdLabelX, mdLabelY, 'g')
 
         a.xaxis.set_major_formatter(FuncFormatter(self._formatFreq))
-        xx, yy = self._prepareDataForPlot( md, mdLabelX, mdLabelY)
-        a.hlines(0.143, xx[0], xx[-1], colors = 'k', linestyles = 'dashed')
+        xx, yy = self._prepareDataForPlot(md, mdLabelX, mdLabelY)
+        a.hlines(0.143, xx[0], xx[-1], colors='k', linestyles='dashed')
         a.grid(True)
-	
-        return plt.show(xplotter)
+
+        return plt.show()
+
+    def interpolRes(self, thr, x, y):
+        """
+        This function is called by _showAnisotropyCurve.
+        It provides the cut point of the curve defined
+        by the points (x,y) with a threshold thr.
+        The flag okToPlot shows if there is no intersection points
+        """
+        idx = np.arange(0, len(x))
+        aux = np.array(y) <= thr
+        idx_x = idx[aux]
+        okToPlot = True
+        resInterp = []
+        if not idx_x.any():
+            okToPlot = False
+        else:
+            if len(idx_x) > 1:
+                idx_2 = idx_x[0]
+                idx_1 = idx_2 - 1
+                if idx_1 < 0:
+                    idx_2 = idx_x[1]
+                    idx_1 = idx_2 - 1
+                y2 = x[idx_2]
+                y1 = x[idx_1]
+                x2 = y[idx_2]
+                x1 = y[idx_1]
+                slope = (y2 - y1) / (x2 - x1)
+                ny = y2 - slope * x2
+                resInterp = 1.0 / (slope * thr + ny)
+            else:
+                okToPlot = False
+
+        return resInterp, okToPlot
 
     def _plotCurveAnisotropy(self, fnmd, title, xTitle, yTitle, mdLabelX, mdLabelY):
+        """
+        This function is called by _showAnisotropyCurve
+        It shows the FSO curve in terms of the resolution
+        The horizontal axis is linear in this plot.
+        """
         md = xmippLib.MetaData(fnmd)
         xplotter = XmippPlotter(figure=None)
         xplotter.plot_title_fontsize = 11
-	
+
         a = xplotter.createSubPlot(title, xTitle, yTitle, 1, 1)
         xplotter.plotMdFile(md, mdLabelX, mdLabelY, 'g')
 
-        a.xaxis.set_major_formatter(FuncFormatter(self._formatFreq))
-        xx, yy = self._prepareDataForPlot( md, mdLabelX, mdLabelY)
-        a.hlines(0.9, xx[0], xx[-1], colors = 'k', linestyles = 'dashed')
-        a.hlines(0.5, xx[0], xx[-1], colors = 'k', linestyles = 'dashed')
-        a.hlines(0.1, xx[0], xx[-1], colors = 'k', linestyles = 'dashed')
-        a.grid(True)
-	
-        return plt.show(xplotter)
+        xx, yy = self._prepareDataForPlot(md, mdLabelX, mdLabelY)
+        from matplotlib.ticker import FuncFormatter
+        a.axes.xaxis.set_major_formatter(FuncFormatter(self._formatFreq))
+        a.axes.set_ylim([-0.1, 1.1])
+        a.axes.plot(xx, yy, 'g')
+        a.axes.set_xlabel('Resolution (A)')
+        a.axes.set_ylabel('FSO (a.u)')
+        hthresholds = [0.1, 0.5, 0.9]
+        a.axes.hlines(hthresholds, xx[0], xx[-1], colors='k', linestyles='dashed')
+        a.axes.grid(True)
+        textstr = ''
+
+        res_01, okToPlot_01 = self.interpolRes(0.1, xx, yy)
+        res_05, okToPlot_05 = self.interpolRes(0.5, xx, yy)
+        res_09, okToPlot_09 = self.interpolRes(0.9, xx, yy)
 
+        if (okToPlot_01 and okToPlot_05 and okToPlot_09):
+            textstr = str(0.9) + ' --> ' + str("{:.2f}".format(res_09)) + 'A\n' + str(0.5) + ' --> ' + str(
+                "{:.2f}".format(res_05)) + 'A\n' + str(0.1) + ' --> ' + str("{:.2f}".format(res_01)) + 'A'
+            a.axes.axvspan(1.0 / res_09, 1.0 / res_01, alpha=0.3, color='green')
+
+            props = dict(boxstyle='round', facecolor='white')
+            a.axes.text(0.0, 0.0, textstr, fontsize=12, ha="left", va="bottom", bbox=props)
+
+        return plt.show()
 
     def _prepareDataForPlot(self, md, mdLabelX, mdLabelY):
         """ plot metadata columns mdLabelX and mdLabelY
             if nbins is in args then and histogram over y data is made
         """
-        if mdLabelX:
-            xx = []
-        else:
-            xx = range(1, md.size() + 1)
-        yy = []
-        for objId in md:
-            if mdLabelX:
-                xx.append(md.getValue(mdLabelX, objId))
-            yy.append(md.getValue(mdLabelY, objId))
+        xx = md.getColumnValues(mdLabelX)
+        yy = md.getColumnValues(mdLabelY)
         return xx, yy
 
+    def _showDirectionalResolution(self, zparam=None):
+        """
+        This function shows the angular distribution of the resolution
+        """
+        fnmd = self.protocol._getExtraPath('Resolution_Distribution.xmd')
+        self._showPolarPlot(fnmd)
 
-    def _showDirectionalResolution(self,zparam=None):
-        fnmd = self.protocol._getExtraPath('fsc/Resolution_Distribution.xmd')
-        titleName = 'Directional FSC distribution'
-        self._showPolarPlotNew(fnmd, titleName, 'ResDist')
+    def _showPolarPlot(self, fnmd):
+        """
+        It is called by _showDirectionalResolution
+        This function shows the angular distribution of the resolution
+        """
+        md = emlib.MetaData(fnmd)
 
+        radius = md.getColumnValues(emlib.MDL_ANGLE_ROT)
+        azimuth = md.getColumnValues(emlib.MDL_ANGLE_TILT)
+        counts = md.getColumnValues(emlib.MDL_RESOLUTION_FRC)
 
-    def _showPolarPlotNew(self, fnmd, titleName, kind):
-        pass
+        # define binning
+        azimuths = np.radians(np.linspace(0, 360, 360))
+        zeniths = np.arange(0, 91, 1)
 
-    def _showColorSlices(self, fileName, setrangelimits, titleFigure, lowlim, highlim):
-        
-        img = ImageHandler().read(self.protocol._getExtraPath(fileName))
-        imgData = img.getData()
-        #imgData2 = np.ma.masked_where(imgData < 0.001, imgData, copy=True)
-        max_Res = np.nanmax(imgData)
-        min_Res = np.nanmin(imgData)
-        fig, im = self._plotVolumeSlices(titleFigure, imgData,
-                                         min_Res, max_Res, self.getColorMap(), dataAxis=self._getAxis())
-        cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])
-        cbar = fig.colorbar(im, cax=cax)
-        cbar.ax.invert_yaxis()
-
-        return plt.show(fig)
-        
-    def _createAngDist2D(self, path):
-        view = XmippPlotter(x=1, y=1, mainTitle="Highest Resolution per Direction", windowTitle="Angular distribution")
-        return view.plotAngularDistributionFromMd(path, 'directional resolution distribution',  min_w=0)
-    
-    def _getAxis(self):
-        return self.getEnumText('sliceAxis')
+        r, theta = np.meshgrid(zeniths, azimuths)
 
+        values = np.zeros((len(azimuths), len(zeniths)))
 
-    def _plotVolumeSlices(self, title, volumeData, vminData, vmaxData, cmap, **kwargs):
-        """ Helper function to create plots of volumes slices. 
-        Params:
-            title: string that will be used as title for the figure.
-            volumeData: numpy array representing a volume from where to take the slices.
-            cmap: color map to represent the slices.
-        """
-        # Get some customization parameters, by providing with default values
-        titleFontSize = kwargs.get('titleFontSize', 14)
-        titleColor = kwargs.get('titleColor','#104E8B')
-        sliceFontSize = kwargs.get('sliceFontSize', 10)
-        sliceColor = kwargs.get('sliceColor', '#104E8B')
-        size = kwargs.get('n', volumeData.shape[0])
-        origSize = kwargs.get('orig_n', size)
-        dataAxis = kwargs.get('dataAxis', 'z')
-    
-        f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
-        f.suptitle(title, fontsize=titleFontSize, color=titleColor, fontweight='bold')
-    
-        def getSlice(slice):
-            if dataAxis == 'y':
-                return volumeData[:,slice,:]
-            elif dataAxis == 'x':
-                return volumeData[:,:,slice]
-            else:
-                return volumeData[slice,:,:]
-    
-        def showSlice(ax, index):
-            sliceTitle = 'Slice %s' % int(index*size/9)
-            slice = int(index*origSize/9)
-            ax.set_title(sliceTitle, fontsize=sliceFontSize, color=sliceColor)
-            return ax.imshow(getSlice(slice), vmin=vminData, vmax=vmaxData,
-                             cmap=self.getColorMap(), interpolation="nearest")
-        
-        im = showSlice(ax1, 3)
-        showSlice(ax2, 4)
-        showSlice(ax3, 5)
-        showSlice(ax4, 6)
-        
-        return f, im 
-
-    def _showChimera(self,  param=None):
-        self.createChimeraScript(OUTPUT_DOA_FILE_CHIMERA, CHIMERA_CMD_DOA, CHIMERA_ELLIP)
-        cmdFile = self.protocol._getPath('chimera_DoA.cmd')
-        view = ChimeraView(cmdFile)
-        return [view]
-    
+        for i in range(0, len(azimuth)):
+            values[int(radius[i]), int(azimuth[i])] = counts[i]
 
-    def numberOfColors(self, min_Res, max_Res, numberOfColors):
-        inter = (max_Res - min_Res)/(numberOfColors-1)
-        colors_labels = ()
-        for step in range(0,numberOfColors):
-            colors_labels += round(min_Res + step*inter,2),
-        return colors_labels
-
-
-    def createChimeraScript(self, map1, map2):
-        scriptFile = self.protocol._getExtraPath('chimeraVisualization.cmd')
-        fhCmd = open(scriptFile, 'w')
-        min_Val = 0.0
-        max_Val = 1.0
-
-        numberOfColors = 21
-        colors_labels = self.numberOfColors(min_Val, max_Val, numberOfColors)
-        colorList = self.colorMapToColorList(colors_labels, self.getColorMap())
-
-        fhCmd.write("open %s\n" % map1)
-        fhCmd.write("open %s\n" % map2)
-        
-        smprt = self.protocol.half1.get().getSamplingRate()
-        fhCmd.write("volume #0 voxelSize %s\n" % (str(smprt)))
-        fhCmd.write("volume #1 voxelSize %s\n" % (str(smprt)))
-        fhCmd.write("volume #1 style mesh\n")
-        fhCmd.write("vol #1 hide\n")
-        
-        scolorStr = '%s,%s:' * numberOfColors
-        scolorStr = scolorStr[:-1]
-
-        line = ("scolor #0 volume #0 perPixel false cmap " + scolorStr + "\n") % colorList
-        fhCmd.write(line)
-
-        scolorStr = '%s %s ' * numberOfColors
-        str_colors = ()
-        for idx, elem in enumerate(colorList):
-            if (idx % 2 == 0):
-                if ((idx % 8) == 0):
-                    str_colors +=  str(elem),
-                else:
-                    str_colors += '" "',
-            else:
-                str_colors += elem,
-        
-        line = ("colorkey 0.01,0.05 0.02,0.95 " + scolorStr + "\n") % str_colors
-        fhCmd.write(line)
+        # ------ Plot ------
+        stp = 0.1
+        lowlim = max(0.0, values.min())
 
-        fhCmd.close()
+        highlim = values.max() + stp
+        fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
+        pc = plt.contourf(theta, r, values, np.arange(lowlim, highlim, stp))
+
+        plt.colorbar(pc)
+        plt.show()
+
+    def _getAxis(self):
+        return self.getEnumText('sliceAxis')
 
-    @staticmethod
-    def colorMapToColorList(steps, colorMap):
-        """ Returns a list of pairs resolution, hexColor to be used in chimera scripts for coloring the volume and
-        the colorKey """
-
-        # Get the map used by monoRes
-        colors = ()
-        ratio = 255.0/(len(steps)-1)
-        for index, step in enumerate(steps):
-            colorPosition = int(round(index*ratio))
-            rgb = colorMap(colorPosition)[:3]
-            colors += step,
-            rgbColor = mcolors.rgb2hex(rgb)
-            colors += rgbColor,
-
-        return colors
-    
     def getColorMap(self):
-        if (COLOR_CHOICES[self.colorMap.get()] is 'other'): 
-            cmap = cm.get_cmap(self.otherColorMap.get())
-        else:
-            cmap = cm.get_cmap(COLOR_CHOICES[self.colorMap.get()])
+        cmap = cm.get_cmap(self.colorMap.get())
         if cmap is None:
             cmap = cm.jet
         return cmap
diff --git a/xmipp3/viewers/viewer_resolution_monogenic_signal.py b/xmipp3/viewers/viewer_resolution_monogenic_signal.py
index 98843f449..55fabb233 100644
--- a/xmipp3/viewers/viewer_resolution_monogenic_signal.py
+++ b/xmipp3/viewers/viewer_resolution_monogenic_signal.py
@@ -32,7 +32,7 @@
 from pyworkflow.utils import replaceExt
 from os.path import exists
 
-from pyworkflow.protocol.params import (LabelParam, EnumParam,
+from pyworkflow.protocol.params import (LabelParam, EnumParam, PointerParam,
                                         IntParam, LEVEL_ADVANCED)
 from pyworkflow.viewer import ProtocolViewer, DESKTOP_TKINTER
 
@@ -42,9 +42,11 @@
 from pwem.emlib.metadata import MetaData, MDL_X, MDL_COUNT
 
 from xmipp3.protocols.protocol_resolution_monogenic_signal import (
-        XmippProtMonoRes, OUTPUT_RESOLUTION_FILE, FN_METADATA_HISTOGRAM,
-        OUTPUT_RESOLUTION_FILE_CHIMERA)
+    XmippProtMonoRes, OUTPUT_RESOLUTION_FILE, FN_METADATA_HISTOGRAM,
+    OUTPUT_RESOLUTION_FILE_CHIMERA)
 from .plotter import XmippPlotter
+from pyworkflow.gui import plotter
+
 
 class XmippMonoResViewer(LocalResolutionViewer):
     """
@@ -55,29 +57,28 @@ class XmippMonoResViewer(LocalResolutionViewer):
     microscopy (cryo-EM).
     """
     _label = 'viewer MonoRes'
-    _targets = [XmippProtMonoRes]      
+    _targets = [XmippProtMonoRes]
     _environments = [DESKTOP_TKINTER]
 
-    
     @staticmethod
     def getColorMapChoices():
         return plt.colormaps()
-   
+
     def __init__(self, *args, **kwargs):
         ProtocolViewer.__init__(self, *args, **kwargs)
 
     def _defineParams(self, form):
         form.addSection(label='Visualization')
-        
+
         form.addParam('doShowVolumeSlices', LabelParam,
                       label="Show resolution slices")
-        
+
         form.addParam('doShowOriginalVolumeSlices', LabelParam,
                       label="Show original volume slices")
 
         form.addParam('doShowResHistogram', LabelParam,
                       label="Show resolution histogram")
-        
+
         group = form.addGroup('Colored resolution Slices and Volumes')
 
         group.addParam('sliceAxis', EnumParam, default=AX_Z,
@@ -86,21 +87,30 @@ def _defineParams(self, form):
                        label='Slice axis')
 
         group.addParam('doShowVolumeColorSlices', LabelParam,
-              label="Show colored slices")
-        
-        group.addParam('doShowOneColorslice', LabelParam, 
-                       expertLevel=LEVEL_ADVANCED, 
-                      label='Show selected slice')
+                       label="Show colored slices")
+
+        group.addParam('doShowOneColorslice', LabelParam,
+                       expertLevel=LEVEL_ADVANCED,
+                       label='Show selected slice')
+
         group.addParam('sliceNumber', IntParam, default=-1,
-                       expertLevel=LEVEL_ADVANCED, 
+                       expertLevel=LEVEL_ADVANCED,
                        label='Show slice number')
-        
+
         group.addParam('doShowChimera', LabelParam,
-                      label="Show Resolution map in ChimeraX")
+                       label="Show Resolution map in ChimeraX")
 
         ColorScaleWizardBase.defineColorScaleParams(group, defaultLowest=self.protocol.min_res_init,
                                                     defaultHighest=self.protocol.max_res_init)
 
+        group.addParam('sharpenedMap', PointerParam, pointerClass='Volume',
+                       label="(Optional) Color a sharpen map by local resolution in ChimeraX",
+                       allowsNull=True,
+                       help='Local resolution should be estimated with the raw maps instead'
+                            ' of sharpen maps. Information about this in (Vilas et al '
+                            'Current Opinion in Structural Biology 2021). This entry parameter '
+                            'allows to color the local resolution in'
+                            'a different map')
 
     def _getVisualizeDict(self):
         self.protocol._createFilenameTemplates()
@@ -111,21 +121,26 @@ def _getVisualizeDict(self):
                 'doShowResHistogram': self._plotHistogram,
                 'doShowChimera': self._showChimera,
                 }
-       
+
     def _showVolumeSlices(self, param=None):
         cm = DataView(self.protocol.resolution_Volume.getFileName())
-        
+
         return [cm]
-    
+
     def _showOriginalVolumeSlices(self, param=None):
-        if self.protocol.halfVolumes.get() is True:
-            cm = DataView(self.protocol.inputVolume.get().getFileName())
-            cm2 = DataView(self.protocol.inputVolume2.get().getFileName())
+        if self.protocol.useHalfVolumes.get():
+            if self.protocol.hasHalfVolumesFile.get():
+                fn1, fn2 = self.protocol.associatedHalves.get().getHalfMaps().split(',')
+            else:
+                fn1 = self.protocol.halfMap1.get().getFileName()
+                fn2 = self.protocol.halfMap2.get().getFileName()
+            cm = DataView(fn1)
+            cm2 = DataView(fn2)
             return [cm, cm2]
         else:
-            cm = DataView(self.protocol.inputVolumes.get().getFileName())
+            cm = DataView(self.protocol.fullMap.get().getFileName())
             return [cm]
-    
+
     def _showVolumeColorSlices(self, param=None):
         if (exists(self.protocol._getExtraPath("mgresolution.mrc"))):
             imageFile = self.protocol._getExtraPath("mgresolution.mrc")
@@ -136,13 +151,13 @@ def _showVolumeColorSlices(self, param=None):
         imgData, min_Res, max_Res, voldim = self.getImgData(imageFile)
 
         xplotter = XmippPlotter(x=2, y=2, mainTitle="Local Resolution Slices "
-                                                     "along %s-axis."
-                                                     %self._getAxis())
-        #The slices to be shown are close to the center. Volume size is divided in 
+                                                    "along %s-axis."
+                                                    % self._getAxis())
+        # The slices to be shown are close to the center. Volume size is divided in
         # 9 segments, the fouth central ones are selected i.e. 3,4,5,6
-        for i in range(3,7):
+        for i in range(3, 7):
             sliceNumber = self.getSlice(i, imgData)
-            a = xplotter.createSubPlot("Slice %s" % (sliceNumber+1), '', '')
+            a = xplotter.createSubPlot("Slice %s" % (sliceNumber + 1), '', '')
             matrix = self.getSliceImage(imgData, sliceNumber, self._getAxis())
             plot = xplotter.plotMatrix(a, matrix, self.lowest.get(), self.highest.get(),
                                        cmap=self.getColorMap(),
@@ -160,22 +175,22 @@ def _showOneColorslice(self, param=None):
         imgData, min_Res, max_Res, voldim = self.getImgData(imageFile)
 
         xplotter = XmippPlotter(x=1, y=1, mainTitle="Local Resolution Slices "
-                                                     "along %s-axis."
-                                                     %self._getAxis())
+                                                    "along %s-axis."
+                                                    % self._getAxis())
         sliceNumber = self.sliceNumber.get()
         if sliceNumber < 0:
-            sliceNumber = int(voldim[0]/2)
+            sliceNumber = int(voldim[0] / 2)
         else:
             sliceNumber -= 1
-        #sliceNumber has no sense to start in zero 
-        a = xplotter.createSubPlot("Slice %s" % (sliceNumber+1), '', '')
+        # sliceNumber has no sense to start in zero
+        a = xplotter.createSubPlot("Slice %s" % (sliceNumber + 1), '', '')
         matrix = self.getSliceImage(imgData, sliceNumber, self._getAxis())
         plot = xplotter.plotMatrix(a, matrix, self.lowest.get(), self.highest.get(),
-                                       cmap=self.getColorMap(),
-                                       interpolation="nearest")
+                                   cmap=self.getColorMap(),
+                                   interpolation="nearest")
         xplotter.getColorBar(plot)
         return [xplotter]
-    
+
     def _plotHistogram(self, param=None):
         md = MetaData()
         md.read(self.protocol._getExtraPath(FN_METADATA_HISTOGRAM))
@@ -191,10 +206,10 @@ def _plotHistogram(self, param=None):
 
         _plotter = EmPlotter()
         _plotter.createSubPlot("Resolutions Histogram",
-                              "Resolution (A)", "# of Counts")
-        barwidth = (x_axis[-1] - x_axis[0])/len(x_axis)
+                               "Resolution (A)", "# of Counts")
+        barwidth = (x_axis[-1] - x_axis[0]) / len(x_axis)
 
-        _plotter.plotDataBar(x_axis, y_axis, barwidth)
+        _plotter.plotDataBar(x_axis[:-2], y_axis[:-2], barwidth)
 
         return [_plotter]
 
@@ -208,13 +223,23 @@ def _showChimera(self, param=None):
         else:
             fnResVol = self.protocol._getExtraPath(OUTPUT_RESOLUTION_FILE_CHIMERA)
 
-        if self.protocol.halfVolumes.get():
-            vol = self.protocol.inputVolume.get()
+        if self.sharpenedMap.get():
+            fnOrigMap = self.sharpenedMap.get().getFileName()
+            sampRate = self.sharpenedMap.get().getSamplingRate()
         else:
-            vol = self.protocol.inputVolumes.get()
-
-        fnOrigMap = vol.getFileName()
-        sampRate = vol.getSamplingRate()
+            if self.protocol.useHalfVolumes.get():
+                if self.protocol.hasHalfVolumesFile.get():
+                    vol = self.protocol.associatedHalves.get().getHalfMaps()
+                    fnOrigMap, _unused = vol.split(',')
+                    sampRate = self.protocol.associatedHalves.get().getSamplingRate()
+                else:
+                    vol = self.protocol.halfMap1.get()
+                    fnOrigMap = vol.getFileName()
+                    sampRate = vol.getSamplingRate()
+            else:
+                vol = self.protocol.fullMap.get()
+                fnOrigMap = vol.getFileName()
+                sampRate = vol.getSamplingRate()
 
         cmdFile = self.protocol._getExtraPath('chimera_resolution_map.py')
         self.createChimeraScript(cmdFile, fnResVol, fnOrigMap, sampRate,
@@ -229,4 +254,3 @@ def getColorMap(self):
         if cmap is None:
             cmap = cm.jet
         return cmap
-
diff --git a/xmipp3/viewers/viewer_resolution_monotomo.py b/xmipp3/viewers/viewer_resolution_monotomo.py
deleted file mode 100644
index 26fd09c5a..000000000
--- a/xmipp3/viewers/viewer_resolution_monotomo.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-# **************************************************************************
-# *
-# * Authors:     J.L. Vilas (jlvilas@cnb.csic.es)
-# *
-# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
-# *
-# * 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 matplotlib.pyplot as plt
-from matplotlib import cm
-from pwem.wizards import ColorScaleWizardBase
-
-from pyworkflow.protocol.params import (LabelParam, EnumParam,
-                                        IntParam, LEVEL_ADVANCED)
-from pyworkflow.viewer import ProtocolViewer, DESKTOP_TKINTER
-from pwem.viewers import ChimeraView, DataView, EmPlotter
-from pwem.emlib.metadata import MetaData, MDL_X, MDL_COUNT
-from pwem.viewers import LocalResolutionViewer
-from pwem.constants import AX_Z
-
-from xmipp3.viewers.plotter import XmippPlotter
-from xmipp3.protocols.protocol_resolution_monotomo import (XmippProtMonoTomo,
-                                                           OUTPUT_RESOLUTION_FILE,
-                                                           FN_METADATA_HISTOGRAM)
-
-class XmippMonoTomoViewer(LocalResolutionViewer):
-    """
-    Visualization tools for MonoRes results.
-
-    MonoRes is a Xmipp packagefor computing the local resolution of 3D
-    density maps studied in structural biology, primarily by cryo-electron
-    microscopy (cryo-EM).
-    """
-    _label = 'viewer MonoTomo'
-    _targets = [XmippProtMonoTomo]
-    _environments = [DESKTOP_TKINTER]
-
-    @staticmethod
-    def getColorMapChoices():
-        return plt.colormaps()
-
-    def __init__(self, *args, **kwargs):
-        ProtocolViewer.__init__(self, *args, **kwargs)
-
-    def _defineParams(self, form):
-        form.addSection(label='Visualization')
-
-        form.addParam('doShowVolumeSlices', LabelParam,
-                      label="Show resolution slices")
-
-        form.addParam('doShowOriginalVolumeSlices', LabelParam,
-                      label="Show original volume slices")
-
-        form.addParam('doShowResHistogram', LabelParam,
-                      label="Show resolution histogram")
-
-        group = form.addGroup('Colored resolution Slices and Volumes')
-
-        group.addParam('sliceAxis', EnumParam, default=AX_Z,
-                       choices=['x', 'y', 'z'],
-                       display=EnumParam.DISPLAY_HLIST,
-                       label='Slice axis')
-
-        group.addParam('doShowVolumeColorSlices', LabelParam,
-                       label="Show colored resolution slices")
-
-        group.addParam('doShowOneColorslice', LabelParam,
-                       expertLevel=LEVEL_ADVANCED,
-                       label='Show selected slice')
-        group.addParam('sliceNumber', IntParam, default=-1,
-                       expertLevel=LEVEL_ADVANCED,
-                       label='Show slice number')
-
-        group.addParam('doShowChimera', LabelParam,
-                       label="Show Resolution map in Chimera")
-
-        ColorScaleWizardBase.defineColorScaleParams(group, defaultHighest=self.protocol.max_res_init.get(), defaultLowest=self.protocol.min_res_init.get())
-
-    def _getVisualizeDict(self):
-        self.protocol._createFilenameTemplates()
-        return {'doShowOriginalVolumeSlices': self._showOriginalVolumeSlices,
-                'doShowVolumeSlices': self._showVolumeSlices,
-                'doShowVolumeColorSlices': self._showVolumeColorSlicesResolution,
-                'doShowOneColorslice': self._showOneColorslice,
-                'doShowResHistogram': self._plotHistogram,
-                'doShowChimera': self._showChimera,
-                }
-
-    def _showVolumeSlices(self, param=None):
-        cm = DataView(self.protocol.resolution_Volume.getFileName())
-
-        return [cm]
-
-    def _showOriginalVolumeSlices(self, param=None):
-
-        # if self.protocol.halfVolumes.get() is True:
-        # Where is halfVolumes?
-        try:
-            cm = DataView(self.protocol.inputVolume.get().getFileName())
-            cm2 = DataView(self.protocol.inputVolume2.get().getFileName())
-            return [cm, cm2]
-        except:
-            cm = DataView(self.protocol.inputVolumes.get().getFileName())
-            return [cm]
-
-    def _showVolumeColorSlicesResolution(self, param=None):
-        self._showVolumeColorSlices(OUTPUT_RESOLUTION_FILE)
-
-    def _showVolumeColorSlices(self, mapFile):
-        imageFile = self.protocol._getFileName(mapFile)
-        imgData, _, _, _ = self.getImgData(imageFile)
-        imgData, min_Res, max_Res, voldim__ = self.getImgData(imageFile)
-
-        xplotter = XmippPlotter(x=2, y=2, mainTitle="Local Resolution Slices "
-                                                    "along %s-axis."
-                                                    % self._getAxis())
-
-        # The slices to be shown are close to the center. Volume size is divided in
-        # 9 segments, the fouth central ones are selected i.e. 3,4,5,6
-        for i in range(3, 7):
-            sliceNumber = self.getSlice(i, imgData)
-            plot = self._createSlicePlot(imgData, sliceNumber, xplotter)
-        xplotter.getColorBar(plot)
-
-        return [plt.show(xplotter)]
-
-    def _showOneColorslice(self, param=None):
-        imageFile = self.protocol._getFileName(OUTPUT_RESOLUTION_FILE)
-        imgData, _, _, volDim  = self.getImgData(imageFile)
-
-        xplotter = XmippPlotter(x=1, y=1, mainTitle="Local Resolution Slices "
-                                                    "along %s-axis."
-                                                    % self._getAxis())
-        sliceNumber = self.sliceNumber.get()
-        if sliceNumber < 0:
-            sliceNumber = int(volDim[0] / 2)
-        else:
-            sliceNumber -= 1
-        # sliceNumber has no sense to start in zero
-        plot = self._createSlicePlot(imgData, sliceNumber, xplotter)
-        xplotter.getColorBar(plot)
-
-        return [plt.show(xplotter)]
-
-    def _createSlicePlot(self, imgData, sliceNumber, xplotter):
-        a = xplotter.createSubPlot("Slice %s" % (sliceNumber + 1), '', '')
-        matrix = self.getSliceImage(imgData, sliceNumber, self._getAxis())
-        plot = xplotter.plotMatrix(a, matrix, self.lowest.get(), self.highest.get(),
-                                   cmap=self.getColorMap(),
-                                   interpolation="nearest")
-        return plot
-
-    def _plotHistogram(self, param=None):
-        md = MetaData()
-        md.read(self.protocol._getFileName(FN_METADATA_HISTOGRAM))
-        x_axis = []
-        y_axis = []
-
-        i = 0
-        for idx in md:
-            x_axis_ = md.getValue(MDL_X, idx)
-            if i == 0:
-                x0 = x_axis_
-            elif i == 1:
-                x1 = x_axis_
-            y_axis_ = md.getValue(MDL_COUNT, idx)
-
-            i += 1
-            x_axis.append(x_axis_)
-            y_axis.append(y_axis_)
-
-        plotter = EmPlotter()
-        plotter.createSubPlot("Resolutions Histogram",
-                              "Resolution (A)", "# of Counts")
-
-        barwidth = x1 - x0
-
-        plotter.plotDataBar(x_axis, y_axis, barwidth)
-
-        return [plotter]
-
-
-    def _getAxis(self):
-        return self.getEnumText('sliceAxis')
-
-    def _showChimera(self, param=None):
-
-        fnResVol = self.protocol._getFileName(OUTPUT_RESOLUTION_FILE)
-
-        vol = self.protocol.inputVolume.get()
-
-        fnOrigMap = vol.getFileName()
-        sampRate = vol.getSamplingRate()
-
-        cmdFile = self.protocol._getExtraPath('chimera_resolution_map.py')
-        self.createChimeraScript(cmdFile, fnResVol, fnOrigMap, sampRate,
-                                 numColors=self.intervals.get(),
-                                 lowResLimit=self.highest.get(),
-                                 highResLimit=self.lowest.get())
-        view = ChimeraView(cmdFile)
-        return [view]
-
-    def getColorMap(self):
-        cmap = cm.get_cmap(self.colorMap.get())
-        if cmap is None:
-            cmap = cm.jet
-        return cmap
diff --git a/xmipp3/viewers/viewer_structure_map_sph.py b/xmipp3/viewers/viewer_structure_map_sph.py
index ab471cf10..26df81fab 100644
--- a/xmipp3/viewers/viewer_structure_map_sph.py
+++ b/xmipp3/viewers/viewer_structure_map_sph.py
@@ -1,3 +1,12 @@
+########
+
+# This viewer has been removed as the protocols associated to it are no longer offered in Scipion.
+# This will be solved in a future release.
+
+########
+
+
+
 # **************************************************************************
 # *
 # * Authors:     Amaya Jimenez Moreno (ajimenez@cnb.csic.es)
@@ -24,128 +33,128 @@
 # *
 # **************************************************************************
 
-import os
-import numpy as np
-from mpl_toolkits.mplot3d import proj3d
-
-from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO, ProtocolViewer
-from pyworkflow.gui.plotter import plt
-import pyworkflow.protocol.params as params
-
-from xmipp3.protocols.protocol_structure_map_sph import XmippProtStructureMapSPH
-
-
-class XmippProtStructureMapSphViewer(ProtocolViewer):
-    """ Wrapper to visualize different type of data objects
-    with the Xmipp program xmipp_showj
-    """
-    
-    _label = 'viewer structure map sph'
-    _environments = [DESKTOP_TKINTER, WEB_DJANGO]
-    _targets = [XmippProtStructureMapSPH]
-
-
-    def _defineParams(self, form):
-        form.addSection(label='Show StructMap')
-        form.addParam('numberOfDimensions', params.IntParam, default=2,
-                      label="Number of dimensions",
-                      help='In normal practice, it should be 1, 2 or, at most, 3.')
-        form.addParam('doShowPlot', params.LabelParam,
-                      label="Display the StructMap")
-    
-    def _getVisualizeDict(self):
-        return {'doShowPlot': self._visualize}
-    
-        
-    def _visualize(self, e=None):
-        nDim = self.numberOfDimensions.get()
-        fnOutput = self.protocol._defineResultsName(nDim)
-        if not os.path.exists(fnOutput):
-            return [self.errorMessage('The necessary metadata was not produced\n'
-                                      'Execute again the protocol\n',
-                                      title='Missing result file')]
-        coordinates = np.loadtxt(fnOutput)
-        
-        # Create labels        
-        count = 0
-        labels = []
-        volList, _, _ = self.protocol._iterInputVolumes()
-        for voli in volList:
-            count+=1
-            labels.append("vol_%02d"%count)
-         
-        val = 0
-        if nDim == 1:
-            AX = plt.subplot(111)
-            plot = plt.plot(coordinates, np.zeros_like(coordinates) + val, 'o', c='g')
-            plt.xlabel('Dimension 1', fontsize=11)
-            AX.set_yticks([1])
-            plt.title('StructMap')
-             
-            for label, x, y in zip(labels, coordinates, np.zeros_like(coordinates)):
-                plt.annotate(label, 
-                             xy = (x, y), xytext = (x+val, y+val),
-                             textcoords = 'data', ha = 'right', va = 'bottom',fontsize=9,
-                             bbox = dict(boxstyle = 'round,pad=0.3', fc = 'yellow', alpha = 0.3))
-            plt.grid(True)
-            plt.show()
-                         
-        elif nDim == 2:
-            plot = plt.scatter(coordinates[:, 0], coordinates[:, 1], marker='o', c='g')
-            plt.xlabel('Dimension 1', fontsize=11)
-            plt.ylabel('Dimension 2', fontsize=11)
-            plt.title('StructMap')
-            for label, x, y in zip(labels, coordinates[:, 0], coordinates[:, 1]):
-                plt.annotate(label, 
-                             xy = (x, y), xytext = (x+val, y+val),
-                             textcoords = 'data', ha = 'right', va = 'bottom',fontsize=9,
-                             bbox = dict(boxstyle = 'round,pad=0.3', fc = 'yellow', alpha = 0.3))
-            plt.grid(True)
-            plt.show()
-                
-        else: 
-                         
-            fig = plt.figure()
-            ax = fig.add_subplot(111, projection = '3d')
-            
-            ax.scatter(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], marker = 'o', c='g')
-            ax.set_xlabel('Dimension 1', fontsize=11)
-            ax.set_ylabel('Dimension 2', fontsize=11)
-            ax.set_zlabel('Dimension 3', fontsize=11)
-            ax.text2D(0.05, 0.95, "StructMap", transform=ax.transAxes)
-                         
-            x2, y2, _ = proj3d.proj_transform(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], ax.get_proj())
-            Labels = []
-            for i in range(len(coordinates[:, 0])):
-                text = labels[i]
-                label = ax.annotate(text,
-                                    xycoords='data',
-                                    xy = (x2[i], y2[i]), xytext = (x2[i]+val, y2[i]+val),
-                                    textcoords = 'data', ha = 'right',
-                                     va = 'bottom', fontsize=9,
-                                    bbox = dict(boxstyle = 'round,pad=0.3',
-                                                 fc = 'yellow', alpha = 0.3))
-                                     
-                Labels.append(label)
-                
-            def update_position(e):
-                x2, y2, _ = proj3d.proj_transform(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], ax.get_proj())
-                for i in range(len(coordinates[:, 0])):
-                    label = Labels[i]
-                    label.xytext = (x2[i],y2[i])
-                    label.update_positions(fig.canvas.get_renderer())
-                fig.canvas.draw()
-            fig.canvas.mpl_connect('button_release_event', update_position)
-            plt.show()
-        
-        return plot
-        
-    def _validate(self):
-        errors = []
-        
-        numberOfDimensions=self.numberOfDimensions.get()
-        if numberOfDimensions > 3 or numberOfDimensions < 1:
-            errors.append("The number of dimensions should be 1, 2 or, at most, 3.")
-        
-        return errors
-    
\ No newline at end of file
+# import os
+# import numpy as np
+# from mpl_toolkits.mplot3d import proj3d
+#
+# from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO, ProtocolViewer
+# from pyworkflow.gui.plotter import plt
+# import pyworkflow.protocol.params as params
+#
+# from xmipp3.protocols.protocol_structure_map_sph import XmippProtStructureMapSPH
+#
+#
+# class XmippProtStructureMapSphViewer(ProtocolViewer):
+#     """ Wrapper to visualize different type of data objects
+#     with the Xmipp program xmipp_showj
+#     """
+#
+#     _label = 'viewer structure map sph'
+#     _environments = [DESKTOP_TKINTER, WEB_DJANGO]
+#     _targets = [XmippProtStructureMapSPH]
+#
+#
+#     def _defineParams(self, form):
+#         form.addSection(label='Show StructMap')
+#         form.addParam('numberOfDimensions', params.IntParam, default=2,
+#                       label="Number of dimensions",
+#                       help='In normal practice, it should be 1, 2 or, at most, 3.')
+#         form.addParam('doShowPlot', params.LabelParam,
+#                       label="Display the StructMap")
+#
+#     def _getVisualizeDict(self):
+#         return {'doShowPlot': self._visualize}
+#
+#
+#     def _visualize(self, e=None):
+#         nDim = self.numberOfDimensions.get()
+#         fnOutput = self.protocol._defineResultsName(nDim)
+#         if not os.path.exists(fnOutput):
+#             return [self.errorMessage('The necessary metadata was not produced\n'
+#                                       'Execute again the protocol\n',
+#                                       title='Missing result file')]
+#         coordinates = np.loadtxt(fnOutput)
+#
+#         # Create labels
+#         count = 0
+#         labels = []
+#         volList, _, _ = self.protocol._iterInputVolumes()
+#         for voli in volList:
+#             count+=1
+#             labels.append("vol_%02d"%count)
+#
+#         val = 0
+#         if nDim == 1:
+#             AX = plt.subplot(111)
+#             plot = plt.plot(coordinates, np.zeros_like(coordinates) + val, 'o', c='g')
+#             plt.xlabel('Dimension 1', fontsize=11)
+#             AX.set_yticks([1])
+#             plt.title('StructMap')
+#
+#             for label, x, y in zip(labels, coordinates, np.zeros_like(coordinates)):
+#                 plt.annotate(label,
+#                              xy = (x, y), xytext = (x+val, y+val),
+#                              textcoords = 'data', ha = 'right', va = 'bottom',fontsize=9,
+#                              bbox = dict(boxstyle = 'round,pad=0.3', fc = 'yellow', alpha = 0.3))
+#             plt.grid(True)
+#             plt.show()
+#
+#         elif nDim == 2:
+#             plot = plt.scatter(coordinates[:, 0], coordinates[:, 1], marker='o', c='g')
+#             plt.xlabel('Dimension 1', fontsize=11)
+#             plt.ylabel('Dimension 2', fontsize=11)
+#             plt.title('StructMap')
+#             for label, x, y in zip(labels, coordinates[:, 0], coordinates[:, 1]):
+#                 plt.annotate(label,
+#                              xy = (x, y), xytext = (x+val, y+val),
+#                              textcoords = 'data', ha = 'right', va = 'bottom',fontsize=9,
+#                              bbox = dict(boxstyle = 'round,pad=0.3', fc = 'yellow', alpha = 0.3))
+#             plt.grid(True)
+#             plt.show()
+#
+#         else:
+#
+#             fig = plt.figure()
+#             ax = fig.add_subplot(111, projection = '3d')
+#
+#             ax.scatter(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], marker = 'o', c='g')
+#             ax.set_xlabel('Dimension 1', fontsize=11)
+#             ax.set_ylabel('Dimension 2', fontsize=11)
+#             ax.set_zlabel('Dimension 3', fontsize=11)
+#             ax.text2D(0.05, 0.95, "StructMap", transform=ax.transAxes)
+#
+#             x2, y2, _ = proj3d.proj_transform(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], ax.get_proj())
+#             Labels = []
+#             for i in range(len(coordinates[:, 0])):
+#                 text = labels[i]
+#                 label = ax.annotate(text,
+#                                     xycoords='data',
+#                                     xy = (x2[i], y2[i]), xytext = (x2[i]+val, y2[i]+val),
+#                                     textcoords = 'data', ha = 'right',
+#                                      va = 'bottom', fontsize=9,
+#                                     bbox = dict(boxstyle = 'round,pad=0.3',
+#                                                  fc = 'yellow', alpha = 0.3))
+#
+#                 Labels.append(label)
+#
+#             def update_position(e):
+#                 x2, y2, _ = proj3d.proj_transform(coordinates[:, 0], coordinates[:, 1], coordinates[:, 2], ax.get_proj())
+#                 for i in range(len(coordinates[:, 0])):
+#                     label = Labels[i]
+#                     label.xytext = (x2[i],y2[i])
+#                     label.update_positions(fig.canvas.get_renderer())
+#                 fig.canvas.draw()
+#             fig.canvas.mpl_connect('button_release_event', update_position)
+#             plt.show()
+#
+#         return plot
+#
+#     def _validate(self):
+#         errors = []
+#
+#         numberOfDimensions=self.numberOfDimensions.get()
+#         if numberOfDimensions > 3 or numberOfDimensions < 1:
+#             errors.append("The number of dimensions should be 1, 2 or, at most, 3.")
+#
+#         return errors
+#
\ No newline at end of file
diff --git a/xmipp3/viewers/viewer_volume_consensus.py b/xmipp3/viewers/viewer_volume_consensus.py
new file mode 100644
index 000000000..8eb7b1f9a
--- /dev/null
+++ b/xmipp3/viewers/viewer_volume_consensus.py
@@ -0,0 +1,48 @@
+# **************************************************************************
+# *
+# * Authors:  Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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'
+# *
+# **************************************************************************
+
+from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO
+from pwem.viewers import ChimeraView
+
+from .viewer import XmippViewer
+from xmipp3.protocols.protocol_volume_consensus import XmippProtVolConsensus
+
+
+class XmippVolumeConsensusViewer(XmippViewer):
+    """ Visualize the output of protocol volume consensus """
+    _label = 'viewer volume consensus'
+    _targets = [XmippProtVolConsensus]
+    _environments = [DESKTOP_TKINTER, WEB_DJANGO]
+
+    def __init__(self, **args):
+        XmippViewer.__init__(self, **args)
+
+    def _visualize(self, obj, **args):
+        import os
+        fnCmd = self.protocol._getPath('result_fusion_chimera.cxc')
+        if os.path.exists(fnCmd):
+            self._views.append(ChimeraView(fnCmd))
+        return self._views
diff --git a/xmipp3/viewers/viewer_volume_deform_sph.py b/xmipp3/viewers/viewer_volume_deform_sph.py
index 2de2de917..4a12481dd 100644
--- a/xmipp3/viewers/viewer_volume_deform_sph.py
+++ b/xmipp3/viewers/viewer_volume_deform_sph.py
@@ -1,3 +1,12 @@
+########
+
+# This viewer has been removed as the protocols associated to it are no longer offered in Scipion.
+# This will be solved in a future release.
+
+########
+
+
+
 # **************************************************************************
 # *
 # * Authors:  Amaya Jimenez Moreno (ajimenez@cnb.csic.es)
@@ -24,148 +33,148 @@
 # *
 # **************************************************************************
 
-from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO, ProtocolViewer
-from pwem.viewers import ChimeraView
-import pyworkflow.protocol.params as params
-from pyworkflow.utils import getExt, removeExt
-from os.path import abspath
-from xmipp3.protocols.protocol_volume_deform_sph import XmippProtVolumeDeformSPH
-
-
-class XmippVolumeDeformSphViewer(ProtocolViewer):
-    """ Visualize the output of protocol volume strain """
-    _label = 'viewer volume deform sph'
-    _targets = [XmippProtVolumeDeformSPH]
-    _environments = [DESKTOP_TKINTER, WEB_DJANGO]
-    
-
-    def _defineParams(self, form):
-        form.addSection(label='Show deformation')
-        form.addParam('doShowStrain', params.LabelParam,
-                      label="Display the strain deformation")
-        form.addParam('doShowRotation', params.LabelParam,
-                      label="Display the rotation deformation")
-        form.addParam('doShowMorphOrigRef', params.LabelParam,
-                      label="Display the morphing between original and reference volumes")
-        form.addParam('doShowMorphDeformedRef', params.LabelParam,
-                      label="Display the morphing between deformed and reference volumes")
-
-    def _getVisualizeDict(self):
-        self.protocol._createFilenameTemplates()
-        return {'doShowStrain': self._doShowStrain,
-                'doShowRotation': self._doShowRotation,
-                'doShowMorphOrigRef': self._doShowMorphOrigRef,
-                'doShowMorphDeformedRef': self._doShowDeformedOrigRef
-                }
-
-
-    def _doShowStrain(self, param=None):
-
-        scriptFile = self.protocol._getPath('strain_chimera.cxc')
-        fhCmd = open(scriptFile, 'w')
-        fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
-        ext = getExt(self.protocol._getFileName('fnInputVol'))
-        fninput = abspath(fnbase + ext[0:4])
-        smprt = self.protocol.outputVolume.getSamplingRate()
-
-        fnbase2 = removeExt(self.protocol._getFileName('fnOutVol'))
-        fnStrain = abspath(fnbase2)
-
-        fhCmd.write("open %s\n" % fninput)
-        fhCmd.write("open %s\n" % (fnStrain+"_strain.mrc"))
-        counter = 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        counter += 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        fhCmd.write("vol #%d hide\n" % counter)
-        #fhCmd.write("focus\n")
-        fhCmd.write('color sample #%d map #%d palette ^rainbow\n' % (counter -1, counter))
-        fhCmd.close()
-
-        view = ChimeraView(scriptFile)
-        return [view]
-
-
-    def _doShowRotation(self, param=None):
-
-        scriptFile = self.protocol._getPath('rotation_chimera.cxc')
-        fhCmd = open(scriptFile, 'w')
-        fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
-        ext = getExt(self.protocol._getFileName('fnInputVol'))
-        fninput = abspath(fnbase + ext[0:4])
-        smprt = self.protocol.outputVolume.getSamplingRate()
-
-        fnbase2 = removeExt(self.protocol._getFileName('fnOutVol'))
-        fnStrain = abspath(fnbase2)
-
-        fhCmd.write("open %s\n" % fninput)
-        fhCmd.write("open %s\n" % (fnStrain+"_rotation.mrc"))
-        counter = 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        counter += 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        #fhCmd.write("focus\n")
-        fhCmd.write("vol #%d hide\n" % (counter))
-        fhCmd.write('color sample #%d map #%d palette ^rainbow\n' % (counter -1, counter))
-        # fhCmd.write("scolor #0 volume #1 cmap rainbow reverseColors True\n")
-        fhCmd.close()
-
-        view = ChimeraView(scriptFile)
-        return [view]
-
-    def _doShowMorphOrigRef(self, param=None):
-
-        scriptFile = self.protocol._getPath('morph_orig_ref_chimera.cxc')
-        fhCmd = open(scriptFile, 'w')
-        fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
-        ext = getExt(self.protocol._getFileName('fnInputVol'))
-        fninput = abspath(fnbase + ext[0:4])
-        smprt = self.protocol.outputVolume.getSamplingRate()
-
-        fnbase2 = removeExt(self.protocol._getFileName('fnRefVol'))
-        ext2 = getExt(self.protocol._getFileName('fnRefVol'))
-        fnref = abspath(fnbase2 + ext2[0:4])
-
-        fhCmd.write("open %s\n" % fninput)
-        fhCmd.write("open %s\n" % fnref)
-
-        counter = 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        fhCmd.write("vol #%d hide\n" % (counter))
-        counter += 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        fhCmd.write("vol #%d hide\n" % (counter))
-        #fhCmd.write("focus\n")
-        fhCmd.write("volume morph #%d,%d frames 500\n" % (counter - 1, counter))
-        fhCmd.close()
-
-        view = ChimeraView(scriptFile)
-        return [view]
-
-    def _doShowDeformedOrigRef(self, param=None):
-
-        scriptFile = self.protocol._getPath('morph_deformed_ref_chimera.cxc')
-        fhCmd = open(scriptFile, 'w')
-        fnbase = removeExt(self.protocol._getFileName('fnOutVol'))
-        ext = getExt(self.protocol._getFileName('fnOutVol'))
-        fninput = abspath(fnbase + ext[0:4])
-        smprt = self.protocol.outputVolume.getSamplingRate()
-
-        fnbase2 = removeExt(self.protocol._getFileName('fnRefVol'))
-        ext2 = getExt(self.protocol._getFileName('fnRefVol'))
-        fnref = abspath(fnbase2 + ext2[0:4])
-
-        fhCmd.write("open %s\n" % fninput)
-        fhCmd.write("open %s\n" % fnref)
-        counter = 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        fhCmd.write("vol #%d hide\n" % counter)
-        counter += 1
-        fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
-        # fhCmd.write("focus\n")
-        fhCmd.write("vol #%d hide\n" % counter)
-        fhCmd.write("volume morph #%d,%d frames 500\n" %  (counter-1, counter))
-        fhCmd.close()
-
-        view = ChimeraView(scriptFile)
-        return [view]
+# from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO, ProtocolViewer
+# from pwem.viewers import ChimeraView
+# import pyworkflow.protocol.params as params
+# from pyworkflow.utils import getExt, removeExt
+# from os.path import abspath
+# from xmipp3.protocols.protocol_volume_deform_sph import XmippProtVolumeDeformSPH
+#
+#
+# class XmippVolumeDeformSphViewer(ProtocolViewer):
+#     """ Visualize the output of protocol volume strain """
+#     _label = 'viewer volume deform sph'
+#     _targets = [XmippProtVolumeDeformSPH]
+#     _environments = [DESKTOP_TKINTER, WEB_DJANGO]
+#
+#
+#     def _defineParams(self, form):
+#         form.addSection(label='Show deformation')
+#         form.addParam('doShowStrain', params.LabelParam,
+#                       label="Display the strain deformation")
+#         form.addParam('doShowRotation', params.LabelParam,
+#                       label="Display the rotation deformation")
+#         form.addParam('doShowMorphOrigRef', params.LabelParam,
+#                       label="Display the morphing between original and reference volumes")
+#         form.addParam('doShowMorphDeformedRef', params.LabelParam,
+#                       label="Display the morphing between deformed and reference volumes")
+#
+#     def _getVisualizeDict(self):
+#         self.protocol._createFilenameTemplates()
+#         return {'doShowStrain': self._doShowStrain,
+#                 'doShowRotation': self._doShowRotation,
+#                 'doShowMorphOrigRef': self._doShowMorphOrigRef,
+#                 'doShowMorphDeformedRef': self._doShowDeformedOrigRef
+#                 }
+#
+#
+#     def _doShowStrain(self, param=None):
+#
+#         scriptFile = self.protocol._getPath('strain_chimera.cxc')
+#         fhCmd = open(scriptFile, 'w')
+#         fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
+#         ext = getExt(self.protocol._getFileName('fnInputVol'))
+#         fninput = abspath(fnbase + ext[0:4])
+#         smprt = self.protocol.outputVolume.getSamplingRate()
+#
+#         fnbase2 = removeExt(self.protocol._getFileName('fnOutVol'))
+#         fnStrain = abspath(fnbase2)
+#
+#         fhCmd.write("open %s\n" % fninput)
+#         fhCmd.write("open %s\n" % (fnStrain+"_strain.mrc"))
+#         counter = 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         counter += 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         fhCmd.write("vol #%d hide\n" % counter)
+#         #fhCmd.write("focus\n")
+#         fhCmd.write('color sample #%d map #%d palette ^rainbow\n' % (counter -1, counter))
+#         fhCmd.close()
+#
+#         view = ChimeraView(scriptFile)
+#         return [view]
+#
+#
+#     def _doShowRotation(self, param=None):
+#
+#         scriptFile = self.protocol._getPath('rotation_chimera.cxc')
+#         fhCmd = open(scriptFile, 'w')
+#         fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
+#         ext = getExt(self.protocol._getFileName('fnInputVol'))
+#         fninput = abspath(fnbase + ext[0:4])
+#         smprt = self.protocol.outputVolume.getSamplingRate()
+#
+#         fnbase2 = removeExt(self.protocol._getFileName('fnOutVol'))
+#         fnStrain = abspath(fnbase2)
+#
+#         fhCmd.write("open %s\n" % fninput)
+#         fhCmd.write("open %s\n" % (fnStrain+"_rotation.mrc"))
+#         counter = 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         counter += 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         #fhCmd.write("focus\n")
+#         fhCmd.write("vol #%d hide\n" % (counter))
+#         fhCmd.write('color sample #%d map #%d palette ^rainbow\n' % (counter -1, counter))
+#         # fhCmd.write("scolor #0 volume #1 cmap rainbow reverseColors True\n")
+#         fhCmd.close()
+#
+#         view = ChimeraView(scriptFile)
+#         return [view]
+#
+#     def _doShowMorphOrigRef(self, param=None):
+#
+#         scriptFile = self.protocol._getPath('morph_orig_ref_chimera.cxc')
+#         fhCmd = open(scriptFile, 'w')
+#         fnbase = removeExt(self.protocol._getFileName('fnInputVol'))
+#         ext = getExt(self.protocol._getFileName('fnInputVol'))
+#         fninput = abspath(fnbase + ext[0:4])
+#         smprt = self.protocol.outputVolume.getSamplingRate()
+#
+#         fnbase2 = removeExt(self.protocol._getFileName('fnRefVol'))
+#         ext2 = getExt(self.protocol._getFileName('fnRefVol'))
+#         fnref = abspath(fnbase2 + ext2[0:4])
+#
+#         fhCmd.write("open %s\n" % fninput)
+#         fhCmd.write("open %s\n" % fnref)
+#
+#         counter = 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         fhCmd.write("vol #%d hide\n" % (counter))
+#         counter += 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         fhCmd.write("vol #%d hide\n" % (counter))
+#         #fhCmd.write("focus\n")
+#         fhCmd.write("volume morph #%d,%d frames 500\n" % (counter - 1, counter))
+#         fhCmd.close()
+#
+#         view = ChimeraView(scriptFile)
+#         return [view]
+#
+#     def _doShowDeformedOrigRef(self, param=None):
+#
+#         scriptFile = self.protocol._getPath('morph_deformed_ref_chimera.cxc')
+#         fhCmd = open(scriptFile, 'w')
+#         fnbase = removeExt(self.protocol._getFileName('fnOutVol'))
+#         ext = getExt(self.protocol._getFileName('fnOutVol'))
+#         fninput = abspath(fnbase + ext[0:4])
+#         smprt = self.protocol.outputVolume.getSamplingRate()
+#
+#         fnbase2 = removeExt(self.protocol._getFileName('fnRefVol'))
+#         ext2 = getExt(self.protocol._getFileName('fnRefVol'))
+#         fnref = abspath(fnbase2 + ext2[0:4])
+#
+#         fhCmd.write("open %s\n" % fninput)
+#         fhCmd.write("open %s\n" % fnref)
+#         counter = 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         fhCmd.write("vol #%d hide\n" % counter)
+#         counter += 1
+#         fhCmd.write("volume #%d voxelSize %s\n" % (counter, str(smprt)))
+#         # fhCmd.write("focus\n")
+#         fhCmd.write("vol #%d hide\n" % counter)
+#         fhCmd.write("volume morph #%d,%d frames 500\n" %  (counter-1, counter))
+#         fhCmd.close()
+#
+#         view = ChimeraView(scriptFile)
+#         return [view]
diff --git a/xmipp3/viewers/viewer_volume_subtraction.py b/xmipp3/viewers/viewer_volume_subtraction.py
new file mode 100644
index 000000000..c82eb5921
--- /dev/null
+++ b/xmipp3/viewers/viewer_volume_subtraction.py
@@ -0,0 +1,120 @@
+# **************************************************************************
+# *
+# * Authors:  Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
+# *
+# *
+# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
+# *
+# * 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
+import tempfile
+from distutils.spawn import find_executable
+from os.path import exists, join
+
+import pyworkflow.protocol.params as params
+from pwem.emlib.image import ImageHandler
+from pwem.objects import (SetOfVolumes)
+from pyworkflow.viewer import DESKTOP_TKINTER, WEB_DJANGO
+from pwem.viewers import Chimera, ChimeraView, EmProtocolViewer
+from xmipp3.protocols.protocol_volume_adjust_sub import XmippProtVolSubtraction, XmippProtVolAdjust
+
+VOLUME_SLICES = 1
+VOLUME_CHIMERA = 0
+
+
+class XmippProtVolSubtractionViewer(EmProtocolViewer):
+    """ Visualize the input and output volumes of protocol XmippProtVolumeSubtraction
+        by choosing Chimera (3D) or Xmipp visualizer (2D).
+        The axes of coordinates x, y, z will be shown by choosing Chimera"""
+
+    _label = 'viewer volumes subtraction'
+    _targets = [XmippProtVolSubtraction, XmippProtVolAdjust]
+    _environments = [DESKTOP_TKINTER, WEB_DJANGO]
+
+    def _defineParams(self, form):
+        form.addSection(label='Visualization volumes subtraction output')
+        form.addParam('displayVol', params.EnumParam,
+                      choices=['chimerax', 'slices'], default=VOLUME_CHIMERA,
+                      display=params.EnumParam.DISPLAY_HLIST,
+                      label='Display volume with',
+                      help='*chimerax*: display volumes as surface with '
+                           'ChimeraX.\n*slices*: display volumes as 2D slices '
+                           'along z axis.\n')
+
+    def _getVisualizeDict(self):
+        return {
+            'displayVol': self._showVolumes,
+        }
+
+    def _validate(self):
+        if find_executable(Chimera.getProgram()) is None:
+            return ["chimerax is not available. Either install it or choose"
+                    " option 'slices'. "]
+        return []
+
+    # =========================================================================
+    # Show Volumes
+    # =========================================================================
+
+    def _showVolumes(self, paramName=None):
+        if self.displayVol == VOLUME_CHIMERA:
+            return self._showVolumesChimera()
+        elif self.displayVol == VOLUME_SLICES:
+            return self._showVolumesXmipp()
+
+    def _createSetOfVolumes(self):
+        tmpFileName = join(tempfile.gettempdir(), 'tmpVolumes_adjust.sqlite')
+        if exists(tmpFileName):
+            os.remove(tmpFileName)
+        _outputVol = self.protocol.outputVolume
+        setOfVolumes = SetOfVolumes(filename=tmpFileName)
+        setOfVolumes.append(_outputVol)
+        setOfVolumes.write()
+        return setOfVolumes
+
+    def _showVolumesChimera(self):
+        tmpFileNameCMD = join(tempfile.gettempdir(), "vol_adjust_chimera.cxc")
+        f = open(tmpFileNameCMD, "w")
+        dim = self.protocol.outputVolume.getDim()[0]
+        sampling = self.protocol.outputVolume.getSamplingRate()
+        tmpFileName = os.path.abspath(join(tempfile.gettempdir(), "axis_vol_adjust.bild"))
+        Chimera.createCoordinateAxisFile(dim,
+                                         bildFileName=tmpFileName,
+                                         sampling=sampling)
+        f.write("open %s\n" % tmpFileName)
+        f.write("cofr 0,0,0\n")  # set center of coordinates
+        _outputVol = self.protocol.outputVolume
+        outputVolFileName = os.path.abspath(ImageHandler.removeFileType(
+            _outputVol.getFileName()))
+
+        # output vol origin coordinates
+        x_output, y_output, z_output = self.protocol.outputVolume.getShiftsFromOrigin()
+        f.write("open %s\n" % outputVolFileName)
+        f.write("volume #2 voxelSize %f origin "
+                "%0.2f,%0.2f,%0.2f\n"
+                % (_outputVol.getSamplingRate(), x_output/sampling, y_output/sampling, z_output/sampling))
+        f.close()
+        return [ChimeraView(tmpFileNameCMD)]
+
+    def _showVolumesXmipp(self):
+        setOfVolumes = self._createSetOfVolumes()
+        return [self.objectView(setOfVolumes)]
diff --git a/xmipp3/wizards.py b/xmipp3/wizards.py
index 2f2354656..14a8e3fe7 100644
--- a/xmipp3/wizards.py
+++ b/xmipp3/wizards.py
@@ -28,8 +28,9 @@
 
 from pwem.constants import *
 from pwem.wizards import *
+from pwem.wizards.wizard import EmWizard
 from pyworkflow.wizard import Wizard
-from xmipp3.viewers import XmippMonoResViewer, XmippMonoTomoViewer, XmippResDeepResViewer
+from xmipp3.viewers import XmippMonoResViewer, XmippResDeepResViewer, XmippProtFSOViewer
 
 from .protocols.protocol_cl2d import IMAGES_PER_CLASS
 
@@ -41,7 +42,8 @@
     XmippProtMaskVolumes, XmippProtAlignVolume, XmippProtCL2D,
     XmippProtHelicalParameters, XmippProtConsensusPicking, XmippProtMonoRes,
     XmippProtRotSpectra, XmippProtReconstructHighRes, XmippProtExtractUnit,
-    XmippProtReconstructHeterogeneous, XmippMetaProtDiscreteHeterogeneityScheduler)
+    XmippProtReconstructHeterogeneous, XmippMetaProtDiscreteHeterogeneityScheduler,
+    XmippProtShiftParticles)
 
 
 #===============================================================================
@@ -147,7 +149,6 @@ class XmippBoxSizeWizard(Wizard):
     def show(self, form):
         form.setVar('boxSize', form.protocol.getBoxSize())
 
-
 #===============================================================================
 # CONSENSUS RADIUS
 #===============================================================================
@@ -499,4 +500,21 @@ def show(self, form):
 
 
 class ColorScaleWizard(ColorScaleWizardBase):
-        _targets = ColorScaleWizardBase.defineTargets(XmippMonoResViewer, XmippMonoTomoViewer, XmippResDeepResViewer)
+        _targets = ColorScaleWizardBase.defineTargets(XmippMonoResViewer, XmippResDeepResViewer, XmippProtFSOViewer)
+
+class XmippSelectPointinVolWizard(EmWizard):
+    _targets = [(XmippProtShiftParticles, ['x', 'y', 'z'])]
+
+    def show(self, form):
+        protocol = form.protocol
+        volume = protocol.inputVol.get()
+        if not volume:
+            print('You must specify input volume')
+            return
+        plt = MaskVolumeWizard(volume.getFileName())
+        plt.initializePlot()
+        form.setVar('x', int(plt.origin[2]))
+        form.setVar('y', int(plt.origin[1]))
+        form.setVar('z', int(plt.origin[0]))
+        del plt
+


Test patch:
diff --git a/xmipp3/tests/test_protocols_subtract_projection.py b/xmipp3/legacy/test_protocols_subtract_projection.py
similarity index 100%
rename from xmipp3/tests/test_protocols_subtract_projection.py
rename to xmipp3/legacy/test_protocols_subtract_projection.py
diff --git a/xmipp3/tests/test_protocol_ctf_consensus.py b/xmipp3/tests/test_protocol_ctf_consensus.py
index 1bdd1a679..84afba9bb 100644
--- a/xmipp3/tests/test_protocol_ctf_consensus.py
+++ b/xmipp3/tests/test_protocol_ctf_consensus.py
@@ -26,7 +26,7 @@
 from pyworkflow.tests import BaseTest, DataSet
 import pyworkflow.tests as tests
 
-from pwem.objects import SetOfCTF, CTFModel, Micrograph, SetOfMicrographs
+from pwem.objects import SetOfCTF, CTFModel, Micrograph, SetOfMicrographs, Pointer
 from pwem.protocols import (ProtImportMicrographs, ProtImportCTF,
                             ProtCreateStreamData)
 from pwem.protocols.protocol_create_stream_data import SET_OF_MICROGRAPHS
@@ -157,14 +157,14 @@ def testCtfConsensus2(self):
                   'setof': SET_OF_MICROGRAPHS
                   }
         protStream = self.newProtocol(ProtCreateStreamData, **kwargs)
-        protStream.inputMics.set(protImport.outputMicrographs)
+        protStream.inputMics = Pointer(protImport, extended="outputMicrographs")
         protStream.setObjLabel('create Stream Mic')
         self.proj.launchProtocol(protStream, wait=False)
 
         self._waitOutput(protStream, 'outputMicrographs')
 
         protCTF = self.newProtocol(XmippProtCTFMicrographs)
-        protCTF.inputMicrographs.set(protStream.outputMicrographs)
+        protCTF.inputMicrographs = Pointer(protStream, extended="outputMicrographs")
         self.proj.launchProtocol(protCTF, wait=False)
 
         self._waitOutput(protCTF, 'outputCTF')
@@ -177,7 +177,7 @@ def testCtfConsensus2(self):
             'resolution': 6
         }
         protCTFSel = self.newProtocol(XmippProtCTFConsensus, **kwargsCons1)
-        protCTFSel.inputCTF.set(protCTF.outputCTF)
+        protCTFSel.inputCTF = Pointer(protCTF, extended="outputCTF")
         self.proj.launchProtocol(protCTFSel, wait=False)
 
         self._waitOutput(protCTFSel, 'outputCTF')
@@ -190,7 +190,7 @@ def testCtfConsensus2(self):
             'resolution': 7,
         }
         protCTFSel2 = self.newProtocol(XmippProtCTFConsensus, **kwargsCons2)
-        protCTFSel2.inputCTF.set(protCTFSel.outputCTF)
+        protCTFSel2.inputCTF = Pointer(protCTFSel, extended="outputCTF")
         self.proj.launchProtocol(protCTFSel2)
 
         self._waitOutput(protCTFSel2, 'outputCTF')
diff --git a/xmipp3/tests/test_protocol_extract_asymmetric_unit.py b/xmipp3/tests/test_protocol_extract_asymmetric_unit.py
index 908ca2887..0046f2825 100644
--- a/xmipp3/tests/test_protocol_extract_asymmetric_unit.py
+++ b/xmipp3/tests/test_protocol_extract_asymmetric_unit.py
@@ -491,12 +491,12 @@ def test_extractunitCellHalfIco(self):
 
     # general function to extract the unit cell
     def extractunitCell(self, sym, offset=0, cropZ=False):
-        FlexProtConvertToPseudoAtoms = Domain.importFromPlugin("continuousflex.protocols",
-                                                               "FlexProtConvertToPseudoAtoms",
-                                                                doRaise=True)
-        NMA_MASK_THRE = Domain.importFromPlugin("continuousflex.protocols.pdb.protocol_pseudoatoms_base",
-                                                "NMA_MASK_THRE",
-                                                doRaise=True)
+        # FlexProtConvertToPseudoAtoms = Domain.importFromPlugin("continuousflex.protocols",
+        #                                                        "FlexProtConvertToPseudoAtoms",
+        #                                                         doRaise=True)
+        # NMA_MASK_THRE = Domain.importFromPlugin("continuousflex.protocols.pdb.protocol_pseudoatoms_base",
+        #                                         "NMA_MASK_THRE",
+        #                                         doRaise=True)
         """ extract unit cell from icosahedral phantom
             using xmipp_i2 symmetry
         """
@@ -580,18 +580,18 @@ def extractunitCell(self, sym, offset=0, cropZ=False):
         self.assertTrue(abs(zdim - self.box[sym][2])<2)
 
         # create pdb fileoutput
-        args = {'inputStructure': prot.outputVolume,
-                'maskMode': NMA_MASK_THRE,
-                'maskThreshold': 0.5,
-                'pseudoAtomRadius': 1.5
-                }
-        prot = self.newProtocol(FlexProtConvertToPseudoAtoms, **args)
-        prot.setObjLabel('get pdb')
-        self.launchProtocol(prot)
+        # args = {'inputStructure': prot.outputVolume,
+        #         'maskMode': NMA_MASK_THRE,
+        #         'maskThreshold': 0.5,
+        #         'pseudoAtomRadius': 1.5
+        #         }
+        # prot = self.newProtocol(FlexProtConvertToPseudoAtoms, **args)
+        # prot.setObjLabel('get pdb')
+        # self.launchProtocol(prot)
 
         # check results
-        filenamePdb = prot._getPath('pseudoatoms.pdb')
-        self.assertTrue(os.path.isfile(filenamePdb))
+        # filenamePdb = prot._getPath('pseudoatoms.pdb')
+        # self.assertTrue(os.path.isfile(filenamePdb))
         # delete temporary files
         os.remove(self.filename[sym])
         os.remove(outputFile)
diff --git a/xmipp3/tests/test_protocol_monotomo.py b/xmipp3/tests/test_protocol_monotomo.py
deleted file mode 100644
index 2f07c2e3c..000000000
--- a/xmipp3/tests/test_protocol_monotomo.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# **************************************************************************
-# *
-# * Authors:    Jose Luis Vilas Prieto (jlvilas@cnb.csic.es)
-# *
-# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
-# *
-# * 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'
-# *
-# **************************************************************************
-
-from os.path import exists
-
-from pyworkflow.tests import BaseTest, DataSet, setupTestProject
-from pwem.protocols import ProtImportVolumes
-
-from xmipp3.protocols import XmippProtCropResizeVolumes, XmippProtMonoTomo
-
-class TestMonoTomoBase(BaseTest):
-    @classmethod
-    def setData(cls, dataProject='resmap'):
-        cls.dataset = DataSet.getDataSet(dataProject)
-        cls.map3D = cls.dataset.getFile('betagal')
-        cls.half1 = cls.dataset.getFile('betagal_half1')
-        cls.half2 = cls.dataset.getFile('betagal_half2')
-
-    @classmethod
-    def runImportVolumes(cls, pattern, samplingRate):
-        """ Run an Import volumes protocol. """
-        cls.protImport = cls.newProtocol(ProtImportVolumes,
-                                         filesPath=pattern,
-                                         samplingRate=samplingRate
-                                         )
-        cls.launchProtocol(cls.protImport)
-        cls.protResize = cls.newProtocol(XmippProtCropResizeVolumes,
-                                         inputVolumes=cls.protImport.outputVolume,
-                                         doResize=True,
-                                         resizeOption=2,
-                                         resizeFactor=5)
-        cls.launchProtocol(cls.protResize)
-        return cls.protResize
-
-
-class TestMonoTomo(TestMonoTomoBase):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        TestMonoTomoBase.setData()
-        cls.protImportHalf1 = cls.runImportVolumes(cls.half1, 3.54)
-        cls.protImportHalf2 = cls.runImportVolumes(cls.half2, 3.54)
-
-
-    def testMonoTomo(self):
-        MonoTomo = self.newProtocol(XmippProtMonoTomo,
-                                    objLabel='two halves monotomo',
-                                    inputVolume=self.protImportHalf1.outputVol,
-                                    inputVolume2=self.protImportHalf2.outputVol,
-                                    provideMaskInHalves=True,
-                                    useMask=False,
-                                    minRes=1,
-                                    maxRes=25,
-                                    )
-        self.launchProtocol(MonoTomo)
-        self.assertTrue(exists(MonoTomo._getExtraPath('mgresolution.mrc')),
-                        "MonoTomo has failed")
-
diff --git a/xmipp3/tests/test_protocol_screen_deepConsensus.py b/xmipp3/tests/test_protocol_screen_deepConsensus.py
new file mode 100644
index 000000000..74cb3b38b
--- /dev/null
+++ b/xmipp3/tests/test_protocol_screen_deepConsensus.py
@@ -0,0 +1,184 @@
+# ***************************************************************************
+# *
+# * Authors:     Daniel Del Hoyo (daniel.delhoyo.gomez@alumnos.upm.es)
+# *
+# * 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 time
+
+from pyworkflow.tests import BaseTest, DataSet
+import pyworkflow.tests as tests
+from pyworkflow.protocol import getProtocolFromDb
+from pyworkflow.object import PointerList, Pointer
+
+from pwem.objects import SetOfCTF, CTFModel, Micrograph, SetOfMicrographs
+from pwem.protocols import (ProtImportMicrographs, ProtImportCoordinates,
+                            ProtCreateStreamData)
+from pwem.protocols.protocol_create_stream_data import SET_OF_MICROGRAPHS, SET_OF_COORDINATES
+
+
+from pwem import emlib
+from xmipp3.protocols import XmippProtScreenDeepConsensus
+
+
+class TestXmippProtScreenDeepConsensus(BaseTest):
+    @classmethod
+    def setUpClass(cls):
+        tests.setupTestProject(cls)
+        cls.dsXmipp = DataSet.getDataSet('deepConsensusPicking')
+
+    def _runInportMicrographs(self):
+      protImport = self.newProtocol(ProtImportMicrographs,
+                                    filesPath=self.dsXmipp.getFile('allMics'),
+                                    samplingRate=1.237, voltage=300)
+      protImport.setObjLabel('Import micrographs from xmipp tutorial ')
+      self.launchProtocol(protImport)
+      self.assertIsNotNone(protImport.outputMicrographs.getFileName(),
+                           "There was a problem with the import")
+      return protImport
+
+    def _runImportCoordinates(self, protImportMics, idx = ''):
+      prot = self.newProtocol(ProtImportCoordinates,
+                               importFrom=ProtImportCoordinates.IMPORT_FROM_XMIPP,
+                               filesPath=self.dsXmipp.getFile('autoPickingCoordinates'),
+                               filesPattern='*.pos', boxSize=150,
+                               scale=1.,
+                               invertX=False,
+                               invertY=False
+                               )
+      prot.inputMicrographs.set(protImportMics.outputMicrographs)
+      prot.setObjLabel('Import coords from xmipp {}'.format(idx))
+      self.launchProtocol(prot)
+      self.assertIsNotNone(prot.outputCoordinates,
+                           "There was a problem with the import")
+      return prot
+
+    def _runStreamCoordinates(self, protImportCoords, idx=''):
+      kwargs = {"setof" : SET_OF_COORDINATES,
+                "inputCoordinates" : protImportCoords.outputCoordinates,
+                "nDim" : 20,
+                "creationInterval" : 5,
+                "extraRandomInterval" : 5,
+                "delay" : 0
+                }
+      prot = self.newProtocol(ProtCreateStreamData, **kwargs)
+      prot.setObjLabel('Streaming coords {}'.format(idx))
+      self.proj.launchProtocol(prot, wait=False)
+      return prot
+
+    def _runDeepConsensusPicking(self, inpCoords, kwargs, case = ''):
+      prot = self.newProtocol(XmippProtScreenDeepConsensus,
+                                   inputCoordinates= inpCoords,
+                                   **kwargs)
+
+      prot.setObjLabel('Consensus picking {}'.format(case))
+      self.launchProtocol(prot, wait=True)
+      self.assertIsNotNone(prot.outputCoordinates,
+                           "There was a problem with the consensus")
+      self.assertIsNotNone(prot.outputParticles,
+                           "There was a problem with the consensus")
+      return prot
+
+    def getDeepConsensusKwargs(self, case=1):
+      ADD_MODEL_TRAIN_PRETRAIN = 1
+      ADD_MODEL_TRAIN_PREVRUN = 2
+
+      ADD_DATA_TRAIN_CUST = 2
+
+      ADD_DATA_TRAIN_CUSTOM_OPT_PARTS = 0
+      ADD_DATA_TRAIN_CUSTOM_OPT_COORS = 1
+
+      kwargs = {
+        'nEpochs' : 1.0,
+        'nModels' :2,
+        'extractingBatch':3,
+        'trainingBatch':3,
+        'predictingBatch':3
+      }
+      if case == 1:
+        #Simplest case
+        caseKwargs = {}
+      elif case == 2:
+        #Pretrained model, do testing, additional data particles, do preliminar predictions
+        caseKwargs = {'doPreliminarPredictions': True,
+                      'modelInitialization': ADD_MODEL_TRAIN_PRETRAIN,
+                      'addTrainingData': ADD_DATA_TRAIN_CUST,
+                      'trainingDataType':ADD_DATA_TRAIN_CUSTOM_OPT_PARTS,
+                      'trainTrueSetOfParticles': self.lastRun.outputParticles,
+                      'trainFalseSetOfParticles': self.lastRun.outputParticles
+                      }
+      elif case == 3:
+        #Previous run model, additional data coords
+        caseKwargs = {'modelInitialization':ADD_MODEL_TRAIN_PREVRUN,
+                      'continueRun':self.lastRun,
+                      'doTesting': True,
+                      'testTrueSetOfParticles': self.lastRun.outputParticles,
+                      'testFalseSetOfParticles': self.lastRun.outputParticles,
+                      'addTrainingData': ADD_DATA_TRAIN_CUST,
+                      'trainingDataType': ADD_DATA_TRAIN_CUSTOM_OPT_COORS,
+                      'trainTrueSetOfCoords': self.lastRun.outputCoordinates,
+                      'trainFalseSetOfCoords': self.lastRun.outputCoordinates
+                      }
+      else:
+        caseKwargs={}
+
+      kwargs.update(caseKwargs)
+      return kwargs
+
+
+    def testImportCoordinates(self):
+      nCoordinateSets = 3
+      protImpMics = self._runInportMicrographs()
+
+      for case in range(1,3):
+        protImpCoords = self._runImportCoordinates(protImpMics, case)
+
+        protsStreamCoords = []
+        for i in range(nCoordinateSets):
+          protsStreamCoords.append(self._runStreamCoordinates(protImpCoords, i))
+
+        for i in range(nCoordinateSets):
+          self._waitOutput(protsStreamCoords[i], "outputCoordinates")
+
+        inpCoords = []
+        for prot in protsStreamCoords:
+          point = Pointer(prot, extended="outputCoordinates")
+          inpCoords.append(point)
+        kwargs = self.getDeepConsensusKwargs(case)
+
+        self.lastRun = self._runDeepConsensusPicking(inpCoords, kwargs, case)
+
+
+    def _updateProtocol(self, prot):
+      prot2 = getProtocolFromDb(prot.getProject().path,
+                                prot.getDbPath(),
+                                prot.getObjId())
+      # Close DB connections
+      prot2.getProject().closeMapper()
+      prot2.closeMappers()
+      return prot2
+
+    def checkStreamOutput(self, prots, outputName):
+      for prot in prots:
+        if not prot.hasAttribute(outputName):
+          return False
+      return True
+
+
diff --git a/xmipp3/tests/test_protocol_simulate_ctf.py b/xmipp3/tests/test_protocol_simulate_ctf.py
new file mode 100644
index 000000000..9f09874d3
--- /dev/null
+++ b/xmipp3/tests/test_protocol_simulate_ctf.py
@@ -0,0 +1,96 @@
+# ***************************************************************************
+# *
+# * Authors:     Carlos Oscar Sanchez Sorzano (coss@cnb.csic.es)
+# *              David Herreros Calero (dherreros@cnb.csic.es)
+# *
+# * 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.tests import BaseTest, DataSet
+import pyworkflow.tests as tests
+
+import pyworkflow.utils as pwutils
+
+from pwem.protocols import ProtImportVolumes
+from xmipp3.protocols import XmippProtSimulateCTF, XmippProtCreateGallery
+
+
+class TestXmippProtSimulateCTF(BaseTest):
+    """ Testing CTF simulation
+    """
+    @classmethod
+    def setUpClass(cls):
+        tests.setupTestProject(cls)
+        cls.dataset = DataSet.getDataSet('relion_tutorial')
+        cls.volume = cls.dataset.getFile(os.path.join('volumes', 'reference_masked.vol'))
+
+    def runImportVolume(self, pattern, sampling):
+        """ Run an Import volume protocol. """
+        protImport = self.newProtocol(ProtImportVolumes,
+                                     filesPath=pattern,
+                                     samplingRate=sampling,
+                                     objLabel='relion_tutorial volume ' + pwutils.removeBaseExt(pattern))
+        self.launchProtocol(protImport)
+        if protImport.outputVolume is None:
+            raise Exception('Import of volume: %s, failed. outputVolume is None.' % pattern)
+        return protImport.outputVolume
+
+    def runCreateGallery(self, volume):
+        protCreateGallery = self.newProtocol(XmippProtCreateGallery,
+                                             inputVolume=volume,
+                                             objLabel='Imported Volume Projections')
+        self.launchProtocol(protCreateGallery)
+        if protCreateGallery.outputReprojections is None:
+            raise Exception('Create Gallery faile: no output produced')
+        return protCreateGallery.outputReprojections
+
+    def runSimulateCTF(self, projections, defocus0=5000, defocusF=25000):
+        protSimulateCTF = self.newProtocol(XmippProtSimulateCTF,
+                                           inputParticles=projections,
+                                           Defocus0=defocus0,
+                                           DefocusF=defocusF,
+                                           objLabel='Projections with CTF - [%.2f, %.2f]' % (defocus0, defocusF))
+
+        self.launchProtocol(protSimulateCTF)
+        return protSimulateCTF.outputParticles
+
+    def testSimulateCTF(self):
+        importedVolume = self.runImportVolume(self.volume, 1)
+        projectionsGallery = self.runCreateGallery(importedVolume)
+
+        # Check default defoci values
+        projectionsCTFDefault = self.runSimulateCTF(projectionsGallery)
+        self.assertTrue(projectionsCTFDefault,
+                        "There was a problem with particles output")
+        self.assertEqual(projectionsCTFDefault.getSize(),1647,
+                        "There was a problem with particles output")
+        self.assertEqual(projectionsCTFDefault.getXDim(),60,
+                        "Unexpected particle size in output particles")
+        self.assertEqual(projectionsCTFDefault.getSamplingRate(),1,
+                        "Unexpected sampling rate in output particles")
+        for projection in projectionsCTFDefault.iterItems():
+            defocusU = projection.getCTF().getDefocusU()
+            defocusV = projection.getCTF().getDefocusV()
+            self.assertEqual(defocusU,defocusV,'DefocusU and DeofcusV are not equal but they should be')
+            self.assertGreaterEqual(defocusU,5000, 'DefocusU and DefocusV values are outside the specified range:'
+                                    '[%f, %f]' % (5000, 25000))
+            self.assertLessEqual(defocusU,25000, 'DefocusU and DefocusV values are outside the specified range:'
+                                    '[%f, %f]' % (5000, 25000))
diff --git a/xmipp3/tests/test_protocol_structure_mapping.py b/xmipp3/tests/test_protocol_structure_mapping.py
deleted file mode 100644
index d4d5688b6..000000000
--- a/xmipp3/tests/test_protocol_structure_mapping.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# ***************************************************************************
-# * Authors:     Mohsen Kazemi (mkazemi@cnb.csic.es)
-# *
-# *
-# * 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'
-# ***************************************************************************/
-from pwem import Domain
-from pyworkflow.tests import BaseTest, setupTestProject, DataSet
-from pwem.protocols import ProtImportVolumes
-
-    
-class TestStructureMapping(BaseTest):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        cls.dsRelion = DataSet.getDataSet('relion_tutorial')
-        cls.protImport1 = cls.newProtocol(ProtImportVolumes,
-                                         filesPath=cls.dsRelion.getFile('volumes/reference_rotated_masked.vol'),
-                                         samplingRate=1.0)
-        cls.launchProtocol(cls.protImport1)        
-        cls.protImport2 = cls.newProtocol(ProtImportVolumes,
-                                         filesPath=cls.dsRelion.getFile('volumes/reference_masked.vol'),
-                                         samplingRate=1.0)
-        cls.launchProtocol(cls.protImport2)
-
-
-    
-    def testStructureMapping(self):
-        """ 
-            Test protocol structure mapping
-        """
-        FlexProtStructureMapping = Domain.importFromPlugin(
-            "continuousflex.protocols",
-            "FlexProtStructureMapping",
-            doRaise=True)
-        protStrucMap = self.newProtocol(FlexProtStructureMapping,
-                                objLabel='structure mapping',
-                                numberOfMpi=1, numberOfThreads=4)
-        protStrucMap.inputVolumes.append(self.protImport1.outputVolume)
-        protStrucMap.inputVolumes.append(self.protImport2.outputVolume)
-        protStrucMap.numberOfModes.set(20)
-        protStrucMap.pseudoAtomTarget.set(2.0)
-        protStrucMap.pseudoAtomRadius.set(0.75)
-        protStrucMap.rcPercentage.set(90.0)
-        protStrucMap.collectivityThreshold.set(0.2)
-        self.launchProtocol(protStrucMap)
\ No newline at end of file
diff --git a/xmipp3/tests/test_protocols_deepVolPostprocessing.py b/xmipp3/tests/test_protocols_deepVolPostprocessing.py
index 62999e983..6d96e61af 100644
--- a/xmipp3/tests/test_protocols_deepVolPostprocessing.py
+++ b/xmipp3/tests/test_protocols_deepVolPostprocessing.py
@@ -53,6 +53,20 @@ def runImportVolumes(cls, pattern, samplingRate):
         cls.launchProtocol(cls.protImport)
         return cls.protImport
 
+
+    @classmethod
+    def runImportVolAndHalf(cls, patternVol, fnameHalf1, fnameHalf2, samplingRate):
+        """ Run an Import volumes protocol. """
+        cls.protImport = cls.newProtocol(ProtImportVolumes,
+                                         filesPath=patternVol,
+                                         setHalfMaps=True,
+                                         half1map=fnameHalf1,
+                                         half2map=fnameHalf2,
+                                         samplingRate=samplingRate
+                                         )
+        cls.launchProtocol(cls.protImport)
+        return cls.protImport
+
     @classmethod
     def runCreateMask(cls, pattern, thr):
         """ Create a volume mask. """
@@ -73,15 +87,26 @@ def runCreateMask(cls, pattern, thr):
         return cls.msk
 
 
+
 class TestDeepVolPostProcessing(TestDeepVolPostProcessingBase):
     @classmethod
-    def setUpClass(cls):
+    def setUpClass(cls, samplingRate=1): # Actual samplingRate=3.54 but using 1 to speed up computations
         setupTestProject(cls)
         TestDeepVolPostProcessingBase.setData()
-        cls.protImportVol = cls.runImportVolumes(cls.map3D, 3.54)
-        cls.protImportHalf1 = cls.runImportVolumes(cls.half1, 3.54)
-        cls.protImportHalf2 = cls.runImportVolumes(cls.half2, 3.54)
-        cls.protCreateMask = cls.runCreateMask(cls.protImportVol.outputVolume, 0.02)
+        cls.protImportVol = cls.runImportVolumes(cls.map3D, samplingRate)
+        cls.protImportHalf1 = cls.runImportVolumes(cls.half1, samplingRate)
+        cls.protImportHalf2 = cls.runImportVolumes(cls.half2, samplingRate)
+        cls.protImportVolWithHalfs= cls.runImportVolAndHalf(cls.map3D, cls.half1, cls.half2, samplingRate)
+        # cls.protCreateMask = cls.runCreateMask(cls.protImportVol.outputVolume, 0.02)
+
+    def _checkMeanVal(self, fname, expectedMean, r_delta=0.5):
+      import numpy as np
+      from pwem.emlib import Image
+      imgHandler = Image()
+      imgHandler.read(fname)
+      obtainedVol = imgHandler.getData()
+      central_cube_slice= obtainedVol[40:60,40:60,40:60]
+      self.assertAlmostEqual( np.mean(central_cube_slice), expectedMean, delta=r_delta*abs(expectedMean), msg="Error expected mean volume does not match")
 
     def testDeepVolPostpro1(self):
         deepPostProc = self.newProtocol(XmippProtDeepVolPostProc,
@@ -90,4 +115,32 @@ def testDeepVolPostpro1(self):
                                    )
         self.launchProtocol(deepPostProc)
         self.assertTrue(exists(deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME)),
-                        "Deep Volume Postprocessing has failed")
\ No newline at end of file
+                        "Deep Volume Postprocessing has failed")
+
+        self._checkMeanVal( deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME), 0.1224, r_delta=0.5)
+
+    def testDeepVolPostpro2(self):
+        deepPostProc = self.newProtocol(XmippProtDeepVolPostProc,
+                                        useHalfMapsInsteadVol=True,
+                                        halfMapsAttached=False,
+                                        inputHalf1=self.protImportHalf1.outputVolume,
+                                        inputHalf2=self.protImportHalf1.outputVolume,
+                                        normalization= XmippProtDeepVolPostProc.NORMALIZATION_AUTO
+                                   )
+        self.launchProtocol(deepPostProc)
+        self.assertTrue(exists(deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME)),
+                        "Deep Volume Postprocessing has failed")
+
+        self._checkMeanVal(deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME), 0.1102, r_delta=0.25)
+
+    def testDeepVolPostpro3(self):
+        deepPostProc = self.newProtocol(XmippProtDeepVolPostProc,
+                                        useHalfMapsInsteadVol=True,
+                                        halfMapsAttached=True,
+                                        inputVolume=self.protImportVolWithHalfs.outputVolume,
+                                        normalization= XmippProtDeepVolPostProc.NORMALIZATION_AUTO
+                                   )
+        self.launchProtocol(deepPostProc)
+        self.assertTrue(exists(deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME)),
+                        "Deep Volume Postprocessing has failed")
+        self._checkMeanVal( deepPostProc._getExtraPath(POSTPROCESS_VOL_BASENAME), 0.1102, r_delta=0.5)
diff --git a/xmipp3/tests/test_protocols_fso.py b/xmipp3/tests/test_protocols_fso.py
index 105e1ca42..d7cce7874 100644
--- a/xmipp3/tests/test_protocols_fso.py
+++ b/xmipp3/tests/test_protocols_fso.py
@@ -81,7 +81,14 @@ def setUpClass(cls):
         cls.protCreateMask = cls.runCreateMask(cls.protImportVol.outputVolume, 0.02)
 
     def testfso1(self):
-        fso = self.newProtocol(XmippProtFSO, half1=self.protImportHalf1.outputVolume, half2=self.protImportHalf2.outputVolume, mask=self.protCreateMask.outputMask, bestAngle=True)
+        fso = self.newProtocol(XmippProtFSO, halfVolumesFile=False, half1=self.protImportHalf1.outputVolume, half2=self.protImportHalf2.outputVolume,
+                               mask=self.protCreateMask.outputMask, estimate3DFSC=True)
+        self.launchProtocol(fso)
+        self.assertTrue(exists(fso._getExtraPath("fso.xmd")), "fso with mask has failed")
+
+    def testfs2(self):
+        fso = self.newProtocol(XmippProtFSO, halfVolumesFile=False, half1=self.protImportHalf1.outputVolume,
+                               half2=self.protImportHalf2.outputVolume, estimate3DFSC=True)
         self.launchProtocol(fso)
         self.assertTrue(exists(fso._getExtraPath("fso.xmd")), "fso without mask has failed")
-        
+
diff --git a/xmipp3/tests/test_protocols_gpuCorr_fullStreaming.py b/xmipp3/tests/test_protocols_gpuCorr_fullStreaming.py
index 7b310aa3d..09b30ae27 100644
--- a/xmipp3/tests/test_protocols_gpuCorr_fullStreaming.py
+++ b/xmipp3/tests/test_protocols_gpuCorr_fullStreaming.py
@@ -1,6 +1,7 @@
 # ***************************************************************************
 # *
 # * Authors:     Amaya Jimenez (ajimenez@cnb.csic.es)
+# *              David Strelak (dstrelak@cnb.csic.es)
 # *
 # * 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
@@ -35,16 +36,15 @@
 
 ProtCTFFind = Domain.importFromPlugin('cistem.protocols', 'CistemProtCTFFind',
                                       doRaise=True)
-
-SparxGaussianProtPicking = Domain.importFromPlugin('eman2.protocols',
-                                                   'SparxGaussianProtPicking',
+EmanProtAutopick = Domain.importFromPlugin('eman2.protocols',
+                                                   'EmanProtAutopick',
                                                    doRaise=True)
 
 
 # Number of mics to be processed
 NUM_MICS = 5
 
-class TestGpuCorrFullStreaming(BaseTest):
+class GpuCorrCommon(BaseTest):
     @classmethod
     def setUpClass(cls):
         setupTestProject(cls)
@@ -65,7 +65,7 @@ def importMicrographs(self):
     def importMicrographsStr(self, fnMics):
         kwargs = {'inputMics': fnMics,
                   'nDim': NUM_MICS,
-                  'creationInterval': 30,
+                  'creationInterval': 15,
                   'delay': 10,
                   'setof': SET_OF_MICROGRAPHS  # SetOfMics
                   }
@@ -95,27 +95,28 @@ def calculateCtf(self, inputMics):
 
     def runPicking(self, inputMicrographs):
         """ Run a particle picking. """
-        protPicking = SparxGaussianProtPicking(boxSize=64, lowerThreshold=0.01)
+        protPicking = EmanProtAutopick(boxSize=64, boxerMode=3, gaussLow=0.01)
         protPicking.inputMicrographs.set(inputMicrographs)
         self.proj.launchProtocol(protPicking, wait=False)
 
         return protPicking
 
     def runExtractParticles(self, inputCoord, setCtfs):
-        protExtract = self.newProtocol(XmippProtExtractParticles,
+        self.protExtract = self.newProtocol(XmippProtExtractParticles,
                                        boxSize=64,
                                        doInvert = False,
                                        doFlip = False)
 
-        protExtract.inputCoordinates.set(inputCoord)
-        protExtract.ctfRelations.set(setCtfs)
+        self.protExtract.inputCoordinates.set(inputCoord)
+        self.protExtract.ctfRelations.set(setCtfs)
 
-        self.proj.launchProtocol(protExtract, wait=False)
+        self.proj.launchProtocol(self.protExtract, wait=False)
 
-        return protExtract
+        return self.protExtract
 
     def runClassify(self, inputParts):
-        protClassify = self.newProtocol(XmippProtStrGpuCrrCL2D)
+        protClassify = self.newProtocol(XmippProtStrGpuCrrCL2D,
+                                        numberOfMpi=4)
 
         protClassify.inputParticles.set(inputParts)
         self.proj.launchProtocol(protClassify, wait=False)
@@ -133,85 +134,37 @@ def _updateProtocol(self, prot):
             return prot2
 
 
-
-    def test_pattern(self):
-
+    def run_common_workflow(self):
         protImportMics = self.importMicrographs()
         self.assertFalse(protImportMics.isFailed(), 'ImportMics has failed.')
 
-        protImportMicsStr = self.importMicrographsStr\
-            (protImportMics.outputMicrographs)
-        counter = 1
-        while not protImportMicsStr.hasAttribute('outputMicrographs'):
-            time.sleep(2)
-            protImportMicsStr = self._updateProtocol(protImportMicsStr)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protImportMicsStr.isFailed(), 'Create stream data has failed.')
-        self.assertTrue(protImportMicsStr.hasAttribute('outputMicrographs'),
-                        'Create stream data has no outputMicrographs in more than 3min.')
+        protImportMicsStr = self.importMicrographsStr(protImportMics.outputMicrographs)
+        self._waitOutput(protImportMicsStr,'outputMicrographs')
 
         protInvContr = self.invertContrast(protImportMicsStr.outputMicrographs)
-        counter = 1
-        while not protInvContr.hasAttribute('outputMicrographs'):
-            time.sleep(2)
-            protInvContr = self._updateProtocol(protInvContr)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protInvContr.isFailed(), 'protInvContr has failed.')
-        self.assertTrue(protInvContr.hasAttribute('outputMicrographs'),
-                        'protInvContr has no outputMicrographs in more than 3min.')
+        self._waitOutput(protInvContr,'outputMicrographs')
 
         protCtf = self.calculateCtf(protInvContr.outputMicrographs)
-        counter = 1
-        while not protCtf.hasAttribute('outputCTF'):
-            time.sleep(2)
-            protCtf = self._updateProtocol(protCtf)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protCtf.isFailed(), 'CTFfind4 has failed.')
-        self.assertTrue(protCtf.hasAttribute('outputCTF'),
-                        'CTFfind4 has no outputCTF in more than 3min.')
+        self._waitOutput(protCtf,'outputCTF')
 
         protPicking = self.runPicking(protInvContr.outputMicrographs)
-        counter = 1
-        while not protPicking.hasAttribute('outputCoordinates'):
-            time.sleep(2)
-            protPicking = self._updateProtocol(protPicking)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protPicking.isFailed(), 'Eman Sparx has failed.')
-        self.assertTrue(protPicking.hasAttribute('outputCoordinates'),
-                        'Eman Sparx has no outputCoordinates in more than 3min.')
-
-        protExtract = self.runExtractParticles(protPicking.outputCoordinates,
+        self._waitOutput(protPicking,'outputCoordinates')
+
+        self.runExtractParticles(protPicking.outputCoordinates,
                                                protCtf.outputCTF)
-        counter = 1
-        while not protExtract.hasAttribute('outputParticles'):
-            time.sleep(2)
-            protExtract = self._updateProtocol(protExtract)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protExtract.isFailed(), 'Extract particles has failed.')
-        self.assertTrue(protExtract.hasAttribute('outputParticles'),
-                        'Extract particles has no outputParticles in more than 3min.')
-
-        protClassify = self.runClassify(protExtract.outputParticles)
-        counter = 1
-        while protClassify.getStatus() != STATUS_FINISHED:
-            time.sleep(2)
-            protClassify = self._updateProtocol(protClassify)
-            self.assertFalse(protClassify.isFailed(), 'GL2D-streaming has failed.')
-
-            if counter > 100:
-                self.assertTrue(protClassify.hasAttribute('outputAverages'),
-                                'GL2D-streaming has no outputAverages in more than 3min.')
-            counter += 1
+        self._waitOutput(self.protExtract,'outputParticles')
+
+        self.verify_classification()
+
+
+class TestGpuCorrFullStreaming(GpuCorrCommon):
+    def test_full_streaming(self):
+        self.run_common_workflow()
+
+    def verify_classification(self):
+        protClassify = self.runClassify(self.protExtract.outputParticles)
+
+        self._waitOutput(protClassify, "outputClasses", timeOut=180)
 
         self.assertTrue(protClassify.hasAttribute('outputAverages'),
                         'GL2D-streaming has no outputAverages at the end.')
diff --git a/xmipp3/tests/test_protocols_gpuCorr_semiStreaming.py b/xmipp3/tests/test_protocols_gpuCorr_semiStreaming.py
index a0432f04c..1d90f18f4 100644
--- a/xmipp3/tests/test_protocols_gpuCorr_semiStreaming.py
+++ b/xmipp3/tests/test_protocols_gpuCorr_semiStreaming.py
@@ -1,6 +1,7 @@
 # ***************************************************************************
 # *
 # * Authors:     Amaya Jimenez (ajimenez@cnb.csic.es)
+# *              David Strelak (dstrelak@cnb.csic.es)
 # *
 # * 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
@@ -23,32 +24,14 @@
 
 import time
 
-from pyworkflow.tests import BaseTest, setupTestProject, DataSet
-from pyworkflow.plugin import Domain
-from pwem.protocols import (ProtImportAverages, ProtImportMicrographs,
-                            ProtCreateStreamData)
-from pwem.protocols.protocol_create_stream_data import SET_OF_MICROGRAPHS
-from pyworkflow.protocol import getProtocolFromDb
+from pwem.protocols import ProtImportAverages
 
-from xmipp3.protocols import XmippProtPreprocessMicrographs
-from xmipp3.protocols.protocol_extract_particles import *
+from xmipp3.tests.test_protocols_gpuCorr_fullStreaming import GpuCorrCommon
 from xmipp3.protocols.protocol_classification_gpuCorr_semi import *
 
-ProtCTFFind = Domain.importFromPlugin('cistem.protocols', 'CistemProtCTFFind',
-                                      doRaise=True)
-SparxGaussianProtPicking = Domain.importFromPlugin('eman2.protocols',
-                                                   'SparxGaussianProtPicking',
-                                                   doRaise=True)
-
-
-# Number of mics to be processed
-NUM_MICS = 5
-
-class TestGpuCorrSemiStreaming(BaseTest):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        cls.dsRelion = DataSet.getDataSet('relion_tutorial')
+class TestGpuCorrSemiStreaming(GpuCorrCommon):
+    def test_semi_streaming(self):
+        self.run_common_workflow()
 
     def importAverages(self):
         prot = self.newProtocol(ProtImportAverages,
@@ -59,70 +42,6 @@ def importAverages(self):
 
         return prot
 
-    def importMicrographs(self):
-        prot = self.newProtocol(ProtImportMicrographs,
-                                filesPath=self.dsRelion.getFile('micrographs'),
-                                filesPattern='*.mrc',
-                                samplingRateMode=1,
-                                magnification=79096,
-                                scannedPixelSize=56, voltage=300,
-                                sphericalAberration=2.0)
-        self.launchProtocol(prot)
-
-        return prot
-
-    def importMicrographsStr(self, fnMics):
-        kwargs = {'inputMics': fnMics,
-                  'nDim': NUM_MICS,
-                  'creationInterval': 30,
-                  'delay': 10,
-                  'setof': SET_OF_MICROGRAPHS  # SetOfMics
-                  }
-        protStream = self.newProtocol(ProtCreateStreamData, **kwargs)
-        protStream.setObjLabel('create Stream Mic')
-        self.proj.launchProtocol(protStream, wait=False)
-
-        return protStream
-
-    def invertContrast(self, inputMics):
-        protInvCont = XmippProtPreprocessMicrographs(doInvert=True)
-        protInvCont.inputMicrographs.set(inputMics)
-        self.proj.launchProtocol(protInvCont, wait=False)
-
-        return protInvCont
-
-    def calculateCtf(self, inputMics):
-        protCTF = ProtCTFFind(useCftfind4=True)
-        protCTF.inputMicrographs.set(inputMics)
-        # Gone in new version: protCTF.ctfDownFactor.set(1.0)
-        protCTF.lowRes.set(44)
-        protCTF.highRes.set(15)
-        self.proj.launchProtocol(protCTF, wait=False)
-
-        return protCTF
-
-
-    def runPicking(self, inputMicrographs):
-        """ Run a particle picking. """
-        protPicking = SparxGaussianProtPicking(boxSize=64, lowerThreshold=0.01)
-        protPicking.inputMicrographs.set(inputMicrographs)
-        self.proj.launchProtocol(protPicking, wait=False)
-
-        return protPicking
-
-    def runExtractParticles(self, inputCoord, setCtfs):
-        protExtract = self.newProtocol(XmippProtExtractParticles,
-                                       boxSize=64,
-                                       doInvert = False,
-                                       doFlip = False)
-
-        protExtract.inputCoordinates.set(inputCoord)
-        protExtract.ctfRelations.set(setCtfs)
-
-        self.proj.launchProtocol(protExtract, wait=False)
-
-        return protExtract
-
     def runClassify(self, inputParts, inputAvgs):
         protClassify = self.newProtocol(XmippProtStrGpuCrrSimple,
                                         useAsRef=REF_AVERAGES)
@@ -132,106 +51,18 @@ def runClassify(self, inputParts, inputAvgs):
         self.proj.launchProtocol(protClassify, wait=False)
 
         return protClassify
+       
 
-
-    def _updateProtocol(self, prot):
-            prot2 = getProtocolFromDb(prot.getProject().path,
-                                      prot.getDbPath(),
-                                      prot.getObjId())
-            # Close DB connections
-            prot2.getProject().closeMapper()
-            prot2.closeMappers()
-            return prot2
-
-
-
-    def test_pattern(self):
-
+    def verify_classification(self):
         protImportAvgs = self.importAverages()
-        if protImportAvgs.isFailed():
-            self.assertTrue(False)
-        protImportMics = self.importMicrographs()
-        self.assertFalse(protImportMics.isFailed(), 'ImportMics has failed.')
-
-        protImportMicsStr = self.importMicrographsStr\
-            (protImportMics.outputMicrographs)
-        counter = 1
-        while not protImportMicsStr.hasAttribute('outputMicrographs'):
-            time.sleep(2)
-            protImportMicsStr = self._updateProtocol(protImportMicsStr)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protImportMicsStr.isFailed(), 'Create stream data has failed.')
-        self.assertTrue(protImportMicsStr.hasAttribute('outputMicrographs'),
-                        'Create stream data has no outputMicrographs in more than 3min.')
-
-        protInvContr = self.invertContrast(protImportMicsStr.outputMicrographs)
-        counter = 1
-        while not protInvContr.hasAttribute('outputMicrographs'):
-            time.sleep(2)
-            protInvContr = self._updateProtocol(protInvContr)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protInvContr.isFailed(), 'protInvContr has failed.')
-        self.assertTrue(protInvContr.hasAttribute('outputMicrographs'),
-                        'protInvContr has no outputMicrographs in more than 3min.')
-
-        protCtf = self.calculateCtf(protInvContr.outputMicrographs)
-        counter = 1
-        while not protCtf.hasAttribute('outputCTF'):
-            time.sleep(2)
-            protCtf = self._updateProtocol(protCtf)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protCtf.isFailed(), 'CTFfind4 has failed.')
-        self.assertTrue(protCtf.hasAttribute('outputCTF'),
-                        'CTFfind4 has no outputCTF in more than 3min.')
-
-        protPicking = self.runPicking(protInvContr.outputMicrographs)
-        counter = 1
-        while not protPicking.hasAttribute('outputCoordinates'):
-            time.sleep(2)
-            protPicking = self._updateProtocol(protPicking)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protPicking.isFailed(), 'Eman Sparx has failed.')
-        self.assertTrue(protPicking.hasAttribute('outputCoordinates'),
-                        'Eman Sparx has no outputCoordinates in more than 3min.')
-
-        protExtract = self.runExtractParticles(protPicking.outputCoordinates,
-                                               protCtf.outputCTF)
-        counter = 1
-        while not protExtract.hasAttribute('outputParticles'):
-            time.sleep(2)
-            protExtract = self._updateProtocol(protExtract)
-            if counter > 100:
-                break
-            counter += 1
-        self.assertFalse(protExtract.isFailed(), 'Extract particles has failed.')
-        self.assertTrue(protExtract.hasAttribute('outputParticles'),
-                        'Extract particles has no outputParticles in more than 3min.')
-
-        protClassify = self.runClassify(protExtract.outputParticles,
+        self.assertFalse(protImportAvgs.isFailed())
+        protClassify = self.runClassify(self.protExtract.outputParticles,
                                         protImportAvgs.outputAverages)
-        counter = 1
-        while protClassify.getStatus()!=STATUS_FINISHED:
-            time.sleep(2)
-            protClassify = self._updateProtocol(protClassify)
-            self.assertFalse(protClassify.isFailed(), 'GL2D-static has failed.')
-            if counter > 100:
-                self.assertTrue(protClassify.hasAttribute('outputClasses'),
-                                'GL2D-static has no outputClasses in more than 3min.')
-                self.assertEqual(protClassify.outputClasses.getSize(),
-                                 protImportAvgs.outputAverages.getSize(),
-                                 'GL2D-static returned a wrong number of classes.')
-            counter += 1
+
+        self._waitOutput(protClassify, "outputClasses", timeOut=200)
 
         self.assertTrue(protClassify.hasAttribute('outputClasses'),
                         'GL2D-static has no outputClasses at the end')
         self.assertEqual(protClassify.outputClasses.getSize(),
                          protImportAvgs.outputAverages.getSize(),
-                         'GL2D-static returned a wrong number of classes at the end.')
+                         'GL2D-static returned a wrong number of classes at the end.')
\ No newline at end of file
diff --git a/xmipp3/tests/test_protocols_localdeblur.py b/xmipp3/tests/test_protocols_localdeblur.py
index cd47a3de8..13abdbeac 100644
--- a/xmipp3/tests/test_protocols_localdeblur.py
+++ b/xmipp3/tests/test_protocols_localdeblur.py
@@ -47,6 +47,16 @@ def runImportVolumes(cls, pattern, samplingRate):
         cls.launchProtocol(cls.protImport)
         return cls.protImport
 
+    @classmethod
+    def runImportVolumes2(cls, emdbId):
+        """ Run an Import volumes protocol. """
+        cls.protImport = cls.newProtocol(ProtImportVolumes,
+                                         importFrom=ProtImportVolumes.IMPORT_FROM_EMDB,
+                                         emdbId=emdbId
+                                         )
+        cls.launchProtocol(cls.protImport)
+        return cls.protImport
+
     @classmethod
     def runCreateMask(cls, pattern, thr):
         """ Create a volume mask. """
@@ -66,6 +76,26 @@ def runCreateMask(cls, pattern, thr):
         cls.launchProtocol(cls.msk)
         return cls.msk
 
+    @classmethod
+    def runCreateMask2(cls, pattern, thr):
+        """ Create a volume mask. """
+        cls.msk = cls.newProtocol(XmippProtCreateMask3D,
+                                  inputVolume=pattern,
+                                  volumeOperation=0,  # OPERATION_THRESHOLD,
+                                  threshold=thr,
+                                  doSmall=False,
+                                  smallSize=False,
+                                  doBig=False,
+                                  doSymmetrize=False,
+                                  doMorphological=True,
+                                  morphologicalOperation=0, # MORPHOLOGY_DILATION
+                                  doInvert=False,
+                                  doSmooth=False,
+                                  sigmaConvolution=2
+                                  )
+        cls.launchProtocol(cls.msk)
+        return cls.msk
+
 
 class TestLocalDeblur(TestLocalDeblurBase):
     @classmethod
@@ -74,12 +104,14 @@ def setUpClass(cls):
         TestLocalDeblurBase.setData()
         cls.protImportVol = cls.runImportVolumes(cls.map3D, 3.54)
         cls.protCreateMask = cls.runCreateMask(cls.protImportVol.outputVolume, 0.02)
+        cls.protImportVol2 = cls.runImportVolumes2('3488')
+        cls.protCreateMask2 = cls.runCreateMask2(cls.protImportVol2.outputVolume, 0.167)
 
-    def testLocalDeblur(self):
+    def testLocalDeblur1(self):
         MonoRes = self.newProtocol(XmippProtMonoRes,
                                    objLabel='single volume monores',
-                                   halfVolumes=False,
-                                   inputVolumes=self.protImportVol.outputVolume,
+                                   usehalfVolumes=False,
+                                   fullMap=self.protImportVol.outputVolume,
                                    Mask=self.protCreateMask.outputMask,
                                    minRes=1,
                                    maxRes=25
@@ -97,5 +129,28 @@ def testLocalDeblur(self):
         self.launchProtocol(LocalDeblur)
         self.assertTrue(exists(LocalDeblur._getExtraPath('sharpenedMap_last.mrc')),
                         "LocalDeblur  has failed")
+
+    def testLocalDeblur2(self):
+        MonoRes = self.newProtocol(XmippProtMonoRes,
+                                   objLabel='single volume monores',
+                                   usehalfVolumes=False,
+                                   fullMap=self.protImportVol2.outputVolume,
+                                   Mask=self.protCreateMask2.outputMask,
+                                   minRes=1,
+                                   maxRes=6
+                                   )
+        self.launchProtocol(MonoRes)
+        self.assertTrue(exists(MonoRes._getExtraPath('monoresResolutionMap.mrc')),
+                        "MonoRes (no split, no premasked) has failed")
+
+        LocalDeblur = self.newProtocol(XmippProtLocSharp,
+                                   objLabel='sharpening localdeblur',
+                                   inputVolume=self.protImportVol2.outputVolume,
+                                   resolutionVolume=MonoRes.resolution_Volume,
+                                   const=1,
+                                   )
+        self.launchProtocol(LocalDeblur)
+        self.assertTrue(exists(LocalDeblur._getExtraPath('sharpenedMap_last.mrc')),
+                        "LocalDeblur  has failed")
  
  
diff --git a/xmipp3/tests/test_protocols_monores.py b/xmipp3/tests/test_protocols_monores.py
index 05aef2e84..a20e75777 100644
--- a/xmipp3/tests/test_protocols_monores.py
+++ b/xmipp3/tests/test_protocols_monores.py
@@ -81,9 +81,10 @@ def setUpClass(cls):
     def testMonoRes1(self):
         MonoRes = self.newProtocol(XmippProtMonoRes,
                                    objLabel='single volume monores',
-                                   halfVolumes=False,
-                                   inputVolumes=self.protImportVol.outputVolume,
-                                   Mask=self.protCreateMask.outputMask,
+                                   useHalfVolumes=False,
+                                   hasHalfVolumesFile=False,
+                                   fullMap=self.protImportVol.outputVolume,
+                                   mask=self.protCreateMask.outputMask,
                                    minRes=1,
                                    maxRes=25,
                                    )
@@ -94,11 +95,12 @@ def testMonoRes1(self):
     def testMonoRes2(self):
         MonoRes = self.newProtocol(XmippProtMonoRes,
                                    objLabel='two halves monores',
-                                   halfVolumes=True,
-                                   inputVolume=self.protImportHalf1.outputVolume,
-                                   inputVolume2=self.protImportHalf2.outputVolume,
+                                   useHalfVolumes=True,
+                                   hasHalfVolumesFile=False,
+                                   halfMap1=self.protImportHalf1.outputVolume,
+                                   halfMap2=self.protImportHalf2.outputVolume,
                                    provideMaskInHalves=True,
-                                   Mask=self.protCreateMask.outputMask,
+                                   mask=self.protCreateMask.outputMask,
                                    minRes=1,
                                    maxRes=25,
                                    )
diff --git a/xmipp3/tests/test_protocols_realignment_classes.py b/xmipp3/tests/test_protocols_realignment_classes.py
index 6902720a4..b95d6c7f0 100644
--- a/xmipp3/tests/test_protocols_realignment_classes.py
+++ b/xmipp3/tests/test_protocols_realignment_classes.py
@@ -31,9 +31,9 @@
 
 ProtCTFFind = Domain.importFromPlugin('cistem.protocols', 'CistemProtCTFFind',
                                       doRaise=True)
-SparxGaussianProtPicking = Domain.importFromPlugin('eman2.protocols',
-                                                   'SparxGaussianProtPicking',
-                                                   doRaise=True)
+EmanProtAutopick = Domain.importFromPlugin('eman2.protocols',
+                                                   'EmanProtAutopick',
+                                           doRaise=True)
 
 
 # Number of mics to be processed
@@ -81,10 +81,11 @@ def calculateCtf(self, inputMics):
 
     def runPicking(self, inputMicrographs):
         """ Run a particle picking. """
-        protPicking = SparxGaussianProtPicking(boxSize=64,
-                                               numberOfThreads=1,
-                                               numberOfMpi=1,
-                                               lowerThreshold=0.001)
+        protPicking = EmanProtAutopick(boxSize=64,
+                                       numberOfThreads=1,
+                                       numberOfMpi=1,
+                                       boxerMode=3,
+                                       gaussLow=0.001)
         protPicking.inputMicrographs.set(inputMicrographs)
         self.launchProtocol(protPicking)
 
diff --git a/xmipp3/tests/test_protocols_sph.py b/xmipp3/tests/test_protocols_sph.py
index e235d1fe6..b10928baa 100644
--- a/xmipp3/tests/test_protocols_sph.py
+++ b/xmipp3/tests/test_protocols_sph.py
@@ -1,3 +1,12 @@
+########
+
+# This test has been removed as the protocols associated to it are no longer offered in Scipion.
+# This will be solved in a future release.
+
+########
+
+
+
 # **************************************************************************
 # *
 # * Authors:    Carlos Oscar Sorzano (coss@cnb.csic.es)
@@ -24,113 +33,113 @@
 # *
 # **************************************************************************
 
-from pyworkflow.tests import BaseTest, DataSet, setupTestProject
-
-from pwem.protocols import ProtImportVolumes, ProtImportParticles, ProtSubSet
-
-from xmipp3.protocols import (XmippProtAngularAlignmentSPH,
-                              XmippProtVolumeDeformSPH,
-                              XmippProtStructureMapSPH)
-
-class TestSPHBase(BaseTest):
-    @classmethod
-    def setData(cls):
-        cls.dataset = DataSet.getDataSet('relion_tutorial')
-        cls.particles = cls.dataset.getFile('import/case2/particles.sqlite')
-        cls.volParticles = cls.dataset.getFile('volume')
-        cls.volInput = cls.dataset.getFile('volumes/reference_rotated.vol')
-        cls.volRef = cls.dataset.getFile('volumes/reference.mrc')
-
-    @classmethod
-    def runImportVolume(cls, pattern, sampling):
-        """ Run an Import volume protocol. """
-        protImport = cls.newProtocol(ProtImportVolumes,
-                                          filesPath=pattern,
-                                          samplingRate=sampling)
-        cls.launchProtocol(protImport)
-        if protImport.outputVolume is None:
-            raise Exception('Import of volume: %s, failed. outputVolume is None.' % pattern)
-        return protImport
-
-    @classmethod
-    def runImportParticles(cls, pattern, sampling):
-        """ Run an Import particles protocol. """
-        protImport = cls.newProtocol(ProtImportParticles,
-                                 objLabel='Particles from scipion',
-                                 importFrom=ProtImportParticles.IMPORT_FROM_SCIPION,
-                                 sqliteFile=pattern,
-                                 magnification=50000,
-                                 samplingRate=sampling,
-                                 haveDataBeenPhaseFlipped=True
-                                 )
-        cls.launchProtocol(protImport)
-        if protImport.outputParticles is None:
-            raise Exception('Import of particles: %s, failed. outputParticles is None.' % pattern)
-        return protImport
-
-
-
-class TestAngularAlignmentSPH(TestSPHBase):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        TestSPHBase.setData()
-        cls.protImportPart = cls.runImportParticles(cls.particles, 7.08)
-        cls.protImportVol = cls.runImportVolume(cls.volParticles, 7.08)
-
-    def test(self):
-        subset = self.newProtocol(ProtSubSet,
-                                  inputFullSet=self.protImportPart.outputParticles,
-                                  chooseAtRandom=True,
-                                  nElements=10)
-        self.launchProtocol(subset)
-
-        alignSph = self.newProtocol(XmippProtAngularAlignmentSPH,
-                                   inputParticles=subset.outputParticles,
-                                   inputVolume=self.protImportVol.outputVolume,
-                                   depth=1,
-                                   targetResolution = 4.0,
-                                   numberOfMpi=8)
-        self.launchProtocol(alignSph)
-        self.assertIsNotNone(alignSph.outputParticles,
-                             "There was a problem with AngularAlignmentSPH")
-
-
-class TestVolumeDeformSPH(TestSPHBase):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        TestSPHBase.setData()
-        cls.protImportVol = cls.runImportVolume(cls.volInput, 1.0)
-        cls.protImportVolRef = cls.runImportVolume(cls.volRef, 1.0)
-
-    def test(self):
-
-        volDeformSph = self.newProtocol(XmippProtVolumeDeformSPH,
-                                   inputVolume=self.protImportVol.outputVolume,
-                                   refVolume = self.protImportVolRef.outputVolume,
-                                   depth=2,
-                                   targetResolution = 4.0)
-        self.launchProtocol(volDeformSph)
-        self.assertIsNotNone(volDeformSph.outputVolume,
-                             "There was a problem with VolumeDeformSPH")
-
-
-
-class TestStructureMapSPH(TestSPHBase):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        TestSPHBase.setData()
-        cls.protImportVol1 = cls.runImportVolume(cls.volInput, 1.0)
-        cls.protImportVol2 = cls.runImportVolume(cls.volRef, 1.0)
-
-    def test(self):
-
-        structureMap = self.newProtocol(XmippProtStructureMapSPH,
-                                   depth=2,
-                                   targetResolution = 4.0)
-        volumeList = [self.protImportVol1.outputVolume, self.protImportVol2.outputVolume]
-        structureMap.inputVolumes.set(volumeList)
-        self.launchProtocol(structureMap)
-        self.assertTrue(structureMap.isFinished(), "There was a problem with StructureMapSPH")
\ No newline at end of file
+# from pyworkflow.tests import BaseTest, DataSet, setupTestProject
+#
+# from pwem.protocols import ProtImportVolumes, ProtImportParticles, ProtSubSet
+#
+# from xmipp3.protocols import (XmippProtAngularAlignmentSPH,
+#                               XmippProtVolumeDeformSPH,
+#                               XmippProtStructureMapSPH)
+#
+# class TestSPHBase(BaseTest):
+#     @classmethod
+#     def setData(cls):
+#         cls.dataset = DataSet.getDataSet('relion_tutorial')
+#         cls.particles = cls.dataset.getFile('import/case2/particles.sqlite')
+#         cls.volParticles = cls.dataset.getFile('volume')
+#         cls.volInput = cls.dataset.getFile('volumes/reference_rotated.vol')
+#         cls.volRef = cls.dataset.getFile('volumes/reference.mrc')
+#
+#     @classmethod
+#     def runImportVolume(cls, pattern, sampling):
+#         """ Run an Import volume protocol. """
+#         protImport = cls.newProtocol(ProtImportVolumes,
+#                                           filesPath=pattern,
+#                                           samplingRate=sampling)
+#         cls.launchProtocol(protImport)
+#         if protImport.outputVolume is None:
+#             raise Exception('Import of volume: %s, failed. outputVolume is None.' % pattern)
+#         return protImport
+#
+#     @classmethod
+#     def runImportParticles(cls, pattern, sampling):
+#         """ Run an Import particles protocol. """
+#         protImport = cls.newProtocol(ProtImportParticles,
+#                                  objLabel='Particles from scipion',
+#                                  importFrom=ProtImportParticles.IMPORT_FROM_SCIPION,
+#                                  sqliteFile=pattern,
+#                                  magnification=50000,
+#                                  samplingRate=sampling,
+#                                  haveDataBeenPhaseFlipped=True
+#                                  )
+#         cls.launchProtocol(protImport)
+#         if protImport.outputParticles is None:
+#             raise Exception('Import of particles: %s, failed. outputParticles is None.' % pattern)
+#         return protImport
+#
+#
+#
+# class TestAngularAlignmentSPH(TestSPHBase):
+#     @classmethod
+#     def setUpClass(cls):
+#         setupTestProject(cls)
+#         TestSPHBase.setData()
+#         cls.protImportPart = cls.runImportParticles(cls.particles, 7.08)
+#         cls.protImportVol = cls.runImportVolume(cls.volParticles, 7.08)
+#
+#     def test(self):
+#         subset = self.newProtocol(ProtSubSet,
+#                                   inputFullSet=self.protImportPart.outputParticles,
+#                                   chooseAtRandom=True,
+#                                   nElements=10)
+#         self.launchProtocol(subset)
+#
+#         alignSph = self.newProtocol(XmippProtAngularAlignmentSPH,
+#                                    inputParticles=subset.outputParticles,
+#                                    inputVolume=self.protImportVol.outputVolume,
+#                                    depth=1,
+#                                    targetResolution = 4.0,
+#                                    numberOfMpi=8)
+#         self.launchProtocol(alignSph)
+#         self.assertIsNotNone(alignSph.outputParticles,
+#                              "There was a problem with AngularAlignmentSPH")
+#
+#
+# class TestVolumeDeformSPH(TestSPHBase):
+#     @classmethod
+#     def setUpClass(cls):
+#         setupTestProject(cls)
+#         TestSPHBase.setData()
+#         cls.protImportVol = cls.runImportVolume(cls.volInput, 1.0)
+#         cls.protImportVolRef = cls.runImportVolume(cls.volRef, 1.0)
+#
+#     def test(self):
+#
+#         volDeformSph = self.newProtocol(XmippProtVolumeDeformSPH,
+#                                    inputVolume=self.protImportVol.outputVolume,
+#                                    refVolume = self.protImportVolRef.outputVolume,
+#                                    depth=2,
+#                                    targetResolution = 4.0)
+#         self.launchProtocol(volDeformSph)
+#         self.assertIsNotNone(volDeformSph.outputVolume,
+#                              "There was a problem with VolumeDeformSPH")
+#
+#
+#
+# class TestStructureMapSPH(TestSPHBase):
+#     @classmethod
+#     def setUpClass(cls):
+#         setupTestProject(cls)
+#         TestSPHBase.setData()
+#         cls.protImportVol1 = cls.runImportVolume(cls.volInput, 1.0)
+#         cls.protImportVol2 = cls.runImportVolume(cls.volRef, 1.0)
+#
+#     def test(self):
+#
+#         structureMap = self.newProtocol(XmippProtStructureMapSPH,
+#                                    depth=2,
+#                                    targetResolution = 4.0)
+#         volumeList = [self.protImportVol1.outputVolume, self.protImportVol2.outputVolume]
+#         structureMap.inputVolumes.set(volumeList)
+#         self.launchProtocol(structureMap)
+#         self.assertTrue(structureMap.isFinished(), "There was a problem with StructureMapSPH")
\ No newline at end of file
diff --git a/xmipp3/tests/test_protocols_xmipp_2d.py b/xmipp3/tests/test_protocols_xmipp_2d.py
index 09fd07832..b76b95ce6 100644
--- a/xmipp3/tests/test_protocols_xmipp_2d.py
+++ b/xmipp3/tests/test_protocols_xmipp_2d.py
@@ -1304,37 +1304,6 @@ def test_CorrectWiener(self):
         self.launchProtocol(protCorrect)
         self.assertIsNotNone(protCorrect.outputParticles, "There was a problem with Wiener Correction")
 
-        
-class TestXmippSubtractProjection(TestXmippBase):
-    @classmethod
-    def setUpClass(cls):
-        setupTestProject(cls)
-        cls.dsRelion = DataSet.getDataSet('relion_tutorial')
-    
-    def test_subtract(self):
-        protParts = self.newProtocol(emprot.ProtImportParticles,
-                                     objLabel='from relion auto-refine',
-                                     importFrom=emprot.ProtImportParticles.IMPORT_FROM_RELION,
-                                     starFile=self.dsRelion.getFile('import/refine3d/extra/relion_it001_data.star'),
-                                     magnification=10000,
-                                     samplingRate=7.08,
-                                     haveDataBeenPhaseFlipped=True
-                                     )
-        self.launchProtocol(protParts)
-        self.assertEqual(60, protParts.outputParticles.getXDim())
-        
-        protVol = self.newProtocol(emprot.ProtImportVolumes,
-                                   filesPath=self.dsRelion.getFile('volumes/reference.mrc'),
-                                   samplingRate=7.08)
-        self.launchProtocol(protVol)
-        self.assertEqual(60, protVol.outputVolume.getDim()[0])
-        
-        protSubtract = self.newProtocol(XmippProtSubtractProjection)
-        protSubtract.inputParticles.set(protParts.outputParticles)
-        protSubtract.inputVolume.set(protVol.outputVolume)
-        self.launchProtocol(protSubtract)
-        self.assertIsNotNone(protSubtract.outputParticles, "There was a problem with subtract projection")
-        
 
 if __name__ == "__main__":
     if len(sys.argv) > 1:
diff --git a/xmipp3/tests/test_protocols_xmipp_3d.py b/xmipp3/tests/test_protocols_xmipp_3d.py
index aad09116d..141b84168 100644
--- a/xmipp3/tests/test_protocols_xmipp_3d.py
+++ b/xmipp3/tests/test_protocols_xmipp_3d.py
@@ -1043,11 +1043,13 @@ def setUpClass(cls):
     
     def testXmippPdbConvertFromDb(self):
         print("Run convert a pdb from database")
-        protConvert = self.newProtocol(XmippProtConvertPdb, pdbId="3j3i", sampling=4, setSize=True, size=100)
+        protConvert = self.newProtocol(XmippProtConvertPdb, pdbId="3j3i", sampling=4, setSize=True,
+                                       size_z=100, size_y=100, size_x=100)
         self.launchProtocol(protConvert)
         self.assertIsNotNone(protConvert.outputVolume.getFileName(), "There was a problem with the conversion")
-        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1, msg="wrong sampling rate")
-        self.assertAlmostEqual(protConvert.outputVolume.getDim()[0], protConvert.size.get(), places=1, msg="wrong size")
+        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1,
+                               msg="wrong sampling rate")
+        self.assertAlmostEqual(protConvert.outputVolume.getDim()[0], protConvert.size_z.get(), places=1, msg="wrong size")
         
     def testXmippPdbConvertFromObj(self):
         print("Run convert a pdb from import")
@@ -1059,19 +1061,21 @@ def testXmippPdbConvertFromObj(self):
         
         protConvert = self.newProtocol(XmippProtConvertPdb, 
                                        inputPdbData=XmippProtConvertPdb.IMPORT_OBJ, 
-                                       sampling=3, setSize=True, size=20)
+                                       sampling=3, setSize=True, size_z=20, size_y=20, size_x=20)
         protConvert.pdbObj.set(protImport.outputPdb)
         self.launchProtocol(protConvert)
         self.assertIsNotNone(protConvert.outputVolume.getFileName(), "There was a problem with the conversion")
-        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1, msg="wrong sampling rate")
-        self.assertAlmostEqual(protConvert.outputVolume.getDim()[0], protConvert.size.get(), places=1, msg="wrong size")
+        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1,
+                               msg="wrong sampling rate")
+        self.assertAlmostEqual(protConvert.outputVolume.getDim()[0], protConvert.size_z.get(), places=1, msg="wrong size")
 
     def testXmippPdbConvertFromFn(self):
         print("Run convert a pdb from file")
-        protConvert = self.newProtocol(XmippProtConvertPdb,inputPdbData=2, pdbFile=self.pdb, sampling=2, setSize=True)
+        protConvert = self.newProtocol(XmippProtConvertPdb,inputPdbData=2, pdbFile=self.pdb, sampling=2, setSize=False)
         self.launchProtocol(protConvert)
         self.assertIsNotNone(protConvert.outputVolume.getFileName(), "There was a problem with the conversion")
-        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1, msg="wrong sampling rate")
+        self.assertAlmostEqual(protConvert.outputVolume.getSamplingRate(), protConvert.sampling.get(), places=1,
+                               msg="wrong sampling rate")
         self.assertAlmostEqual(protConvert.outputVolume.getDim()[0], 48, places=1, msg="wrong size")
 
 
@@ -1148,3 +1152,40 @@ def testXmippValidateNonTilt(self):
             print("Test: '%s' not found." % className)
     else:
         unittest.main()
+
+
+class TestXmippVolSubtraction(TestXmippBase):
+
+    @classmethod
+    def setUpClass(cls):
+        setupTestProject(cls)
+        cls.dataset = DataSet.getDataSet('xmipp_tutorial')
+        cls.vol1 = cls.dataset.getFile('volumes/volume_1_iter_002.mrc')
+        cls.vol2 = cls.dataset.getFile('volumes/volume_2_iter_002.mrc')
+
+    def testXmippVolSub(self):
+        print("Import Volume 1")
+        protImportVol1 = self.newProtocol(ProtImportVolumes,
+                                         objLabel='Volume',
+                                         filesPath=self.vol1,
+                                         samplingRate=7.08)
+        self.launchProtocol(protImportVol1)
+        self.assertIsNotNone(protImportVol1.getFiles(),
+                             "There was a problem with the import 1")
+        print("Import Volume 2")
+        protImportVol2 = self.newProtocol(ProtImportVolumes,
+                                         objLabel='Volume',
+                                         filesPath=self.vol2,
+                                         samplingRate=7.08)
+        self.launchProtocol(protImportVol2)
+        self.assertIsNotNone(protImportVol2.getFiles(),
+                             "There was a problem with the import 2")
+        print("Run volume subtraction")
+        protVolSub = self.newProtocol(XmippProtVolSubtraction,
+                                      vol1=protImportVol1.outputVolume,
+                                      vol2=protImportVol2.outputVolume,
+                                      pdb=False,
+                                      masks=False)
+        self.launchProtocol(protVolSub)
+        self.assertIsNotNone(protVolSub.outputVolume,
+                             "There was a problem with Volumes subtraction")
diff --git a/xmipp3/tests/test_protocols_xmipp_mics.py b/xmipp3/tests/test_protocols_xmipp_mics.py
index f6051bf10..8bd4eaad8 100644
--- a/xmipp3/tests/test_protocols_xmipp_mics.py
+++ b/xmipp3/tests/test_protocols_xmipp_mics.py
@@ -188,7 +188,7 @@ def setUpClass(cls):
     def testDownsampling(self):
         # test downsampling a set of micrographs
         downFactorValue = 2
-        protDown = XmippProtPreprocessMicrographs(doDownsample=True, downFactor=downFactorValue)
+        protDown = XmippProtPreprocessMicrographs(doDownsample=True, downFactor=downFactorValue, objLabel='Downsampling')
         protDown.inputMicrographs.set(self.protImport.outputMicrographs)
         self.proj.launchProtocol(protDown, wait=True)
         # check that output micrographs have double sampling rate than input micrographs
@@ -198,21 +198,22 @@ def testDownsampling(self):
     def testPreprocessing(self):
         # test Crop, Take logarithm and Remove bad pixels on a set of micrographs
         cropPixels = 100
-        protPreprocess = XmippProtPreprocessMicrographs(doCrop=True, doLog=True, doRemoveBadPix=True, cropPixels=cropPixels)
+        protPreprocess = XmippProtPreprocessMicrographs(doCrop=True, doLog=True, doRemoveBadPix=True,
+                                                        cropPixels=cropPixels, objLabel='Crop, Log and bad pixels')
         protPreprocess.inputMicrographs.set(self.protImport.outputMicrographs)
         self.proj.launchProtocol(protPreprocess, wait=True)
         self.assertIsNotNone(protPreprocess.outputMicrographs, "SetOfMicrographs has not been preprocessed.")
 
     def testInvertNormalize(self):
         # test invert and normalize a set of micrographs
-        protInvNorm = XmippProtPreprocessMicrographs(doInvert=True, doNormalize=True)
+        protInvNorm = XmippProtPreprocessMicrographs(doInvert=True, doNormalize=True, objLabel='Invert and normalize')
         protInvNorm.inputMicrographs.set(self.protImport.outputMicrographs)
         self.proj.launchProtocol(protInvNorm, wait=True)
         self.assertIsNotNone(protInvNorm.outputMicrographs, "SetOfMicrographs has not been preprocessed.")
 
     def testSmooth(self):
         # test smooth a set of micrographs
-        protSmooth = XmippProtPreprocessMicrographs(doSmooth=True, sigmaConvolution=3)
+        protSmooth = XmippProtPreprocessMicrographs(doSmooth=True, sigmaConvolution=3, objLabel='Smooth')
         protSmooth.inputMicrographs.set(self.protImport.outputMicrographs)
         self.proj.launchProtocol(protSmooth, wait=True)
         self.assertIsNotNone(protSmooth.outputMicrographs, "SetOfMicrographs has not been preprocessed.")
@@ -242,13 +243,13 @@ def testStreaming(self):
         protStream = self.newProtocol(ProtCreateStreamData, **kwargs)
         self.proj.launchProtocol(protStream, wait=False)
 
-        while not protStream.hasAttribute('outputMicrographs'):
-            time.sleep(3)
-            protStream = self._updateProtocol(protStream)
+        self._waitOutput(protStream,'outputMicrographs')
 
         protDenoise = self.newProtocol(XmippProtPreprocessMicrographs,
-                                       doDenoise=True, maxIteration=50)
-        protDenoise.inputMicrographs.set(protStream.outputMicrographs)
+                                       doDenoise=True, maxIteration=50,
+                                       objLabel= 'denoise in streaming')
+        protDenoise.inputMicrographs.set(protStream)
+        protDenoise.inputMicrographs.setExtended('outputMicrographs')
         self.proj.launchProtocol(protDenoise)
 
 
@@ -876,14 +877,21 @@ def setUpClass(cls):
         cls.protPP = cls.runFakedPicking(cls.protDown.outputMicrographs,
                                          cls.allCrdsDir)
 
-    def _updateProtocol(self, prot):
-        prot2 = getProtocolFromDb(prot.getProject().path,
-                                  prot.getDbPath(),
-                                  prot.getObjId())
+    def _updateProtocol(self, protocol):
+        jobId = protocol.getJobId()
+        label = protocol.getObjLabel()
+        comment = protocol.getObjComment()
+
+        prot2 = getProtocolFromDb(protocol.getProject().path,
+                                  protocol.getDbPath(),
+                                  protocol.getObjId())
+        protocol.copy(prot2, copyId=False, excludeInputs=True)
+        protocol.setJobId(jobId)
+        protocol.setObjLabel(label)
+        protocol.setObjComment(comment)
         # Close DB connections
         prot2.getProject().closeMapper()
         prot2.closeMappers()
-        return prot2
 
     def testStreamingAndNonStreaming(self):
         protExtract = self.newProtocol(XmippProtExtractParticles,
@@ -907,30 +915,29 @@ def testStreamingAndNonStreaming(self):
                         "Output sets size does not much the input set size.")
 
         kwargs = {'nDim': 20,  # 20 objects/particles
-                  'creationInterval': 1,  # wait 1 sec. after creation
+                  'creationInterval': 10,  # wait 1 sec. after creation
+                  'groups': 10,
                   'setof': 3,  # create SetOfParticles
                   'inputParticles': protExtract.outputParticles
                   }
 
         protStream = self.newProtocol(ProtCreateStreamData, **kwargs)
         self.proj.launchProtocol(protStream, wait=False)
-
-        count = 0
-        while not protStream.hasAttribute('outputParticles') and count < 600:
-            time.sleep(1)
-            protStream = self._updateProtocol(protStream)
+        self._waitOutput(protStream, "outputParticles")
 
         protElimination2 = self.newProtocol(XmippProtEliminateEmptyParticles)
-        protElimination2.inputParticles.set(protStream.outputParticles)
+        protElimination2.inputParticles.set(protStream)
+        protElimination2.inputParticles.setExtended("outputParticles")
         self.launchProtocol(protElimination2)
 
+
         partSet = SetOfParticles(
             filename=protStream._getPath("particles.sqlite"))
         outSet = SetOfParticles(
             filename=protElimination2._getPath('outputParticles.sqlite'))
         elimSet = SetOfParticles(
             filename=protElimination2._getPath('eliminatedParticles.sqlite'))
-        self.assertTrue(outSet.getSize() + elimSet.getSize() ==
+        self.assertEquals(outSet.getSize() + elimSet.getSize(),
                         partSet.getSize(),
                         "Output sets size does not much the input set size.")
 
@@ -998,19 +1005,6 @@ def testStreamingAndNonStreaming(self):
         self.assertSetSize(protConsOr.consensusCoordinates, 432,
                         "Output coordinates size for OR consensus is wrong.")
 
-
-        # STREAMING tests
-        def waitForOutput(prot, output, timeout=600):
-            """ Waits until the output is ready
-                or till timeout (in seconds) is reached (10min by default).
-            """
-            count = 0
-            while not prot.hasAttribute(output) and count < timeout:
-                prot = self._updateProtocol(prot)
-                time.sleep(1)
-                count += 1
-            return prot
-
         kwargs = {'nDim': 3,  # 3 objects
                   'creationInterval': 20,  # wait 1 sec. after creation
                   'setof': 1,  # create SetOfMicrographs
@@ -1019,15 +1013,14 @@ def waitForOutput(prot, output, timeout=600):
 
         protStream = self.newProtocol(ProtCreateStreamData, **kwargs)
         self.proj.launchProtocol(protStream, wait=False)
-
-        protStream = waitForOutput(protStream, 'outputMicrographs')
+        self._waitOutput(protStream, 'outputMicrographs')
 
         protAutoPP = XmippParticlePickingAutomatic()
         protAutoPP.xmippParticlePicking.set(self.protFaPi)
         protAutoPP.micsToPick.set(1)
         protAutoPP.inputMicrographs.set(protStream.outputMicrographs)
         self.proj.launchProtocol(protAutoPP, wait=False)
-        protAutoPP = waitForOutput(protAutoPP, 'outputCoordinates')
+        self._waitOutput(protAutoPP, 'outputCoordinates')
 
         # Consensus Picking launching
         protCons2 = self.newProtocol(XmippProtConsensusPicking,
diff --git a/xmipp3/tests/test_protocols_xmipp_movie_resize.py b/xmipp3/tests/test_protocols_xmipp_movie_resize.py
index d18a010b9..186706d7e 100644
--- a/xmipp3/tests/test_protocols_xmipp_movie_resize.py
+++ b/xmipp3/tests/test_protocols_xmipp_movie_resize.py
@@ -46,32 +46,21 @@ class TestMovieResize(BaseTest):
     def setUpClass(cls):
         setupTestProject(cls)
         cls.dataset = DataSet.getDataSet('movies')
+        cls.dataFile = cls.dataset.getFile('ribo/')
         cls.movie1 = cls.dataset.getFile('qbeta/qbeta.mrc')
         cls.movie2 = cls.dataset.getFile('cct/cct_1.em')
 
-    def _updateProtocol(self, prot):
-        prot2 = getProtocolFromDb(prot.getProject().path,
-                                  prot.getDbPath(),
-                                  prot.getObjId())
-        # Close DB connections
-        prot2.getProject().closeMapper()
-        prot2.closeMappers()
-        return prot2
-
-
-    def runImportMoviesRibo(cls):
-        args = {'filesPath': cls.dataset.getFile('ribo/'),
+    def runImportMoviesRibo(self):
+        args = {'filesPath': self.dataFile,
                 'filesPattern': '*.mrcs',
                 'amplitudConstrast': 0.1,
                 'sphericalAberration': 2.,
                 'voltage': 300,
                 'samplingRate': 3.54
                 }
-        cls.protMovieImport = cls.newProtocol(ProtImportMovies, **args)
-        cls.proj.launchProtocol(cls.protMovieImport, wait=False)
-        return cls.protMovieImport
-
-
+        protMovieImport = self.newProtocol(ProtImportMovies, **args)
+        self.proj.launchProtocol(protMovieImport, wait=False)
+        return protMovieImport
 
     def importMoviesStr(self, fnMovies):
         kwargs = {'inputMovies': fnMovies,
@@ -102,27 +91,23 @@ def runMovieResize(self, fnMovies, option, paramVal):
 
         return protResize
 
+    def _updateProtocol(self, prot):
+        prot2 = getProtocolFromDb(prot.getProject().path,
+                                  prot.getDbPath(),
+                                  prot.getObjId())
+        # Close DB connections
+        prot2.getProject().closeMapper()
+        prot2.closeMappers()
+        return prot2
 
 
     def test_pattern(self):
 
         protImport = self.runImportMoviesRibo()
-        counter = 1
-        while not protImport.hasAttribute('outputMovies'):
-            time.sleep(2)
-            protImport = self._updateProtocol(protImport)
-            if counter > 100:
-                break
-            counter += 1
+        self._waitOutput(protImport, 'outputMovies')
 
         protImportMovsStr = self.importMoviesStr(protImport.outputMovies)
-        counter = 1
-        while not protImportMovsStr.hasAttribute('outputMovies'):
-            time.sleep(2)
-            protImportMovsStr = self._updateProtocol(protImportMovsStr)
-            if counter > 100:
-                break
-            counter += 1
+        self._waitOutput(protImportMovsStr, 'outputMovies')
         if protImportMovsStr.isFailed():
             self.assertTrue(False)
 
@@ -130,40 +115,18 @@ def test_pattern(self):
         protMovieResize = self.runMovieResize(protImportMovsStr.outputMovies,
                                               RESIZE_SAMPLINGRATE,
                                               newSamplingRate)
-
-        counter = 1
-        while not protMovieResize.hasAttribute('outputMovies'):
-            time.sleep(2)
-            protMovieResize = self._updateProtocol(protMovieResize)
-            if counter > 100:
-                break
-            counter += 1
+        self._waitOutput(protMovieResize, 'outputMovies')
 
         newDimensions = 1700
         protMovieResize2 = self.runMovieResize(protMovieResize.outputMovies,
                                               RESIZE_DIMENSIONS,
                                               newDimensions)
-
-        counter = 1
-        while not protMovieResize2.hasAttribute('outputMovies'):
-            time.sleep(2)
-            protMovieResize2 = self._updateProtocol(protMovieResize2)
-            if counter > 100:
-                break
-            counter += 1
-
+        self._waitOutput(protMovieResize2, 'outputMovies')
 
         newFactor = 2.15
         protMovieResize3 = self.runMovieResize(protMovieResize2.outputMovies,
                                               RESIZE_FACTOR, newFactor)
-
-        counter = 1
-        while not protMovieResize3.hasAttribute('outputMovies'):
-            time.sleep(2)
-            protMovieResize3 = self._updateProtocol(protMovieResize3)
-            if counter > 100:
-                break
-            counter += 1
+        self._waitOutput(protMovieResize3, 'outputMovies')
 
         if not protMovieResize3.hasAttribute('outputMovies'):
             self.assertTrue(False)
diff --git a/xmipp3/tests/test_protocols_xmipp_movies.py b/xmipp3/tests/test_protocols_xmipp_movies.py
index 56b556bb7..1f841e383 100644
--- a/xmipp3/tests/test_protocols_xmipp_movies.py
+++ b/xmipp3/tests/test_protocols_xmipp_movies.py
@@ -490,11 +490,11 @@ def getArgs(self, filesPath, pattern=''):
     def test_estimate(self):
         protGain = self.newProtocol(XmippProtMovieGain,
                                     objLabel='estimate gain',
-                                    useExistingGainImage=False,
+                                    estimateGain=True,
+                                    estimateResidualGain=True,
                                     estimateOrientation=False,
                                     normalizeGain=False)
         protGain.inputMovies.set(self.protImport.outputMovies)
-        protGain.useExistingGainImage.set(False)
         self.launchProtocol(protGain)