modelcraft 5.0.3__py3-none-any.whl → 6.0.0__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 (90) hide show
  1. modelcraft/__init__.py +16 -31
  2. modelcraft/__main__.py +0 -1
  3. modelcraft/arguments.py +35 -7
  4. modelcraft/combine.py +22 -41
  5. modelcraft/contents.py +188 -164
  6. modelcraft/environ.py +0 -7
  7. modelcraft/geometry.py +39 -27
  8. modelcraft/job.py +6 -5
  9. modelcraft/jobs/acedrg.py +2 -0
  10. modelcraft/jobs/buccaneer.py +22 -4
  11. modelcraft/jobs/comit.py +2 -0
  12. modelcraft/jobs/ctruncate.py +3 -1
  13. modelcraft/jobs/emda.py +2 -0
  14. modelcraft/jobs/findwaters.py +2 -0
  15. modelcraft/jobs/freerflag.py +2 -0
  16. modelcraft/jobs/libg.py +2 -0
  17. modelcraft/jobs/molrep.py +2 -0
  18. modelcraft/jobs/nautilus.py +28 -14
  19. modelcraft/jobs/nucleofind.py +88 -0
  20. modelcraft/jobs/parrot.py +13 -2
  21. modelcraft/jobs/phasematch.py +2 -1
  22. modelcraft/jobs/refmac.py +3 -1
  23. modelcraft/jobs/servalcat.py +36 -2
  24. modelcraft/jobs/sheetbend.py +2 -0
  25. modelcraft/modelcraftem.py +49 -6
  26. modelcraft/modelcraftxray.py +90 -42
  27. modelcraft/monlib.py +55 -52
  28. modelcraft/pdbe.py +54 -0
  29. modelcraft/pipeline.py +1 -1
  30. modelcraft/prune.py +69 -0
  31. modelcraft/reflections.py +11 -1
  32. modelcraft/scripts/contents.py +5 -215
  33. modelcraft/scripts/copies.py +26 -17
  34. modelcraft/scripts/modelcraft.py +1 -0
  35. modelcraft/scripts/sidechains.py +141 -0
  36. modelcraft/scripts/validate.py +81 -0
  37. modelcraft/sequence.py +106 -0
  38. modelcraft/solvent.py +42 -113
  39. modelcraft/structure.py +64 -41
  40. modelcraft/tests/ccp4/__init__.py +7 -11
  41. modelcraft/tests/ccp4/test_acedrg.py +2 -0
  42. modelcraft/tests/ccp4/test_arguments.py +3 -0
  43. modelcraft/tests/ccp4/test_buccaneer.py +3 -2
  44. modelcraft/tests/ccp4/test_cell.py +4 -1
  45. modelcraft/tests/ccp4/test_comit.py +2 -0
  46. modelcraft/tests/ccp4/test_contents.py +99 -17
  47. modelcraft/tests/ccp4/test_copies.py +1 -0
  48. modelcraft/tests/ccp4/test_ctruncate.py +2 -0
  49. modelcraft/tests/ccp4/test_findwaters.py +2 -0
  50. modelcraft/tests/ccp4/test_freerflag.py +2 -0
  51. modelcraft/tests/ccp4/test_libg.py +1 -0
  52. modelcraft/tests/ccp4/test_molrep.py +3 -0
  53. modelcraft/tests/ccp4/test_monlib.py +75 -45
  54. modelcraft/tests/ccp4/test_nautilus.py +5 -3
  55. modelcraft/tests/ccp4/test_nucleofind.py +62 -0
  56. modelcraft/tests/ccp4/test_parrot.py +3 -1
  57. modelcraft/tests/ccp4/test_phasematch.py +2 -0
  58. modelcraft/tests/ccp4/test_prune.py +17 -0
  59. modelcraft/tests/ccp4/test_reflections.py +110 -1
  60. modelcraft/tests/ccp4/test_refmac.py +3 -0
  61. modelcraft/tests/{unittests/test_contents.py → ccp4/test_sequence.py} +5 -12
  62. modelcraft/tests/ccp4/test_servalcat.py +52 -0
  63. modelcraft/tests/ccp4/test_sheetbend.py +4 -3
  64. modelcraft/tests/ccp4/test_sidechains.py +25 -0
  65. modelcraft/tests/ccp4/test_solvent.py +12 -26
  66. modelcraft/tests/ccp4/test_structure.py +1 -0
  67. modelcraft/tests/ccp4/test_validation.py +19 -0
  68. modelcraft/tests/ccp4/test_xray.py +12 -6
  69. modelcraft/tests/ccpem/test_em.py +3 -0
  70. modelcraft/tests/ccpem/test_emda.py +2 -0
  71. modelcraft/tests/ccpem/test_refmac.py +1 -0
  72. modelcraft/tests/ccpem/test_servalcat.py +4 -3
  73. modelcraft/utils.py +16 -4
  74. modelcraft/validation.py +101 -0
  75. modelcraft-6.0.0.dist-info/METADATA +76 -0
  76. modelcraft-6.0.0.dist-info/RECORD +85 -0
  77. {modelcraft-5.0.3.dist-info → modelcraft-6.0.0.dist-info}/WHEEL +1 -1
  78. {modelcraft-5.0.3.dist-info → modelcraft-6.0.0.dist-info}/entry_points.txt +2 -0
  79. modelcraft/coot/prune.py +0 -1085
  80. modelcraft/coot/sidechains.py +0 -68
  81. modelcraft/jobs/acorn.py +0 -114
  82. modelcraft/jobs/coot.py +0 -104
  83. modelcraft/tests/ccp4/test_coot.py +0 -29
  84. modelcraft/tests/ccp4/test_geometry.py +0 -20
  85. modelcraft/tests/unittests/__init__.py +0 -0
  86. modelcraft/tests/unittests/test_reflections.py +0 -101
  87. modelcraft-5.0.3.dist-info/METADATA +0 -49
  88. modelcraft-5.0.3.dist-info/RECORD +0 -82
  89. modelcraft-5.0.3.dist-info/licenses/LICENSE +0 -504
  90. {modelcraft-5.0.3.dist-info → modelcraft-6.0.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
3
- from ..reflections import DataItem, write_mtz
4
+
4
5
  from ..job import Job
6
+ from ..reflections import DataItem, write_mtz
5
7
 
6
8
 
7
9
  @dataclasses.dataclass
modelcraft/jobs/emda.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..job import Job
4
6
  from ..maps import read_map
5
7
 
@@ -1,5 +1,7 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..job import Job
4
6
  from ..reflections import DataItem, write_mtz
5
7
  from ..structure import read_structure, write_mmcif
@@ -1,5 +1,7 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..job import Job
4
6
  from ..reflections import DataItem
5
7
 
modelcraft/jobs/libg.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..job import Job
4
6
  from ..structure import write_mmcif
5
7
 
modelcraft/jobs/molrep.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import dataclasses
2
2
  import xml.etree.ElementTree as ET
3
+
3
4
  import gemmi
5
+
4
6
  from ..job import Job
5
7
  from ..reflections import DataItem, write_mtz
6
8
  from ..structure import read_structure
@@ -1,10 +1,13 @@
1
1
  import dataclasses
2
2
  import xml.etree.ElementTree as ET
3
+
3
4
  import gemmi
4
- from ..contents import AsuContents, PolymerType
5
+
6
+ from ..contents import AsuContents
5
7
  from ..job import Job
6
8
  from ..reflections import DataItem, write_mtz
7
- from ..structure import read_structure, remove_non_library_atoms, write_mmcif
9
+ from ..sequence import DNA_CODES, PolymerType
10
+ from ..structure import read_structure, write_mmcif
8
11
 
9
12
 
10
13
  @dataclasses.dataclass
@@ -16,6 +19,18 @@ class NautilusResult:
16
19
  longest_fragment: int
17
20
  seconds: float
18
21
 
22
+ def __init__(self, job: Job):
23
+ job._check_files_exist("xmlout.xml", "xyzout.cif")
24
+ xml = ET.parse(job._path("xmlout.xml")).getroot()
25
+ structure = read_structure(job._path("xyzout.cif"))
26
+ _deoxyfy(structure)
27
+ self.structure = structure
28
+ self.fragments_built = int(xml.find("Final/FragmentsBuilt").text)
29
+ self.residues_built = int(xml.find("Final/ResiduesBuilt").text)
30
+ self.residues_sequenced = int(xml.find("Final/ResiduesSequenced").text)
31
+ self.longest_fragment = int(xml.find("Final/ResiduesLongestFragment").text)
32
+ self.seconds = job._seconds
33
+
19
34
 
20
35
  class Nautilus(Job):
21
36
  def __init__(
@@ -63,15 +78,14 @@ class Nautilus(Job):
63
78
  self._args += ["-xmlout", "xmlout.xml"]
64
79
 
65
80
  def _result(self) -> NautilusResult:
66
- self._check_files_exist("xmlout.xml", "xyzout.cif")
67
- xml = ET.parse(self._path("xmlout.xml")).getroot()
68
- structure = read_structure(self._path("xyzout.cif"))
69
- remove_non_library_atoms(structure)
70
- return NautilusResult(
71
- structure=structure,
72
- fragments_built=int(xml.find("Final/FragmentsBuilt").text),
73
- residues_built=int(xml.find("Final/ResiduesBuilt").text),
74
- residues_sequenced=int(xml.find("Final/ResiduesSequenced").text),
75
- longest_fragment=int(xml.find("Final/ResiduesLongestFragment").text),
76
- seconds=self._seconds,
77
- )
81
+ return NautilusResult(self)
82
+
83
+
84
+ def _deoxyfy(structure: gemmi.Structure) -> None:
85
+ dna_codes = set(DNA_CODES.values())
86
+ for chain in structure[0]:
87
+ for residue in chain:
88
+ if residue.name in dna_codes and "O2'" in residue:
89
+ for i, atom in reversed(list(enumerate(residue))):
90
+ if atom.name == "O2'":
91
+ del residue[i]
@@ -0,0 +1,88 @@
1
+ import dataclasses
2
+
3
+ import gemmi
4
+
5
+ from ..contents import AsuContents, PolymerType
6
+ from ..job import Job
7
+ from ..jobs.nautilus import NautilusResult
8
+ from ..reflections import DataItem, write_mtz
9
+ from ..structure import write_mmcif
10
+
11
+
12
+ @dataclasses.dataclass
13
+ class NucleoFindPrediction:
14
+ phosphate: gemmi.Ccp4Map
15
+ sugar: gemmi.Ccp4Map
16
+ base: gemmi.Ccp4Map
17
+ seconds: float
18
+
19
+
20
+ class NucleoFindPredict(Job):
21
+ def __init__(self, fphi: DataItem):
22
+ super().__init__("nucleofind")
23
+ self.fphi = fphi
24
+
25
+ def _setup(self) -> None:
26
+ write_mtz(self._path("hklin.mtz"), [self.fphi])
27
+ self._args += ["--input", "hklin.mtz"]
28
+ self._args += ["--amplitude", self.fphi.label(0)]
29
+ self._args += ["--phase", self.fphi.label(1)]
30
+ self._args += ["--model", "core"]
31
+ self._args += ["--gpu"]
32
+ self._args += ["--nthreads", "1"]
33
+ self._args += ["--output", "."]
34
+
35
+ def _result(self) -> NucleoFindPrediction:
36
+ self._check_files_exist("nucleofind-phosphate.map")
37
+ self._check_files_exist("nucleofind-sugar.map")
38
+ self._check_files_exist("nucleofind-base.map")
39
+
40
+ return NucleoFindPrediction(
41
+ phosphate=gemmi.read_ccp4_map(self._path("nucleofind-phosphate.map")),
42
+ sugar=gemmi.read_ccp4_map(self._path("nucleofind-sugar.map")),
43
+ base=gemmi.read_ccp4_map(self._path("nucleofind-base.map")),
44
+ seconds=self._seconds,
45
+ )
46
+
47
+
48
+ class NucleoFindBuild(Job):
49
+ def __init__(
50
+ self,
51
+ contents: AsuContents,
52
+ fphi: DataItem,
53
+ prediction: NucleoFindPrediction,
54
+ structure: gemmi.Structure = None,
55
+ em: bool = False,
56
+ ):
57
+ super().__init__("nucleofind-build")
58
+ self.contents = contents
59
+ self.fphi = fphi
60
+ self.prediction = prediction
61
+ self.structure = structure
62
+ self.em = em
63
+
64
+ def _setup(self) -> None:
65
+ types = [PolymerType.RNA, PolymerType.DNA]
66
+ self.contents.write_sequence_file(self._path("seqin.seq"), types)
67
+ self._args += ["--seqin", "seqin.seq"]
68
+ write_mtz(self._path("hklin.mtz"), [self.fphi])
69
+ self._args += ["--mtzin", "hklin.mtz"]
70
+ self._args += ["--colin-fc", self.fphi.label()]
71
+ self.prediction.phosphate.write_ccp4_map(self._path("phosin.map"))
72
+ self.prediction.sugar.write_ccp4_map(self._path("sugarin.map"))
73
+ self.prediction.base.write_ccp4_map(self._path("basein.map"))
74
+ self._args += ["--phosin", "phosin.map"]
75
+ self._args += ["--sugarin", "sugarin.map"]
76
+ self._args += ["--basein", "basein.map"]
77
+ if self.structure is not None:
78
+ write_mmcif(self._path("xyzin.cif"), self.structure)
79
+ self._args += ["--pdbin", "xyzin.cif"]
80
+ self._args += ["--cycles", "3"]
81
+ self._args += ["--pdbout", "xyzout.cif"]
82
+ self._args += ["--xmlout", "xmlout.xml"]
83
+ self._args += ["--remove_clashing_protein"]
84
+ if self.em:
85
+ self._args += ["--em"]
86
+
87
+ def _result(self) -> NautilusResult:
88
+ return NautilusResult(self)
modelcraft/jobs/parrot.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..contents import AsuContents
4
6
  from ..job import Job
7
+ from ..monlib import MonLib
5
8
  from ..reflections import DataItem, write_mtz
6
9
  from ..solvent import solvent_fraction
7
10
  from ..structure import write_mmcif
@@ -23,12 +26,14 @@ class Parrot(Job):
23
26
  phases: DataItem,
24
27
  fphi: DataItem = None,
25
28
  structure: gemmi.Structure = None,
29
+ monlib: MonLib = None,
26
30
  ):
27
31
  super().__init__("cparrot")
28
32
  self.contents = contents
29
33
  self.fsigf = fsigf
30
34
  self.freer = freer
31
35
  self.phases = phases
36
+ self.monlib = monlib
32
37
  self.fphi = fphi
33
38
  self.structure = structure
34
39
 
@@ -49,8 +54,14 @@ class Parrot(Job):
49
54
  if self.structure is not None:
50
55
  write_mmcif(self._path("xyzin.cif"), self.structure)
51
56
  self._args += ["-pdbin-mr", "xyzin.cif"]
52
- solvent_content = solvent_fraction(self.contents, self.fsigf)
53
- self._args += ["-solvent-content", "%.3f" % solvent_content]
57
+ solvent_content = solvent_fraction(
58
+ contents=self.contents,
59
+ cell=self.fsigf.cell,
60
+ spacegroup=self.fsigf.spacegroup,
61
+ resolution=self.fsigf.resolution_high(),
62
+ monlib=self.monlib,
63
+ )
64
+ self._args += ["-solvent-content", f"{solvent_content:.3f}"]
54
65
  self._args += ["-cycles", "5"]
55
66
  self._args += ["-anisotropy-correction"]
56
67
  self._args += ["-mtzout", "hklout.mtz"]
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+
2
3
  from ..job import Job
3
4
  from ..reflections import DataItem, write_mtz
4
5
 
@@ -35,7 +36,7 @@ class PhaseMatch(Job):
35
36
  def _result(self) -> PhaseMatchResult:
36
37
  self._check_files_exist("hklout.mtz")
37
38
  path = self._path("stdout.txt")
38
- with open(path) as stream:
39
+ with open(path, encoding="utf-8") as stream:
39
40
  for line in stream:
40
41
  if line[:19] == "Overall statistics:":
41
42
  keys = next(stream).split()
modelcraft/jobs/refmac.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import dataclasses
2
2
  import shutil
3
3
  import xml.etree.ElementTree as ET
4
+
4
5
  import gemmi
6
+
5
7
  from ..job import Job
6
8
  from ..reflections import DataItem, write_mtz
7
9
  from ..structure import read_structure, write_mmcif
@@ -98,7 +100,7 @@ class Refmac(Job):
98
100
  abcd=DataItem(mtz, "HLACOMB,HLBCOMB,HLCCOMB,HLDCOMB"),
99
101
  fphi_best=DataItem(mtz, "FWT,PHWT"),
100
102
  fphi_diff=DataItem(mtz, "DELFWT,PHDELWT"),
101
- fphi_calc=DataItem(mtz, "FC_ALL_LS,PHIC_ALL_LS"),
103
+ fphi_calc=DataItem(mtz, "FC_ALL,PHIC_ALL"),
102
104
  rwork=float(rworks[-1].text),
103
105
  rfree=float(rfrees[-1].text),
104
106
  fsc=float(fscs[-1].text),
@@ -1,12 +1,46 @@
1
1
  import dataclasses
2
2
  import shutil
3
+
3
4
  import gemmi
5
+
4
6
  from ..job import Job
5
7
  from ..maps import read_map
6
- from ..reflections import DataItem
8
+ from ..reflections import DataItem, write_mtz
7
9
  from ..structure import read_structure, write_mmcif
8
10
 
9
11
 
12
+ @dataclasses.dataclass
13
+ class ServalcatFwResult:
14
+ fmean: DataItem
15
+ fanom: DataItem
16
+ imean: DataItem
17
+ seconds: float
18
+
19
+
20
+ class ServalcatFw(Job):
21
+ def __init__(self, observations: DataItem):
22
+ super().__init__("servalcat")
23
+ self.observations = observations
24
+
25
+ def _setup(self) -> None:
26
+ write_mtz(self._path("hklin.mtz"), [self.observations])
27
+ self._args += ["fw", "--hklin", "hklin.mtz", "-o", "output"]
28
+
29
+ def _result(self) -> ServalcatFwResult:
30
+ self._check_files_exist("output.mtz")
31
+ mtz = gemmi.read_mtz_file(self._path("output.mtz"))
32
+ result = ServalcatFwResult(
33
+ fmean=DataItem(mtz, "F,SIGF"),
34
+ fanom=None,
35
+ imean=None,
36
+ seconds=self._seconds,
37
+ )
38
+ if self.observations.types == "KMKM":
39
+ result.fanom = DataItem(mtz, "F(+),SIGF(+),F(-),SIGF(-)")
40
+ result.imean = DataItem(mtz, "I,SIGI")
41
+ return result
42
+
43
+
10
44
  @dataclasses.dataclass
11
45
  class ServalcatNemapResult:
12
46
  fphi: DataItem
@@ -190,7 +224,7 @@ class ServalcatFsc(Job):
190
224
  def _result(self) -> ServalcatFscResult:
191
225
  self._check_files_exist("fsc.dat")
192
226
  fsc = None
193
- with open(self._path("fsc.dat")) as text:
227
+ with open(self._path("fsc.dat"), encoding="utf-8") as text:
194
228
  for line in text:
195
229
  if line.startswith("# FSCaverage of fsc_FC_full ="):
196
230
  fsc = float(line.strip().split()[-1])
@@ -1,5 +1,7 @@
1
1
  import dataclasses
2
+
2
3
  import gemmi
4
+
3
5
  from ..job import Job
4
6
  from ..reflections import DataItem, write_mtz
5
7
  from ..structure import read_structure, write_mmcif
@@ -1,10 +1,14 @@
1
1
  import os
2
+ import shutil
2
3
  import time
4
+
3
5
  import gemmi
6
+
4
7
  from . import __version__
5
8
  from .jobs.buccaneer import Buccaneer
6
9
  from .jobs.emda import EmdaMapMask
7
10
  from .jobs.nautilus import Nautilus
11
+ from .jobs.nucleofind import NucleoFindBuild, NucleoFindPredict
8
12
  from .jobs.refmac import RefmacMapToMtz
9
13
  from .jobs.servalcat import ServalcatFsc, ServalcatNemap, ServalcatRefine, ServalcatTrim
10
14
  from .maps import read_map
@@ -28,24 +32,36 @@ class ModelCraftEm(Pipeline):
28
32
  self.maps = {}
29
33
  self.fmean = None
30
34
  self.phases = None
35
+ self.fphi = None
36
+ self.nucleofind_prediction = None
31
37
 
32
38
  def run(self):
33
39
  print(f"# ModelCraft {__version__}", flush=True)
34
40
  os.makedirs(self.args.directory, exist_ok=self.args.overwrite_directory)
35
41
  self.start_time = time.time()
36
42
  self._read_input_maps()
43
+ original_map_centre = self.map_centre()
37
44
  self._trim_input_maps()
38
45
  self._calculate_fmean_and_phases()
39
46
  structure = self.args.model
40
47
  best_fsc = None
41
48
  cycles_without_improvement = 0
49
+ build_nucleic = self.args.contents.rnas or self.args.contents.dnas
50
+ if build_nucleic and shutil.which("nucleofind"):
51
+ try:
52
+ self.nucleofind_prediction = NucleoFindPredict(self.fphi).run(self)
53
+ except FileNotFoundError:
54
+ print("Warning: nucleofind prediction failed", flush=True)
42
55
  for cycle in range(1, self.args.cycles + 1):
43
56
  print(f"\n## Cycle {cycle}\n", flush=True)
44
57
  if self.args.contents.proteins:
45
58
  structure = self.buccaneer(structure)
46
59
  structure = self.servalcat_refine(structure)
47
- if self.args.contents.rnas or self.args.contents.dnas:
48
- structure = self.nautilus(structure)
60
+ if build_nucleic:
61
+ if self.nucleofind_prediction is None:
62
+ structure = self.nautilus(structure)
63
+ else:
64
+ structure = self.nucleofind(structure)
49
65
  structure = self.servalcat_refine(structure)
50
66
  model_stats = ModelStats(structure)
51
67
  fsc = self.servalcat_fsc(structure)
@@ -54,6 +70,7 @@ class ModelCraftEm(Pipeline):
54
70
  if best_fsc is None or fsc > best_fsc:
55
71
  best_fsc = fsc
56
72
  cycles_without_improvement = 0
73
+ self.shift(structure, original_map_centre)
57
74
  write_mmcif(self.path("modelcraft.cif"), structure)
58
75
  self.report["final"] = stats
59
76
  else:
@@ -88,21 +105,21 @@ class ModelCraftEm(Pipeline):
88
105
  density=self.maps["build_map"],
89
106
  resolution=self.args.resolution,
90
107
  ).run(self)
