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.
Files changed (56) hide show
  1. {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/METADATA +8 -7
  2. {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/RECORD +51 -47
  3. {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/WHEEL +1 -1
  4. xmipp3/__init__.py +110 -92
  5. xmipp3/checkProtocolsConf.py +117 -0
  6. xmipp3/constants.py +3 -0
  7. xmipp3/convert/convert.py +31 -10
  8. xmipp3/protocols/__init__.py +4 -2
  9. xmipp3/protocols/protocol_apply_tilt_to_ctf.py +114 -0
  10. xmipp3/protocols/protocol_cl2d.py +1 -1
  11. xmipp3/protocols/protocol_cl2d_clustering.py +303 -0
  12. xmipp3/protocols/protocol_classify_pca.py +3 -1
  13. xmipp3/protocols/protocol_classify_pca_streaming.py +1 -0
  14. xmipp3/protocols/protocol_compare_reprojections.py +2 -2
  15. xmipp3/protocols/protocol_convert_pdb.py +9 -4
  16. xmipp3/protocols/protocol_create_gallery.py +84 -12
  17. xmipp3/protocols/protocol_ctf_consensus.py +186 -273
  18. xmipp3/protocols/protocol_ctf_defocus_group.py +4 -4
  19. xmipp3/protocols/protocol_ctf_micrographs.py +12 -1
  20. xmipp3/protocols/protocol_deep_center.py +2 -2
  21. xmipp3/protocols/protocol_deep_center_predict.py +140 -0
  22. xmipp3/protocols/protocol_extract_particles.py +1 -1
  23. xmipp3/protocols/protocol_flexalign.py +46 -47
  24. xmipp3/protocols/protocol_mics_defocus_balancer.py +341 -0
  25. xmipp3/protocols/protocol_movie_alignment_consensus.py +1 -1
  26. xmipp3/protocols/protocol_movie_dose_analysis.py +159 -77
  27. xmipp3/protocols/protocol_movie_gain.py +5 -1
  28. xmipp3/protocols/protocol_movie_max_shift.py +246 -178
  29. xmipp3/protocols/protocol_reconstruct_fourier.py +29 -14
  30. xmipp3/protocols/protocol_reconstruct_highres.py +15 -2
  31. xmipp3/protocols/protocol_simulate_ctf.py +1 -1
  32. xmipp3/protocols/protocol_subtract_projection.py +89 -28
  33. xmipp3/protocols/protocol_tilt_analysis.py +95 -191
  34. xmipp3/protocols/protocol_trigger_data.py +22 -12
  35. xmipp3/protocols/protocol_validate_fscq.py +1 -1
  36. xmipp3/protocols/protocol_volume_adjust_sub.py +0 -4
  37. xmipp3/protocols/protocol_volume_local_sharpening.py +34 -24
  38. xmipp3/protocols.conf +141 -115
  39. xmipp3/tests/test_protocols_deepcenter_predict.py +66 -0
  40. xmipp3/tests/test_protocols_xmipp_2d.py +27 -7
  41. xmipp3/tests/test_protocols_xmipp_3d.py +16 -755
  42. xmipp3/tests/test_protocols_xmipp_mics.py +43 -4
  43. xmipp3/tests/test_protocols_xmipp_movies.py +0 -169
  44. xmipp3/version.py +38 -0
  45. xmipp3/viewers/__init__.py +3 -1
  46. xmipp3/viewers/viewer_apply_tilt_to_ctf.py +81 -0
  47. xmipp3/viewers/viewer_cl2d_clustering.py +131 -0
  48. xmipp3/viewers/viewer_movie_alignment.py +3 -9
  49. xmipp3/protocols/protocol_angular_resolution_alignment.py +0 -204
  50. xmipp3/protocols/protocol_movie_opticalflow.py +0 -416
  51. xmipp3/tests/test_protocols_angular_resolution_alignment.py +0 -88
  52. xmipp3/tests/test_protocols_mixed_movies.py +0 -149
  53. xmipp3/viewers/viewer_angular_resolution_alignment.py +0 -148
  54. {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/LICENSE +0 -0
  55. {scipion_em_xmipp-24.6.0.0.dist-info → scipion_em_xmipp-24.12.2.dist-info}/entry_points.txt +0 -0
  56. {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 writeMovieMd(movie, outXmd, f1, fN, useAlignment=False):
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 f1 < firstFrame or fN > lastFrame:
1677
- raise Exception("Frame range could not be greater"
1678
- " than the movie one.")
1697
+ if isEer:
1698
+ lastFrame = eerFrames
1679
1699
 
1680
- ih = ImageHandler()
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 Exception("Can not write alignment for movie. ")
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 Exception("Trying to write frames which have not been aligned.")
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
- row.setValue(md.MDL_IMAGE, ih.locationToXmipp(frame))
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])
@@ -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 = UPDATED
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.append(er)
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 = UPDATED
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 = PROD
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=True,
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
- cifToPdb(pdbRaw, convertedPdb)
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