scipion-em-xmipp 24.6.0.0__py3-none-any.whl → 24.12.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/METADATA +8 -7
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/RECORD +51 -47
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/WHEEL +1 -1
- xmipp3/__init__.py +110 -92
- xmipp3/checkProtocolsConf.py +117 -0
- xmipp3/constants.py +3 -0
- xmipp3/convert/convert.py +31 -10
- xmipp3/protocols/__init__.py +4 -2
- xmipp3/protocols/protocol_apply_tilt_to_ctf.py +114 -0
- xmipp3/protocols/protocol_cl2d.py +1 -1
- xmipp3/protocols/protocol_cl2d_clustering.py +303 -0
- xmipp3/protocols/protocol_classify_pca.py +3 -1
- xmipp3/protocols/protocol_classify_pca_streaming.py +1 -0
- xmipp3/protocols/protocol_compare_reprojections.py +2 -2
- xmipp3/protocols/protocol_convert_pdb.py +9 -4
- xmipp3/protocols/protocol_create_gallery.py +84 -12
- xmipp3/protocols/protocol_ctf_consensus.py +186 -273
- xmipp3/protocols/protocol_ctf_defocus_group.py +4 -4
- xmipp3/protocols/protocol_ctf_micrographs.py +12 -1
- xmipp3/protocols/protocol_deep_center.py +2 -2
- xmipp3/protocols/protocol_deep_center_predict.py +140 -0
- xmipp3/protocols/protocol_extract_particles.py +1 -1
- xmipp3/protocols/protocol_flexalign.py +46 -47
- xmipp3/protocols/protocol_mics_defocus_balancer.py +341 -0
- xmipp3/protocols/protocol_movie_alignment_consensus.py +1 -1
- xmipp3/protocols/protocol_movie_dose_analysis.py +159 -77
- xmipp3/protocols/protocol_movie_gain.py +5 -1
- xmipp3/protocols/protocol_movie_max_shift.py +246 -178
- xmipp3/protocols/protocol_reconstruct_fourier.py +29 -14
- xmipp3/protocols/protocol_reconstruct_highres.py +15 -2
- xmipp3/protocols/protocol_simulate_ctf.py +1 -1
- xmipp3/protocols/protocol_subtract_projection.py +89 -28
- xmipp3/protocols/protocol_tilt_analysis.py +95 -191
- xmipp3/protocols/protocol_trigger_data.py +22 -12
- xmipp3/protocols/protocol_validate_fscq.py +1 -1
- xmipp3/protocols/protocol_volume_adjust_sub.py +0 -4
- xmipp3/protocols/protocol_volume_local_sharpening.py +34 -24
- xmipp3/protocols.conf +141 -115
- xmipp3/tests/test_protocols_deepcenter_predict.py +66 -0
- xmipp3/tests/test_protocols_xmipp_2d.py +27 -7
- xmipp3/tests/test_protocols_xmipp_3d.py +16 -755
- xmipp3/tests/test_protocols_xmipp_mics.py +43 -4
- xmipp3/tests/test_protocols_xmipp_movies.py +0 -169
- xmipp3/version.py +38 -0
- xmipp3/viewers/__init__.py +3 -1
- xmipp3/viewers/viewer_apply_tilt_to_ctf.py +81 -0
- xmipp3/viewers/viewer_cl2d_clustering.py +131 -0
- xmipp3/viewers/viewer_movie_alignment.py +3 -9
- xmipp3/protocols/protocol_angular_resolution_alignment.py +0 -204
- xmipp3/protocols/protocol_movie_opticalflow.py +0 -416
- xmipp3/tests/test_protocols_angular_resolution_alignment.py +0 -88
- xmipp3/tests/test_protocols_mixed_movies.py +0 -149
- xmipp3/viewers/viewer_angular_resolution_alignment.py +0 -148
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/LICENSE +0 -0
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/entry_points.txt +0 -0
- {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/top_level.txt +0 -0
xmipp3/convert/convert.py
CHANGED
|
@@ -31,6 +31,7 @@ This module contains converter functions that will serve to:
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
import os
|
|
34
|
+
from typing import Union
|
|
34
35
|
from os.path import join, exists
|
|
35
36
|
from collections import OrderedDict
|
|
36
37
|
try:
|
|
@@ -46,7 +47,7 @@ from pwem.constants import (NO_INDEX, ALIGN_NONE, ALIGN_PROJ, ALIGN_2D,
|
|
|
46
47
|
ALIGN_3D)
|
|
47
48
|
from pwem.objects import (Angles, Coordinate, Micrograph, Volume, Particle,
|
|
48
49
|
MovieParticle, CTFModel, Acquisition, SetOfParticles,
|
|
49
|
-
Class3D, SetOfVolumes, Transform)
|
|
50
|
+
Class3D, SetOfVolumes, Movie, SetOfMovies, Transform)
|
|
50
51
|
from pwem.emlib.image import ImageHandler
|
|
51
52
|
import pwem.emlib.metadata as md
|
|
52
53
|
|
|
@@ -1657,10 +1658,30 @@ def writeShiftsMovieAlignment(movie, xmdFn, s0, sN):
|
|
|
1657
1658
|
|
|
1658
1659
|
globalShiftsMD.write(xmdFn)
|
|
1659
1660
|
|
|
1661
|
+
def makeEerFilename(eer: str, fractioning: int, supersampling: str, datatype: str) -> str:
|
|
1662
|
+
ARG_PREAMLE = '#'
|
|
1663
|
+
SEPARATOR = ','
|
|
1664
|
+
return eer + ARG_PREAMLE + ('%d' % fractioning) + SEPARATOR + supersampling + SEPARATOR + datatype
|
|
1660
1665
|
|
|
1661
|
-
def
|
|
1666
|
+
def frameToRow(frame, row, firstFrame, eerFrames):
|
|
1667
|
+
frameFilename = ImageHandler.locationToXmipp(frame)
|
|
1668
|
+
|
|
1669
|
+
if os.path.splitext(frameFilename)[-1] == '.eer':
|
|
1670
|
+
frameFilename = makeEerFilename(frameFilename, eerFrames, '4K', 'uint16')
|
|
1671
|
+
row.setValue(md.MDL_IMAGE, frameFilename)
|
|
1672
|
+
|
|
1673
|
+
def isEerMovie(movie: Union[Movie, SetOfMovies]):
|
|
1674
|
+
if movie is not None:
|
|
1675
|
+
files = movie.getFiles()
|
|
1676
|
+
if len(files) > 0:
|
|
1677
|
+
return next(iter(files)).endswith('.eer')
|
|
1678
|
+
|
|
1679
|
+
return False
|
|
1680
|
+
|
|
1681
|
+
def writeMovieMd(movie, outXmd, f1, fN, useAlignment=False, eerFrames=40):
|
|
1662
1682
|
movieMd = md.MetaData()
|
|
1663
1683
|
frame = movie.clone()
|
|
1684
|
+
isEer = isEerMovie(movie)
|
|
1664
1685
|
# get some info about the movie
|
|
1665
1686
|
# problem is, that it can come from a movie set, and some
|
|
1666
1687
|
# values might refer to different movie, e.g. no of frames :(
|
|
@@ -1673,19 +1694,20 @@ def writeMovieMd(movie, outXmd, f1, fN, useAlignment=False):
|
|
|
1673
1694
|
if frames is not None:
|
|
1674
1695
|
lastFrame = frames
|
|
1675
1696
|
|
|
1676
|
-
if
|
|
1677
|
-
|
|
1678
|
-
" than the movie one.")
|
|
1697
|
+
if isEer:
|
|
1698
|
+
lastFrame = eerFrames
|
|
1679
1699
|
|
|
1680
|
-
|
|
1700
|
+
if f1 < firstFrame or fN > lastFrame:
|
|
1701
|
+
raise ValueError(f"Frame range [{f1}-{fN}] could not be greater"
|
|
1702
|
+
f" than the movie one [{firstFrame}-{lastFrame}].")
|
|
1681
1703
|
|
|
1682
1704
|
if useAlignment:
|
|
1683
1705
|
alignment = movie.getAlignment()
|
|
1684
1706
|
if alignment is None:
|
|
1685
|
-
raise
|
|
1707
|
+
raise ValueError("Can not write alignment for movie.")
|
|
1686
1708
|
a0, aN = alignment.getRange()
|
|
1687
1709
|
if a0 < firstFrame or aN > lastFrame:
|
|
1688
|
-
raise
|
|
1710
|
+
raise ValueError("Trying to write frames which have not been aligned.")
|
|
1689
1711
|
shiftListX, shiftListY = alignment.getShifts()
|
|
1690
1712
|
|
|
1691
1713
|
row = md.Row()
|
|
@@ -1693,8 +1715,7 @@ def writeMovieMd(movie, outXmd, f1, fN, useAlignment=False):
|
|
|
1693
1715
|
|
|
1694
1716
|
for i in range(f1, fN+1):
|
|
1695
1717
|
frame.setIndex(stackIndex)
|
|
1696
|
-
|
|
1697
|
-
|
|
1718
|
+
frameToRow(frame, row, firstFrame=firstFrame, eerFrames=eerFrames)
|
|
1698
1719
|
if useAlignment:
|
|
1699
1720
|
shiftIndex = i - firstFrame
|
|
1700
1721
|
row.setValue(emlib.MDL_SHIFT_X, shiftListX[shiftIndex])
|
xmipp3/protocols/__init__.py
CHANGED
|
@@ -31,14 +31,15 @@ from .protocol_preprocess import *
|
|
|
31
31
|
from .protocol_assignment_tilt_pair import XmippProtAssignmentTiltPair
|
|
32
32
|
from .protocol_align_volume import XmippProtAlignVolume, XmippProtAlignVolumeForWeb
|
|
33
33
|
from .protocol_angular_graph_consistency import XmippProtAngularGraphConsistency
|
|
34
|
-
from .protocol_angular_resolution_alignment import XmippProtResolutionAlignment
|
|
35
34
|
from .protocol_preprocess.protocol_add_noise import (XmippProtAddNoiseVolumes,
|
|
36
35
|
XmippProtAddNoiseParticles)
|
|
37
36
|
from .protocol_apply_alignment import XmippProtApplyAlignment
|
|
37
|
+
from .protocol_apply_tilt_to_ctf import XmippProtApplyTiltToCtf
|
|
38
38
|
from .protocol_apply_transformation_matrix import XmippProtApplyTransformationMatrix
|
|
39
39
|
from .protocol_break_symmetry import XmippProtAngBreakSymmetry
|
|
40
40
|
from .protocol_cl2d_align import XmippProtCL2DAlign
|
|
41
41
|
from .protocol_cl2d import XmippProtCL2D
|
|
42
|
+
from .protocol_cl2d_clustering import XmippProtCL2DClustering
|
|
42
43
|
from .protocol_classify_pca import XmippProtClassifyPca
|
|
43
44
|
from .protocol_classify_pca_streaming import XmippProtClassifyPcaStreaming
|
|
44
45
|
#from .protocol_classify_kmeans2d import XmippProtKmeansClassif2D
|
|
@@ -63,11 +64,11 @@ from .protocol_extract_particles_pairs import XmippProtExtractParticlesPairs
|
|
|
63
64
|
from .protocol_extract_asymmetric_unit import XmippProtExtractUnit
|
|
64
65
|
from .protocol_helical_parameters import XmippProtHelicalParameters
|
|
65
66
|
from .protocol_kerdensom import XmippProtKerdensom
|
|
67
|
+
from .protocol_mics_defocus_balancer import XmippProtMicDefocusSampler
|
|
66
68
|
from .protocol_ml2d import XmippProtML2D
|
|
67
69
|
from .protocol_movie_gain import XmippProtMovieGain
|
|
68
70
|
from .protocol_movie_alignment_consensus import XmippProtConsensusMovieAlignment
|
|
69
71
|
from .protocol_flexalign import XmippProtFlexAlign
|
|
70
|
-
from .protocol_movie_opticalflow import XmippProtOFAlignment, ProtMovieAlignment
|
|
71
72
|
from .protocol_movie_max_shift import XmippProtMovieMaxShift
|
|
72
73
|
from .protocol_movie_dose_analysis import XmippProtMovieDoseAnalysis
|
|
73
74
|
from .protocol_movie_split_frames import XmippProtSplitFrames
|
|
@@ -135,3 +136,4 @@ from .protocol_volume_local_adjust import XmippProtLocalVolAdj
|
|
|
135
136
|
from .protocol_classes_2d_mapping import XmippProtCL2DMap
|
|
136
137
|
from .protocol_deep_hand import XmippProtDeepHand
|
|
137
138
|
from .protocol_deep_center import XmippProtDeepCenter
|
|
139
|
+
from .protocol_deep_center_predict import XmippProtDeepCenterPredict
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# **************************************************************************
|
|
2
|
+
# *
|
|
3
|
+
# * Authors: Oier Lauzirika Zarrabeitia (oierlauzi@bizkaia.eu)
|
|
4
|
+
# *
|
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
|
6
|
+
# *
|
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
|
9
|
+
# * the Free Software Foundation; either version 2 of the License, or
|
|
10
|
+
# * (at your option) any later version.
|
|
11
|
+
# *
|
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# * GNU General Public License for more details.
|
|
16
|
+
# *
|
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
|
18
|
+
# * along with this program; if not, write to the Free Software
|
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
20
|
+
# * 02111-1307 USA
|
|
21
|
+
# *
|
|
22
|
+
# * All comments concerning this program package may be sent to the
|
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
|
24
|
+
# *
|
|
25
|
+
# **************************************************************************
|
|
26
|
+
|
|
27
|
+
from typing import Optional
|
|
28
|
+
|
|
29
|
+
import pyworkflow.protocol.params as params
|
|
30
|
+
from pyworkflow.utils.properties import Message
|
|
31
|
+
|
|
32
|
+
from pwem.objects import (Particle, Coordinate, Micrograph, CTFModel,
|
|
33
|
+
SetOfParticles, SetOfMicrographs)
|
|
34
|
+
from pwem.protocols import EMProtocol
|
|
35
|
+
from pyworkflow import BETA, UPDATED, NEW, PROD
|
|
36
|
+
|
|
37
|
+
import math
|
|
38
|
+
|
|
39
|
+
class XmippProtApplyTiltToCtf(EMProtocol):
|
|
40
|
+
""" Apply a local deviation to the CTF based on the micrograph's tilt
|
|
41
|
+
angle"""
|
|
42
|
+
_devStatus = NEW
|
|
43
|
+
|
|
44
|
+
_label = 'apply tilt to ctf'
|
|
45
|
+
_tilt_axes = ['X', 'Y']
|
|
46
|
+
_tilt_signs = ['Increasing', 'Decreasing']
|
|
47
|
+
|
|
48
|
+
#--------------------------- DEFINE param functions --------------------------------------------
|
|
49
|
+
def _defineParams(self, form):
|
|
50
|
+
form.addSection(label='Input')
|
|
51
|
+
form.addParam('inputParticles', params.PointerParam, label=Message.LABEL_INPUT_PART,
|
|
52
|
+
pointerClass=SetOfParticles, pointerCondition='hasCTF and hasCoordinates',
|
|
53
|
+
important=True,
|
|
54
|
+
help='Select the particles that you want to apply the'
|
|
55
|
+
'local CTF correction.')
|
|
56
|
+
form.addParam('inputMicrographs', params.PointerParam, label=Message.LABEL_INPUT_MIC,
|
|
57
|
+
pointerClass=SetOfMicrographs,
|
|
58
|
+
important=True,
|
|
59
|
+
help='The micrographs from which particles were extracted.')
|
|
60
|
+
form.addParam('tiltAxis', params.EnumParam, label='Tilt axis',
|
|
61
|
+
choices=self._tilt_axes, default=1,
|
|
62
|
+
help='The tilt axis. In tomography this is Y by convention.')
|
|
63
|
+
form.addParam('tiltAngle', params.FloatParam, label='Tilt angle',
|
|
64
|
+
default=0, validators=[params.Range(0, 90)],
|
|
65
|
+
help='The angle at which the acquisition is tilted. '
|
|
66
|
+
'In degrees.')
|
|
67
|
+
form.addParam('tiltSign', params.EnumParam, label='Tilt sign',
|
|
68
|
+
choices=self._tilt_signs, default=0,
|
|
69
|
+
help='Wether defocus increases or decreases in terms '
|
|
70
|
+
'of the selected tilt axis.')
|
|
71
|
+
|
|
72
|
+
#--------------------------- INSERT steps functions --------------------------------------------
|
|
73
|
+
|
|
74
|
+
def _insertAllSteps(self):
|
|
75
|
+
self._insertFunctionStep('createOutputStep')
|
|
76
|
+
|
|
77
|
+
#--------------------------- STEPS functions --------------------------------------------
|
|
78
|
+
def createOutputStep(self):
|
|
79
|
+
inputParticles: SetOfParticles = self.inputParticles.get()
|
|
80
|
+
outputParticles: SetOfParticles = self._createSetOfParticles()
|
|
81
|
+
|
|
82
|
+
TILT_INDICES = [1, 0]
|
|
83
|
+
SIGNS = [+1, -1]
|
|
84
|
+
sign = SIGNS[self.tiltSign.get()]
|
|
85
|
+
self.tiltIndex = TILT_INDICES[self.tiltAxis.get()]
|
|
86
|
+
self.sineFactor = sign*math.sin(math.radians(self.tiltAngle.get()))
|
|
87
|
+
|
|
88
|
+
outputParticles.copyInfo(inputParticles)
|
|
89
|
+
outputParticles.copyItems(inputParticles,
|
|
90
|
+
updateItemCallback=self._updateItem )
|
|
91
|
+
|
|
92
|
+
self._defineOutputs(outputParticles=outputParticles)
|
|
93
|
+
self._defineSourceRelation(self.inputParticles, outputParticles)
|
|
94
|
+
|
|
95
|
+
#--------------------------- UTILS functions --------------------------------------------
|
|
96
|
+
def _updateItem(self, particle: Particle, _):
|
|
97
|
+
# Obtain necessary objects
|
|
98
|
+
coordinate: Coordinate = particle.getCoordinate()
|
|
99
|
+
micrograph: Optional[Micrograph] = coordinate.getMicrograph()
|
|
100
|
+
if micrograph is None:
|
|
101
|
+
micrographId = coordinate.getMicId()
|
|
102
|
+
micrograph = self.inputMicrographs.get()[micrographId]
|
|
103
|
+
|
|
104
|
+
# Compute the CTF offset
|
|
105
|
+
dimensions = micrograph.getXDim(), micrograph.getYDim()
|
|
106
|
+
position = coordinate.getPosition()
|
|
107
|
+
r = position[self.tiltIndex] - (dimensions[self.tiltIndex] / 2)
|
|
108
|
+
r *= micrograph.getSamplingRate() # Convert to angstroms.
|
|
109
|
+
dy = self.sineFactor*r
|
|
110
|
+
|
|
111
|
+
# Write to output
|
|
112
|
+
ctf: CTFModel = particle.getCTF()
|
|
113
|
+
ctf.setDefocusU(ctf.getDefocusU() + dy)
|
|
114
|
+
ctf.setDefocusV(ctf.getDefocusV() + dy)
|
|
@@ -67,7 +67,7 @@ class XmippProtCL2D(ProtClassify2D):
|
|
|
67
67
|
the original dataset into a given number of classes. """
|
|
68
68
|
|
|
69
69
|
_label = 'cl2d'
|
|
70
|
-
_devStatus =
|
|
70
|
+
_devStatus = PROD
|
|
71
71
|
|
|
72
72
|
_possibleOutputs = {OUTPUTCLASSES: SetOfClasses2D,
|
|
73
73
|
OUTPUTCLASSES+CLASSES_CORE: SetOfClasses2D,
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# *
|
|
3
|
+
# * Authors: Daniel Marchan Torres (da.marchan@cnb.csic.es)
|
|
4
|
+
# *
|
|
5
|
+
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
|
|
6
|
+
# *
|
|
7
|
+
# * This program is free software; you can redistribute it and/or modify
|
|
8
|
+
# * it under the terms of the GNU General Public License as published by
|
|
9
|
+
# * the Free Software Foundation; either version 2 of the License, or
|
|
10
|
+
# * (at your option) any later version.
|
|
11
|
+
# *
|
|
12
|
+
# * This program is distributed in the hope that it will be useful,
|
|
13
|
+
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# * GNU General Public License for more details.
|
|
16
|
+
# *
|
|
17
|
+
# * You should have received a copy of the GNU General Public License
|
|
18
|
+
# * along with this program; if not, write to the Free Software
|
|
19
|
+
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
20
|
+
# * 02111-1307 USA
|
|
21
|
+
# *
|
|
22
|
+
# * All comments concerning this program package may be sent to the
|
|
23
|
+
# * e-mail address 'scipion@cnb.csic.es'
|
|
24
|
+
# *
|
|
25
|
+
# ******************************************************************************
|
|
26
|
+
|
|
27
|
+
import os.path
|
|
28
|
+
from pwem.objects.data import Class2D, Particle, SetOfClasses2D, SetOfAverages
|
|
29
|
+
from pwem.protocols import ProtAnalysis2D
|
|
30
|
+
from pyworkflow.protocol.params import (PointerParam, IntParam,
|
|
31
|
+
EnumParam, LEVEL_ADVANCED, LT, GT)
|
|
32
|
+
from pyworkflow import NEW, BETA
|
|
33
|
+
from xmipp3 import XmippProtocol
|
|
34
|
+
|
|
35
|
+
FN = "class_representatives"
|
|
36
|
+
RESULT_FILE = 'best_clusters_with_names.txt'
|
|
37
|
+
OUTPUT_CLASSES = 'outputClasses'
|
|
38
|
+
OUTPUT_AVERAGES = 'outputAverages'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class XmippProtCL2DClustering(ProtAnalysis2D, XmippProtocol):
|
|
42
|
+
"""2D clustering protocol to group similar images (2D Averages or 2D Classes) """
|
|
43
|
+
|
|
44
|
+
_label = 'clustering 2d classes'
|
|
45
|
+
_devStatus = BETA
|
|
46
|
+
|
|
47
|
+
_possibleOutputs = {OUTPUT_CLASSES: SetOfClasses2D,
|
|
48
|
+
OUTPUT_AVERAGES: SetOfAverages}
|
|
49
|
+
|
|
50
|
+
CLASSES = 0
|
|
51
|
+
AVERAGES = 1
|
|
52
|
+
BOTH = 2
|
|
53
|
+
|
|
54
|
+
def __init__(self, **args):
|
|
55
|
+
ProtAnalysis2D.__init__(self, **args)
|
|
56
|
+
|
|
57
|
+
#--------------------------- DEFINE param functions ------------------------
|
|
58
|
+
def _defineParams(self, form):
|
|
59
|
+
form.addSection(label='Input')
|
|
60
|
+
form.addParam('inputSet2D', PointerParam,
|
|
61
|
+
label="Input 2D images",
|
|
62
|
+
important=True, pointerClass='SetOfClasses2D, SetOfAverages',
|
|
63
|
+
help='Select the input classes or input averages to be clustered.')
|
|
64
|
+
form.addParam('min_cluster', IntParam, label='Minimum number of clusters',
|
|
65
|
+
default=10, expertLevel=LEVEL_ADVANCED,
|
|
66
|
+
validators=[GT(1, 'Error must be greater than 1')],
|
|
67
|
+
help=' This number will limit the search for the optimum number of clusters. '
|
|
68
|
+
'By default, the 2D averages will start searching for the optimum number of clusters'
|
|
69
|
+
'with a minimum number of 10 classes.')
|
|
70
|
+
form.addParam('max_cluster', IntParam, label='Maximum number of clusters',
|
|
71
|
+
default=-1, expertLevel=LEVEL_ADVANCED,
|
|
72
|
+
validators=[LT(50, 'Error must be smaller than the number of classes - 2.')],
|
|
73
|
+
help='This number will limit the search for the optimum number of clusters. '
|
|
74
|
+
'If -1 then it will act as default. By default, the 2D averages will end searching'
|
|
75
|
+
'for the optimum number of clusters until a maximum number of N_classes - 2.')
|
|
76
|
+
form.addParam('compute_threads', IntParam, label='Number of computational threads',
|
|
77
|
+
default=8, expertLevel=LEVEL_ADVANCED,
|
|
78
|
+
validators=[
|
|
79
|
+
GT(0, 'Error must be greater than 0.')],
|
|
80
|
+
help=' By default, the program will use 8 threads for computation.'
|
|
81
|
+
'The higher the number the fastest the computation will be')
|
|
82
|
+
|
|
83
|
+
form.addSection(label='Output')
|
|
84
|
+
form.addParam('extractOption', EnumParam,
|
|
85
|
+
choices=['Classes', 'Averages', 'Both'],
|
|
86
|
+
default=self.CLASSES,
|
|
87
|
+
label="Extraction option", display=EnumParam.DISPLAY_COMBO,
|
|
88
|
+
help='Select an option to extract from the 2D Classes: \n '
|
|
89
|
+
'_Classes_: Create a new set of 2D classes with the respective cluster distribution. \n '
|
|
90
|
+
'_Averages_: Extract the representatives of each cluster. This are the most representative averages. \n'
|
|
91
|
+
'_Both_: Create a new set of 2D classes and extract their representatives.')
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# --------------------------- INSERT steps functions ------------------------
|
|
95
|
+
def _insertAllSteps(self):
|
|
96
|
+
convertStep = self._insertFunctionStep(self.convertStep)
|
|
97
|
+
clusterStep = self._insertFunctionStep(self.clusterClasses, prerequisites=convertStep)
|
|
98
|
+
self._insertFunctionStep(self.createOutputStep, prerequisites=clusterStep)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def convertStep(self):
|
|
102
|
+
self.info('Writting class representatives')
|
|
103
|
+
self.directoryPath = self._getExtraPath()
|
|
104
|
+
self.imgsFn = os.path.join(self.directoryPath, FN + ".mrcs")
|
|
105
|
+
self.refIdsFn = os.path.join(self.directoryPath, FN + ".txt")
|
|
106
|
+
|
|
107
|
+
inputSet2D = self.inputSet2D.get()
|
|
108
|
+
classes_refIds = []
|
|
109
|
+
|
|
110
|
+
if isinstance(inputSet2D, SetOfClasses2D):
|
|
111
|
+
self.samplingRate = inputSet2D.getFirstItem().getRepresentative().getSamplingRate()
|
|
112
|
+
for rep in inputSet2D.iterRepresentatives():
|
|
113
|
+
idClass, _ = rep.getLocation()
|
|
114
|
+
classes_refIds.append(idClass)
|
|
115
|
+
else: # In case the input is a SetOfAverages
|
|
116
|
+
self.samplingRate = inputSet2D.getSamplingRate()
|
|
117
|
+
for rep in inputSet2D.iterItems():
|
|
118
|
+
idClass, _ = rep.getLocation()
|
|
119
|
+
classes_refIds.append(idClass)
|
|
120
|
+
|
|
121
|
+
# Save the corresponding .mrcs file
|
|
122
|
+
inputSet2D.writeStack(self.imgsFn) # The same method for SetOfClasses and SetOfAverages
|
|
123
|
+
# Save the original ref ids
|
|
124
|
+
with open(self.refIdsFn, "w") as file:
|
|
125
|
+
for item in classes_refIds:
|
|
126
|
+
file.write(f"{item}\n")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def clusterClasses(self):
|
|
130
|
+
'xmipp_cl2d_clustering -i path/to/inputAverages.mrcs -o path/to/outputDir -m 10 -M 20 -j 8'
|
|
131
|
+
|
|
132
|
+
min_cluster = self.min_cluster.get()
|
|
133
|
+
max_cluster = self.max_cluster.get()
|
|
134
|
+
compute_threads = self.compute_threads.get()
|
|
135
|
+
|
|
136
|
+
args = (" -i %s -o %s -m %d -M %d -j %d"
|
|
137
|
+
%(self.imgsFn, self.directoryPath, min_cluster, max_cluster, compute_threads))
|
|
138
|
+
|
|
139
|
+
self.runJob("xmipp_cl2d_clustering", args)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def createOutputStep(self):
|
|
143
|
+
output_dict = {}
|
|
144
|
+
inputSet2D = self.inputSet2D.get()
|
|
145
|
+
inputSet2D.loadAllProperties()
|
|
146
|
+
|
|
147
|
+
result_dict_file = os.path.join(self.directoryPath, RESULT_FILE)
|
|
148
|
+
result_dict = self.read_clusters_from_txt(result_dict_file)
|
|
149
|
+
|
|
150
|
+
message = ("Classify original input set of %d images into %d groups of structural different images"
|
|
151
|
+
% (self.inputSet2D.get().getSize(), len(result_dict)))
|
|
152
|
+
|
|
153
|
+
self.summaryVar.set(message)
|
|
154
|
+
|
|
155
|
+
if self.extractOption.get() == self.CLASSES or self.extractOption.get() == self.BOTH:
|
|
156
|
+
output_dict = self.createOutputSetOfClasses(inputSet2D, result_dict, output_dict)
|
|
157
|
+
|
|
158
|
+
if self.extractOption.get() == self.AVERAGES or self.extractOption.get() == self.BOTH:
|
|
159
|
+
output_dict = self.createOutputSetOfAverages(inputSet2D, result_dict, output_dict)
|
|
160
|
+
|
|
161
|
+
self._defineOutputs(**output_dict)
|
|
162
|
+
|
|
163
|
+
if self.extractOption.get() == self.CLASSES or self.extractOption.get() == self.BOTH:
|
|
164
|
+
self._defineSourceRelation(inputSet2D.getImagesPointer(), output_dict[OUTPUT_CLASSES])
|
|
165
|
+
if self.extractOption.get() == self.AVERAGES or self.extractOption.get() == self.BOTH:
|
|
166
|
+
self._defineSourceRelation(self.inputSet2D, output_dict[OUTPUT_AVERAGES])
|
|
167
|
+
|
|
168
|
+
self._store()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def createOutputSetOfAverages(self, inputSet2D, result_dict, output_dict):
|
|
172
|
+
outputRefs = self._createSetOfAverages() # We need to create always an empty set since we need to rebuild it
|
|
173
|
+
|
|
174
|
+
for cluster, classesRef in result_dict.items():
|
|
175
|
+
self.info('For cluster %d' % cluster)
|
|
176
|
+
self.info('We have the following ref classes: %s' % classesRef)
|
|
177
|
+
firstTime = True
|
|
178
|
+
for classRef in classesRef:
|
|
179
|
+
if firstTime: # Just want to get the first ref
|
|
180
|
+
if isinstance(inputSet2D, SetOfClasses2D):
|
|
181
|
+
classTmp = inputSet2D.getItem("id", classRef).clone()
|
|
182
|
+
rep = classTmp.getRepresentative().clone()
|
|
183
|
+
else:
|
|
184
|
+
rep = inputSet2D.getItem("id", classRef).clone()
|
|
185
|
+
self.samplingRate = inputSet2D.getSamplingRate()
|
|
186
|
+
self.info('Using centroid to create new Average %s' % classRef)
|
|
187
|
+
newAvg = Particle()
|
|
188
|
+
newAvg.copyInfo(rep)
|
|
189
|
+
newAvg.setObjId(int(classRef))
|
|
190
|
+
newAvg.setClassId(int(classRef))
|
|
191
|
+
outputRefs.append(newAvg)
|
|
192
|
+
firstTime = False
|
|
193
|
+
|
|
194
|
+
outputRefs.setSamplingRate(self.samplingRate)
|
|
195
|
+
output_dict[OUTPUT_AVERAGES] = outputRefs
|
|
196
|
+
|
|
197
|
+
return output_dict
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def createOutputSetOfClasses(self, inputSet2D, result_dict, output_dict):
|
|
201
|
+
classes2DSet = self._createSetOfClasses2D(inputSet2D.getImagesPointer())
|
|
202
|
+
dictClasses = {}
|
|
203
|
+
|
|
204
|
+
for cluster, classesRef in result_dict.items():
|
|
205
|
+
self.info('For cluster %d' % cluster)
|
|
206
|
+
self.info('We have the following ref classes: %s' % classesRef)
|
|
207
|
+
firstTime = True
|
|
208
|
+
newParticles = []
|
|
209
|
+
|
|
210
|
+
for classRef in classesRef:
|
|
211
|
+
classTmp = inputSet2D.getItem("id", classRef)
|
|
212
|
+
if firstTime:
|
|
213
|
+
self.info('First iter, using centroid to create new Class: %s' % classRef)
|
|
214
|
+
newClass = Class2D()
|
|
215
|
+
newClass.copyInfo(classTmp)
|
|
216
|
+
newClass.setObjId(int(classRef))
|
|
217
|
+
newClassId = newClass.getObjId()
|
|
218
|
+
firstTime = False
|
|
219
|
+
|
|
220
|
+
for particle in classTmp.iterItems():
|
|
221
|
+
particle.setClassId(newClassId)
|
|
222
|
+
newParticles.append(particle.clone())
|
|
223
|
+
|
|
224
|
+
dictClasses[newClassId] = newParticles
|
|
225
|
+
self.info('Class particles size: %d' % len(newParticles))
|
|
226
|
+
classes2DSet.append(newClass)
|
|
227
|
+
|
|
228
|
+
for classId, particles in dictClasses.items():
|
|
229
|
+
self.info('Filling Class with ID %d with %d particles' % (classId, len(particles)))
|
|
230
|
+
class2D = classes2DSet[classId]
|
|
231
|
+
class2D.enableAppend()
|
|
232
|
+
for particle in particles:
|
|
233
|
+
class2D.append(particle)
|
|
234
|
+
|
|
235
|
+
classes2DSet.update(class2D)
|
|
236
|
+
|
|
237
|
+
classes2DSet.write()
|
|
238
|
+
|
|
239
|
+
output_dict[OUTPUT_CLASSES] = classes2DSet
|
|
240
|
+
|
|
241
|
+
return output_dict
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# --------------------------- INFO functions --------------------------------------------
|
|
245
|
+
def _summary(self):
|
|
246
|
+
summary = []
|
|
247
|
+
if not hasattr(self, OUTPUT_CLASSES) and not hasattr(self, OUTPUT_AVERAGES):
|
|
248
|
+
summary.append("Output set not ready yet.")
|
|
249
|
+
else:
|
|
250
|
+
summary.append(self.summaryVar.get())
|
|
251
|
+
|
|
252
|
+
return summary
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _validate(self):
|
|
256
|
+
errors = []
|
|
257
|
+
if ((self.extractOption.get() == self.CLASSES or self.extractOption.get() == self.BOTH)
|
|
258
|
+
and not isinstance(self.inputSet2D.get(), SetOfClasses2D)):
|
|
259
|
+
errors.append("The input 2D must be a SetOfClasses2D to generate a SetOfClasses2D.")
|
|
260
|
+
|
|
261
|
+
return errors
|
|
262
|
+
|
|
263
|
+
# ------------------------------------ Utils ----------------------------------------------
|
|
264
|
+
def read_clusters_from_txt(self, file_path):
|
|
265
|
+
"""
|
|
266
|
+
Reads a cluster dictionary from a .txt file formatted as:
|
|
267
|
+
Cluster 0:
|
|
268
|
+
6
|
|
269
|
+
Cluster 1:
|
|
270
|
+
36
|
|
271
|
+
34
|
|
272
|
+
44
|
|
273
|
+
...
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
file_path (str): The path to the .txt file.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
dict: A dictionary where the key is the cluster number and the value is a list of associated numbers.
|
|
280
|
+
"""
|
|
281
|
+
clusters = {}
|
|
282
|
+
current_cluster = None
|
|
283
|
+
|
|
284
|
+
with open(file_path, 'r') as file:
|
|
285
|
+
for line in file:
|
|
286
|
+
line = line.strip()
|
|
287
|
+
if line.startswith("Cluster"):
|
|
288
|
+
# Extract the cluster number
|
|
289
|
+
current_cluster = int(line.split()[1].replace(':', ''))
|
|
290
|
+
clusters[current_cluster] = []
|
|
291
|
+
elif current_cluster is not None and line.isdigit():
|
|
292
|
+
# Append numbers to the current cluster's list
|
|
293
|
+
clusters[current_cluster].append(line)
|
|
294
|
+
|
|
295
|
+
return clusters
|
|
296
|
+
|
|
297
|
+
# --------------------------------- Viewer functions ---------------------------------
|
|
298
|
+
def getClusterPlot(self):
|
|
299
|
+
return self._getExtraPath('best_cluster_visualization_with_images.png')
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def getClusterImagesPlot(self):
|
|
303
|
+
return self._getExtraPath('all_clusters_with_labels.png')
|
|
@@ -291,8 +291,10 @@ class XmippProtClassifyPca(ProtClassify2D, XmippProtocol):
|
|
|
291
291
|
errors.append("You should resize the particles."
|
|
292
292
|
" Sizes smaller than 128 pixels are recommended.")
|
|
293
293
|
er = self.validateDLtoolkit()
|
|
294
|
+
if not isinstance(er, list):
|
|
295
|
+
er = [er]
|
|
294
296
|
if er:
|
|
295
|
-
errors
|
|
297
|
+
errors+=er
|
|
296
298
|
return errors
|
|
297
299
|
|
|
298
300
|
def _warnings(self):
|
|
@@ -331,6 +331,7 @@ class XmippProtClassifyPcaStreaming(ProtStreamingBase, XmippProtClassifyPca):
|
|
|
331
331
|
# Limit the number of threads
|
|
332
332
|
env['OMP_NUM_THREADS'] = '12'
|
|
333
333
|
env['MKL_NUM_THREADS'] = '12'
|
|
334
|
+
env['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
|
|
334
335
|
return env
|
|
335
336
|
|
|
336
337
|
def _updateFnClassification(self):
|
|
@@ -39,7 +39,7 @@ from pwem.emlib.image import ImageHandler
|
|
|
39
39
|
from pwem import emlib
|
|
40
40
|
|
|
41
41
|
from pyworkflow.protocol.constants import LEVEL_ADVANCED
|
|
42
|
-
from pyworkflow import UPDATED
|
|
42
|
+
from pyworkflow import UPDATED, PROD
|
|
43
43
|
from pyworkflow import VERSION_3_0
|
|
44
44
|
from pyworkflow.protocol.params import (PointerParam, StringParam, FloatParam, BooleanParam, EnumParam)
|
|
45
45
|
from pyworkflow.utils.path import cleanPath, cleanPattern
|
|
@@ -63,7 +63,7 @@ class XmippProtCompareReprojections(ProtAnalysis3D, ProjMatcher):
|
|
|
63
63
|
|
|
64
64
|
_label = 'compare reprojections'
|
|
65
65
|
_lastUpdateVersion = VERSION_3_0
|
|
66
|
-
_devStatus =
|
|
66
|
+
_devStatus = PROD
|
|
67
67
|
_possibleOutputs = {}
|
|
68
68
|
|
|
69
69
|
PARTICLES = 0
|
|
@@ -39,13 +39,13 @@ from pwem.objects import Volume, Transform, SetOfVolumes, AtomStruct, SetOfAtomS
|
|
|
39
39
|
from pwem.protocols import ProtInitialVolume
|
|
40
40
|
import pyworkflow.protocol.params as params
|
|
41
41
|
import pyworkflow.protocol.constants as const
|
|
42
|
-
from pyworkflow.utils import replaceBaseExt, removeExt, getExt, createLink, replaceExt, removeBaseExt
|
|
42
|
+
from pyworkflow.utils import replaceBaseExt, removeExt, getExt, createLink, replaceExt, removeBaseExt, red
|
|
43
43
|
from pyworkflow import UPDATED, PROD
|
|
44
44
|
|
|
45
45
|
class XmippProtConvertPdb(ProtInitialVolume):
|
|
46
46
|
""" Convert atomic structure(s) into volume(s) """
|
|
47
47
|
_label = 'convert pdbs to volumes'
|
|
48
|
-
_devStatus =
|
|
48
|
+
_devStatus = UPDATED
|
|
49
49
|
OUTPUT_NAME1 = "outputVolume"
|
|
50
50
|
OUTPUT_NAME2 = "outputVolumes"
|
|
51
51
|
OUTPUT_NAME3 = "outputPdb"
|
|
@@ -111,7 +111,7 @@ class XmippProtConvertPdb(ProtInitialVolume):
|
|
|
111
111
|
label="Store centered PDB",
|
|
112
112
|
help='Set to \'Yes\' if you want to save centered PDB. '
|
|
113
113
|
'It will be stored in the output directory of this protocol.')
|
|
114
|
-
form.addParam('convertCif', params.BooleanParam, default=
|
|
114
|
+
form.addParam('convertCif', params.BooleanParam, default=False,
|
|
115
115
|
expertLevel=const.LEVEL_ADVANCED,
|
|
116
116
|
label="Convert CIF to PDB",
|
|
117
117
|
help='If set to true and input atom struct file is a CIF, it will get converted to PDB.')
|
|
@@ -278,7 +278,12 @@ class XmippProtConvertPdb(ProtInitialVolume):
|
|
|
278
278
|
|
|
279
279
|
# If file extension is .cif and conversion is required, convert to .pdb, or else we link it as is
|
|
280
280
|
if getExt(pdbRaw) == ".cif" and self.convertCif.get():
|
|
281
|
-
|
|
281
|
+
try:
|
|
282
|
+
cifToPdb(pdbRaw, convertedPdb)
|
|
283
|
+
except:
|
|
284
|
+
self.error(red("The conversion has failed. It might be for an excessive number of atoms. "\
|
|
285
|
+
"Try not to convert the CIF into a PDB (hidden flag)."))
|
|
286
|
+
raise RuntimeError("PDB conversion")
|
|
282
287
|
else:
|
|
283
288
|
createLink(pdbRaw, convertedPdb)
|
|
284
289
|
|