91
- fphi = refmac.fphi
108
+ self.fphi = refmac.fphi
92
109
  elif self.args.half_maps:
93
110
  nemap = ServalcatNemap(
94
111
  halfmap1=self.maps["half_map1"],
95
112
  halfmap2=self.maps["half_map2"],
96
113
  resolution=self.args.resolution,
97
114
  ).run(self)
98
- fphi = nemap.fphi
115
+ self.fphi = nemap.fphi
99
116
  else:
100
117
  refmac = RefmacMapToMtz(
101
118
  density=self.maps["single_map"],
102
119
  resolution=self.args.resolution,
103
120
  ).run(self)
104
- fphi = refmac.fphi
105
- self.fmean, self.phases = convert_to_fsigf_and_phifom(fphi)
121
+ self.fphi = refmac.fphi
122
+ self.fmean, self.phases = convert_to_fsigf_and_phifom(self.fphi)
106
123
 
107
124
  def buccaneer(self, structure: gemmi.Structure) -> gemmi.Structure:
108
125
  result = Buccaneer(
@@ -126,6 +143,16 @@ class ModelCraftEm(Pipeline):
126
143
  ).run(self)
127
144
  return result.structure
128
145
 
146
+ def nucleofind(self, structure: gemmi.Structure) -> gemmi.Structure:
147
+ result = NucleoFindBuild(
148
+ contents=self.args.contents,
149
+ fphi=self.fphi,
150
+ prediction=self.nucleofind_prediction,
151
+ structure=structure,
152
+ em=True,
153
+ ).run(self)
154
+ return result.structure
155
+
129
156
  def servalcat_refine(self, structure: gemmi.Structure) -> gemmi.Structure:
130
157
  if ModelStats(structure).residues == 0:
131
158
  self.terminate(reason="No residues to refine")
@@ -148,3 +175,19 @@ class ModelCraftEm(Pipeline):
148
175
  density=self.maps.get("single_map"),
149
176
  ).run(self)
150
177
  return result.fsc
178
+
179
+ def map_centre(self):
180
+ first_map = next(iter(self.maps.values()))
181
+ fractional = gemmi.Fractional(0.5, 0.5, 0.5)
182
+ return first_map.grid.unit_cell.orthogonalize(fractional)
183
+
184
+ def shift(self, structure: gemmi.Structure, original_map_centre: gemmi.Position):
185
+ model_centre = structure[0].calculate_center_of_mass()
186
+ diff = original_map_centre - model_centre
187
+ shift = gemmi.Vec3(
188
+ round(diff[0] / structure.cell.a) * structure.cell.a,
189
+ round(diff[1] / structure.cell.b) * structure.cell.b,
190
+ round(diff[2] / structure.cell.c) * structure.cell.c,
191
+ )
192
+ transform = gemmi.Transform(gemmi.Mat33(), shift)
193
+ structure[0].transform_pos_and_adp(transform)