servalcat 0.4.88__cp313-cp313-win_amd64.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.

Potentially problematic release.


This version of servalcat might be problematic. Click here for more details.

Files changed (45) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cp313-win_amd64.pyd +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +823 -0
  7. servalcat/refine/refine_geom.py +220 -0
  8. servalcat/refine/refine_spa.py +345 -0
  9. servalcat/refine/refine_xtal.py +268 -0
  10. servalcat/refine/spa.py +136 -0
  11. servalcat/refine/xtal.py +273 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +182 -0
  14. servalcat/refmac/refmac_keywords.py +639 -0
  15. servalcat/refmac/refmac_wrapper.py +403 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +473 -0
  18. servalcat/spa/fsc.py +387 -0
  19. servalcat/spa/localcc.py +188 -0
  20. servalcat/spa/realspcc_from_var.py +128 -0
  21. servalcat/spa/run_refmac.py +972 -0
  22. servalcat/spa/shift_maps.py +293 -0
  23. servalcat/spa/shiftback.py +137 -0
  24. servalcat/spa/translate.py +129 -0
  25. servalcat/utils/__init__.py +35 -0
  26. servalcat/utils/commands.py +1397 -0
  27. servalcat/utils/fileio.py +737 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +712 -0
  30. servalcat/utils/logger.py +116 -0
  31. servalcat/utils/maps.py +345 -0
  32. servalcat/utils/model.py +782 -0
  33. servalcat/utils/refmac.py +760 -0
  34. servalcat/utils/restraints.py +782 -0
  35. servalcat/utils/symmetry.py +295 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +256 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1622 -0
  40. servalcat/xtal/twin.py +115 -0
  41. servalcat-0.4.88.dist-info/METADATA +55 -0
  42. servalcat-0.4.88.dist-info/RECORD +45 -0
  43. servalcat-0.4.88.dist-info/WHEEL +5 -0
  44. servalcat-0.4.88.dist-info/entry_points.txt +4 -0
  45. servalcat-0.4.88.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,1397 @@
1
+ """
2
+ Author: "Keitaro Yamashita, Garib N. Murshudov"
3
+ MRC Laboratory of Molecular Biology
4
+
5
+ This software is released under the
6
+ Mozilla Public License, version 2.0; see LICENSE.
7
+ """
8
+ from __future__ import absolute_import, division, print_function, generators
9
+ from servalcat.utils import logger
10
+ from servalcat.utils import fileio
11
+ from servalcat.utils import symmetry
12
+ from servalcat.utils import model
13
+ from servalcat.utils import hkl
14
+ from servalcat.utils import restraints
15
+ from servalcat.utils import maps
16
+ from servalcat.refmac import refmac_keywords
17
+ from servalcat.refine.refine import Geom
18
+ from servalcat import ext
19
+ import os
20
+ import gemmi
21
+ import numpy
22
+ import scipy.spatial
23
+ import pandas
24
+ import json
25
+ import re
26
+ import argparse
27
+
28
+ def add_arguments(p):
29
+ subparsers = p.add_subparsers(dest="subcommand")
30
+
31
+ # show
32
+ parser = subparsers.add_parser("show", description = 'Show file info supported by the program')
33
+ parser.add_argument('files', nargs='+')
34
+
35
+ # json2csv
36
+ parser = subparsers.add_parser("json2csv", description = 'Convert json to csv for plotting')
37
+ parser.add_argument('json')
38
+ parser.add_argument('-o', '--output_prefix')
39
+
40
+ # symmodel
41
+ parser = subparsers.add_parser("symmodel", description="Add symmetry annotation to model")
42
+ parser.add_argument('--model', required=True)
43
+ group = parser.add_mutually_exclusive_group()
44
+ group.add_argument('--map', help="Take box size from the map")
45
+ group.add_argument('--cell', type=float, nargs=6, metavar=("a", "b", "c", "alpha", "beta", "gamma"),
46
+ help="Box size")
47
+ sym_group = parser.add_argument_group("symmetry")
48
+ symmetry.add_symmetry_args(sym_group, require_pg=True)
49
+ parser.add_argument('--contacting_only', action="store_true", help="Filter out non-contacting NCS")
50
+ parser.add_argument('--chains', nargs="*", action="append", help="Select chains to keep")
51
+ parser.add_argument('--howtoname', choices=["dup", "short", "number"], default="short",
52
+ help="How to decide new chain IDs in expanded model (default: short); "
53
+ "dup: use original chain IDs (with different segment IDs), "
54
+ "short: use unique new IDs, "
55
+ "number: add number to original chain ID")
56
+ parser.add_argument('--biomt', action="store_true", help="Add BIOMT also")
57
+ parser.add_argument('-o', '--output_prfix')
58
+ parser.add_argument('--pdb', action="store_true", help="Write a pdb file")
59
+ parser.add_argument('--cif', action="store_true", help="Write a cif file")
60
+
61
+ # helical_biomt
62
+ parser = subparsers.add_parser("helical_biomt", description="generate BIOMT of helical reconstruction for PDB deposition")
63
+ parser.add_argument('--model', required=True)
64
+ group = parser.add_mutually_exclusive_group()
65
+ group.add_argument('--map', help="Take box size from the map")
66
+ group.add_argument('--cell', type=float, nargs=6, metavar=("a", "b", "c", "alpha", "beta", "gamma"),
67
+ help="Box size")
68
+ sym_group = parser.add_argument_group("symmetry")
69
+ symmetry.add_symmetry_args(sym_group, require_pg=True)
70
+ parser.add_argument('--start', type=int)
71
+ parser.add_argument('--end', type=int)
72
+ parser.add_argument('--howtoname', choices=["dup", "short", "number"], default="short",
73
+ help="How to decide new chain IDs in expanded model (default: short); "
74
+ "dup: use original chain IDs (with different segment IDs), "
75
+ "short: use unique new IDs, "
76
+ "number: add number to original chain ID")
77
+ parser.add_argument('-o', '--output_prfix')
78
+
79
+ # expand
80
+ parser = subparsers.add_parser("expand", description="Expand symmetry")
81
+ parser.add_argument('--model', required=True)
82
+ parser.add_argument('--chains', nargs="*", action="append", help="Select chains to keep")
83
+ group = parser.add_mutually_exclusive_group()
84
+ group.add_argument('--howtoname', choices=["dup", "short", "number"], default="short",
85
+ help="How to decide new chain IDs in expanded model (default: short); "
86
+ "dup: use original chain IDs (with different segment IDs), "
87
+ "short: use unique new IDs, "
88
+ "number: add number to original chain ID")
89
+ group.add_argument("--split", action="store_true", help="split file for each operator")
90
+ parser.add_argument('-o', '--output_prfix')
91
+ parser.add_argument('--pdb', action="store_true", help="Write a pdb file")
92
+ parser.add_argument('--cif', action="store_true", help="Write a cif file")
93
+
94
+ # h_add
95
+ parser = subparsers.add_parser("h_add", description = 'Add hydrogen in riding position')
96
+ parser.add_argument('model')
97
+ parser.add_argument('--ligand', nargs="*", action="append")
98
+ parser.add_argument("--monlib",
99
+ help="Monomer library path. Default: $CLIBD_MON")
100
+ parser.add_argument('-o','--output')
101
+ parser.add_argument("--pos", choices=["elec", "nucl"], default="elec")
102
+
103
+ # add_op3
104
+ parser = subparsers.add_parser("add_op3", description = "Add OP3 atoms to 5' ends")
105
+ parser.add_argument('model')
106
+ parser.add_argument('--ligand', nargs="*", action="append")
107
+ parser.add_argument("--monlib",
108
+ help="Monomer library path. Default: $CLIBD_MON")
109
+ parser.add_argument('-o','--output')
110
+
111
+ # map_peaks
112
+ parser = subparsers.add_parser("map_peaks", description = 'List density peaks and write a coot script')
113
+ parser.add_argument('--model', required=True, help="Model")
114
+ group = parser.add_mutually_exclusive_group(required=True)
115
+ group.add_argument('--map', help="Map file")
116
+ group.add_argument('--mtz', help="MTZ for map file")
117
+ parser.add_argument('--mtz_labels', default="DELFWT,PHDELWT", help='F,PHI labels (default: %(default)s)')
118
+ parser.add_argument('--oversample_pixel', type=float, help='Desired pixel spacing in map (Angstrom)')
119
+ group = parser.add_mutually_exclusive_group(required=True)
120
+ group.add_argument('--sigma_level', type=float, help="Threshold map level in sigma unit")
121
+ group.add_argument('--abs_level', type=float, help="Threshold map level in absolute unit")
122
+ parser.add_argument('--blob_pos', choices=["peak", "centroid"], default="centroid",
123
+ help="default: %(default)s")
124
+ parser.add_argument('--min_volume', type=float, default=0.3, help="minimum blob volume (default: %(default).1f)")
125
+ parser.add_argument('--max_volume', type=float, help="maximum blob volume (default: none)")
126
+ parser.add_argument('-o','--output_prefix', default="peaks")
127
+
128
+ # h_density
129
+ parser = subparsers.add_parser("h_density", description = 'Hydrogen density analysis')
130
+ parser.add_argument('--model', required=True, help="Model with hydrogen atoms")
131
+ group = parser.add_mutually_exclusive_group(required=True)
132
+ group.add_argument('--map', help="Fo-Fc map file")
133
+ group.add_argument('--mtz', help="MTZ for Fo-Fc map file")
134
+ parser.add_argument('--mtz_labels', default="DELFWT,PHDELWT", help='F,PHI labels (default: %(default)s)')
135
+ parser.add_argument('--oversample_pixel', type=float, help='Desired pixel spacing in map (Angstrom)')
136
+ #parser.add_argument("--source", choices=["electron", "xray", "neutron"], default="electron")
137
+ group = parser.add_mutually_exclusive_group(required=True)
138
+ group.add_argument('--sigma_level', type=float, help="Threshold map level in sigma unit")
139
+ group.add_argument('--abs_level', type=float, help="Threshold map level in absolute unit")
140
+ parser.add_argument('--max_dist', type=float, default=0.5, help="max distance between peak and hydrogen position in the model (default: %(default).1f)")
141
+ parser.add_argument('--blob_pos', choices=["peak", "centroid"], default="centroid",
142
+ help="default: %(default)s")
143
+ parser.add_argument('--min_volume', type=float, default=0.3, help="minimum blob volume (default: %(default).1f)")
144
+ parser.add_argument('--max_volume', type=float, default=3, help="maximum blob volume (default: %(default).1f)")
145
+ parser.add_argument('-o','--output_prefix')
146
+
147
+ # fix_link
148
+ parser = subparsers.add_parser("fix_link", description = 'Fix LINKR/_struct_conn records in the model')
149
+ parser.add_argument('model')
150
+ parser.add_argument('--ligand', nargs="*", action="append")
151
+ parser.add_argument("--monlib",
152
+ help="Monomer library path. Default: $CLIBD_MON")
153
+ parser.add_argument('--bond_margin', type=float, default=1.3, help='(default: %(default).1f)')
154
+ parser.add_argument('--metal_margin', type=float, default=1.1, help='(default: %(default).1f)')
155
+ parser.add_argument('-o','--output', help="Default: input_fixlink.{pdb|mmcif}")
156
+
157
+ # merge_models
158
+ parser = subparsers.add_parser("merge_models", description = 'Merge multiple model files')
159
+ parser.add_argument('models', nargs="+")
160
+ parser.add_argument('-o','--output', required=True)
161
+
162
+ # merge_dicts
163
+ parser = subparsers.add_parser("merge_dicts", description = 'Merge restraint dictionary cif files')
164
+ parser.add_argument('cifs', nargs="+")
165
+ parser.add_argument('-o','--output', default="merged.cif", help="Output cif file (default: %(default)s)")
166
+
167
+ # geom
168
+ parser = subparsers.add_parser("geom", description = 'Calculate geometry and show outliers')
169
+ parser.add_argument('model')
170
+ parser.add_argument('--ligand', nargs="*", action="append")
171
+ parser.add_argument("--monlib",
172
+ help="Monomer library path. Default: $CLIBD_MON")
173
+ parser.add_argument('--keywords', nargs='+', action="append",
174
+ help="refmac keyword(s)")
175
+ parser.add_argument('--keyword_file', nargs='+', action="append",
176
+ help="refmac keyword file(s)")
177
+ parser.add_argument('--sigma', type=float, default=5,
178
+ help="sigma cutoff to print outliers (default: %(default).1f)")
179
+ parser.add_argument('--per_atom_score_as_b', action='store_true',
180
+ help="write model file with per-atom score as B values")
181
+ parser.add_argument("--check_skew", action='store_true', help="(experimental) check bond skew to test magnification")
182
+ parser.add_argument('-n', '--nucleus', action="store_true", help="Use nucleus distances (for neutron)")
183
+ parser.add_argument("--ignore_h", action='store_true', help="ignore hydrogen")
184
+ parser.add_argument("--selection", help="evaluate part of the model")
185
+ parser.add_argument('-o', '--output_prefix',
186
+ help="default: taken from input file")
187
+
188
+ # adp
189
+ parser = subparsers.add_parser("adp", description = 'ADP analysis')
190
+ parser.add_argument('model')
191
+ parser.add_argument('-o', '--output_prefix',
192
+ help="default: taken from input file")
193
+
194
+ # power
195
+ parser = subparsers.add_parser("power", description = 'Show power spectrum')
196
+ parser.add_argument("--map", nargs="*", action="append")
197
+ parser.add_argument("--halfmaps", nargs="*", action="append")
198
+ parser.add_argument('--mask', help='Mask file')
199
+ parser.add_argument('-d', '--resolution', type=float)
200
+ parser.add_argument('-o', '--output_prefix', default="power")
201
+
202
+ # fcalc
203
+ parser = subparsers.add_parser("fcalc", description = 'Structure factor from model')
204
+ parser.add_argument('--model', required=True)
205
+ parser.add_argument("--no_expand_ncs", action='store_true', help="Do not expand strict NCS in MTRIX or _struct_ncs_oper")
206
+ parser.add_argument("--method", choices=["fft", "direct"], default="fft")
207
+ parser.add_argument("--source", choices=["electron", "xray", "neutron"], default="electron")
208
+ parser.add_argument('--ligand', nargs="*", action="append")
209
+ parser.add_argument("--monlib",
210
+ help="Monomer library path. Default: $CLIBD_MON")
211
+ parser.add_argument('--cell', type=float, nargs=6, metavar=("a", "b", "c", "alpha", "beta", "gamma"),
212
+ help="Override unit cell")
213
+ parser.add_argument('--auto_box_with_padding', type=float, help="Determine box size from model with specified padding")
214
+ parser.add_argument('--cutoff', type=float, default=1e-5)
215
+ parser.add_argument('--rate', type=float, default=1.5)
216
+ parser.add_argument('--add_dummy_sigma', action='store_true', help="write dummy SIGF")
217
+ parser.add_argument('--as_intensity', action='store_true', help="if you want |F|^2")
218
+ parser.add_argument('--keep_charges', action='store_true',
219
+ help="Use scattering factor for charged atoms. Use it with care.")
220
+ parser.add_argument('-d', '--resolution', type=float, required=True)
221
+ parser.add_argument('-o', '--output_prefix')
222
+
223
+ # nemap
224
+ parser = subparsers.add_parser("nemap", description = 'Normalized expected map calculation from half maps')
225
+ parser.add_argument("--halfmaps", required=True, nargs=2)
226
+ parser.add_argument('--pixel_size', type=float, help='Override pixel size (A)')
227
+ parser.add_argument("--half1_only", action='store_true', help="Only use half 1 for map calculation (use half 2 only for noise estimation)")
228
+ parser.add_argument('-B', type=float, help="local B value")
229
+ parser.add_argument("--no_fsc_weights", action='store_true',
230
+ help="Just for debugging purpose: turn off FSC-based weighting")
231
+ parser.add_argument("--sharpening_b", type=float,
232
+ help="Use B value (negative value for sharpening) instead of standard deviation of the signal")
233
+ parser.add_argument("-d", '--resolution', type=float)
234
+ parser.add_argument('-m', '--mask', help="mask file")
235
+ parser.add_argument('-o', '--output_prefix', default='nemap')
236
+ parser.add_argument("--trim", action='store_true', help="Write trimmed maps")
237
+ parser.add_argument("--trim_mtz", action='store_true', help="Write trimmed mtz")
238
+ parser.add_argument("--local_fourier_weighting_with", type=float, default=0,
239
+ help="Experimental: give kernel size in A^-1 unit to use local Fourier weighting instead of resolution-dependent weights")
240
+
241
+ # blur
242
+ parser = subparsers.add_parser("blur", description = 'Blur data by specified B value')
243
+ parser.add_argument('--hklin', required=True, help="input MTZ file")
244
+ parser.add_argument('-B', type=float, required=True, help="B value for blurring (negative value for sharpening)")
245
+ parser.add_argument('-o', '--output_prefix')
246
+
247
+ # mask_from_model
248
+ parser = subparsers.add_parser("mask_from_model", description = 'Make a mask from model')
249
+ parser.add_argument("--map", required=True, help="For unit cell and pixel size reference")
250
+ parser.add_argument("--model", required=True)
251
+ parser.add_argument("--selection")
252
+ parser.add_argument('--radius', type=float, required=True,
253
+ help='Radius in angstrom')
254
+ parser.add_argument('--soft_edge', type=float, default=0,
255
+ help='Soft edge (default: %(default).1f)')
256
+ parser.add_argument('-o', '--output', default="mask_from_model.mrc")
257
+
258
+ # applymask (and normalize within mask)
259
+ parser = subparsers.add_parser("applymask", description = 'Apply mask and optionally normalize map within mask')
260
+ parser.add_argument("--map", required=True)
261
+ parser.add_argument('--mask', required=True, help='Mask file')
262
+ parser.add_argument("--normalize", action='store_true',
263
+ help="Normalize map values using mean and sd within the mask")
264
+ parser.add_argument("--trim", action='store_true', help="Write trimmed map")
265
+ parser.add_argument('--mask_cutoff', type=float, default=0.5,
266
+ help="cutoff value for normalization and trimming (default: %(default)s)")
267
+ parser.add_argument('-o', '--output_prefix')
268
+
269
+ # map2mtz
270
+ parser = subparsers.add_parser("map2mtz", description = 'FFT map and write an mtz')
271
+ parser.add_argument("--map", required=True)
272
+ parser.add_argument("-d", '--resolution', type=float)
273
+ parser.add_argument('-o', '--output')
274
+
275
+ # sm2mm
276
+ parser = subparsers.add_parser("sm2mm", description = 'Small molecule files (cif/hkl/res/ins) to macromolecules (pdb/mmcif/mtz)')
277
+ parser.add_argument('files', nargs='+', help='Cif/ins/res/hkl files')
278
+ parser.add_argument('-o', '--output_prefix')
279
+
280
+ # seq
281
+ parser = subparsers.add_parser("seq", description = 'Print/align model sequence')
282
+ parser.add_argument("--model", required=True)
283
+ parser.add_argument('--seq', nargs="*", action="append", help="Sequence file(s)")
284
+
285
+ # dnarna
286
+ parser = subparsers.add_parser("dnarna", description = 'DNA to RNA or RNA to DNA model conversion')
287
+ parser.add_argument("model")
288
+ group = parser.add_mutually_exclusive_group(required=True)
289
+ group.add_argument('--to_dna', action='store_true', help="To DNA")
290
+ group.add_argument('--to_rna', action='store_true', help="To RNA")
291
+ parser.add_argument('--chains', nargs="*", action="append", help="Select chains to convert")
292
+ parser.add_argument('-o', '--output')
293
+
294
+ # add_arguments()
295
+
296
+ def parse_args(arg_list):
297
+ parser = argparse.ArgumentParser()
298
+ add_arguments(parser)
299
+ return parser.parse_args(arg_list)
300
+ # parse_args()
301
+
302
+ def symmodel(args):
303
+ if args.chains: args.chains = sum(args.chains, [])
304
+ model_format = fileio.check_model_format(args.model)
305
+
306
+ howtoname = dict(dup=gemmi.HowToNameCopiedChain.Dup,
307
+ short=gemmi.HowToNameCopiedChain.Short,
308
+ number=gemmi.HowToNameCopiedChain.AddNumber)[args.howtoname]
309
+
310
+ if (args.twist, args.rise).count(None) == 1:
311
+ raise SystemExit("ERROR: give both helical parameters --twist and --rise")
312
+
313
+ is_helical = args.twist is not None
314
+ st, cif_ref = fileio.read_structure_from_pdb_and_mmcif(args.model)
315
+ st.spacegroup_hm = "P 1"
316
+ map_and_start = None
317
+ if args.map:
318
+ logger.writeln("Reading cell from map")
319
+ map_and_start = fileio.read_ccp4_map(args.map)
320
+ st.cell = map_and_start[0].unit_cell
321
+ elif args.cell:
322
+ st.cell = gemmi.UnitCell(*args.cell)
323
+ elif not st.cell.is_crystal():
324
+ raise SystemExit("Error: Unit cell parameters look wrong. Please use --map or --cell")
325
+
326
+ if args.chains:
327
+ logger.writeln("Keep {} chains only".format(" ".join(args.chains)))
328
+ chains = set(args.chains)
329
+ for m in st:
330
+ to_del = [c.name for c in m if c.name not in chains]
331
+ for c in to_del: m.remove_chain(c)
332
+ if st[0].count_atom_sites() == 0:
333
+ raise SystemExit("ERROR: no atoms left. Check --chains option.")
334
+
335
+ all_chains = [c.name for c in st[0] if c.name not in st[0]]
336
+
337
+ symmetry.update_ncs_from_args(args, st, map_and_start=map_and_start, filter_contacting=args.contacting_only)
338
+
339
+ if args.biomt:
340
+ st.assemblies.clear()
341
+ st.raw_remarks = []
342
+ a = model.prepare_assembly("1", all_chains, st.ncs, is_helical=is_helical)
343
+ st.assemblies.append(a)
344
+
345
+ if not args.output_prfix:
346
+ args.output_prfix = fileio.splitext(os.path.basename(args.model))[0] + "_asu"
347
+
348
+ if args.pdb or args.cif:
349
+ fileio.write_model(st, args.output_prfix, pdb=args.pdb, cif=args.cif, cif_ref=cif_ref)
350
+ else:
351
+ fileio.write_model(st, file_name=args.output_prfix+model_format, cif_ref=cif_ref)
352
+
353
+ # Sym expand
354
+ model.expand_ncs(st, howtoname=howtoname)
355
+ st.assemblies.clear()
356
+ args.output_prfix += "_expanded"
357
+ if args.pdb or args.cif:
358
+ fileio.write_model(st, args.output_prfix, pdb=args.pdb, cif=args.cif)
359
+ else:
360
+ fileio.write_model(st, file_name=args.output_prfix+model_format)
361
+ # symmodel()
362
+
363
+ def helical_biomt(args):
364
+ if (args.twist, args.rise).count(None) > 0:
365
+ raise SystemExit("ERROR: give helical parameters --twist and --rise")
366
+
367
+ model_format = fileio.check_model_format(args.model)
368
+ howtoname = dict(dup=gemmi.HowToNameCopiedChain.Dup,
369
+ short=gemmi.HowToNameCopiedChain.Short,
370
+ number=gemmi.HowToNameCopiedChain.AddNumber)[args.howtoname]
371
+
372
+ st, cif_ref = fileio.read_structure_from_pdb_and_mmcif(args.model)
373
+ st.spacegroup_hm = "P 1"
374
+ map_and_start = None
375
+ if args.map:
376
+ logger.writeln("Reading cell from map")
377
+ map_and_start = fileio.read_ccp4_map(args.map)
378
+ st.cell = map_and_start[0].unit_cell
379
+ elif args.cell:
380
+ st.cell = gemmi.UnitCell(*args.cell)
381
+ elif not st.cell.is_crystal():
382
+ raise SystemExit("Error: Unit cell parameters look wrong. Please use --map or --cell")
383
+
384
+ all_chains = [c.name for c in st[0] if c.name not in st[0]]
385
+
386
+ ncsops = symmetry.ncsops_from_args(args, st.cell, map_and_start=map_and_start, st=st,
387
+ helical_min_n=args.start, helical_max_n=args.end)
388
+ #ncsops = [x for x in ncsops if not x.tr.is_identity()] # remove identity
389
+
390
+ logger.writeln("")
391
+ logger.writeln("-------------------------------------------------------------")
392
+ logger.writeln("You may need to write following matrices in OneDep interface:")
393
+ for idx, op in enumerate(ncsops):
394
+ logger.writeln("")
395
+ logger.writeln("operator {}".format(idx+1))
396
+ mat = op.tr.mat.tolist()
397
+ vec = op.tr.vec.tolist()
398
+ for i in range(3):
399
+ mstr = ["{:10.6f}".format(mat[i][j]) for j in range(3)]
400
+ logger.writeln("{} {:14.5f}".format(" ".join(mstr), vec[i]))
401
+ logger.writeln("-------------------------------------------------------------")
402
+ logger.writeln("")
403
+
404
+ # BIOMT
405
+ st.assemblies.clear()
406
+ st.raw_remarks = []
407
+ a = model.prepare_assembly("1", all_chains, ncsops, is_helical=True)
408
+ st.assemblies.append(a)
409
+
410
+ if not args.output_prfix:
411
+ args.output_prfix = fileio.splitext(os.path.basename(args.model))[0] + "_biomt"
412
+
413
+ fileio.write_model(st, args.output_prfix, pdb=(model_format == ".pdb"), cif=True, cif_ref=cif_ref)
414
+ logger.writeln("")
415
+ logger.writeln("These {}.* files may be used for deposition (once OneDep implemented reading BIOMT from file..)".format(args.output_prfix))
416
+ logger.writeln("")
417
+ # BIOMT expand
418
+ st.transform_to_assembly("1", howtoname)
419
+ args.output_prfix += "_expanded"
420
+ fileio.write_model(st, file_name=args.output_prfix+model_format)
421
+ logger.writeln(" note that this expanded model file is just for visual inspection, *not* for deposition!")
422
+ # helical_biomt()
423
+
424
+ def symexpand(args):
425
+ if args.chains: args.chains = sum(args.chains, [])
426
+ model_format = fileio.check_model_format(args.model)
427
+ if not args.split:
428
+ howtoname = dict(dup=gemmi.HowToNameCopiedChain.Dup,
429
+ short=gemmi.HowToNameCopiedChain.Short,
430
+ number=gemmi.HowToNameCopiedChain.AddNumber)[args.howtoname]
431
+
432
+ st = fileio.read_structure(args.model)
433
+
434
+ if args.chains:
435
+ logger.writeln("Keep {} chains only".format(" ".join(args.chains)))
436
+ chains = set(args.chains)
437
+ for m in st:
438
+ to_del = [c.name for c in m if c.name not in chains]
439
+ for c in to_del: m.remove_chain(c)
440
+
441
+ all_chains = [c.name for c in st[0] if c.name not in st[0]]
442
+
443
+ if not args.output_prfix:
444
+ args.output_prfix = fileio.splitext(os.path.basename(args.model))[0]
445
+
446
+ if len(st.ncs) > 0:
447
+ symmetry.show_ncs_operators_axis_angle(st.ncs)
448
+ non_given = [op for op in st.ncs if not op.given]
449
+ if len(non_given) > 0:
450
+ if args.split:
451
+ for i, op in enumerate(st.ncs):
452
+ if op.given: continue
453
+ st_tmp = st.clone()
454
+ for m in st_tmp: m.transform_pos_and_adp(op.tr)
455
+ output_prfix = args.output_prfix + "_ncs_{:02d}".format(i+1)
456
+ if args.pdb or args.cif:
457
+ fileio.write_model(st_tmp, output_prfix, pdb=args.pdb, cif=args.cif)
458
+ else:
459
+ fileio.write_model(st_tmp, file_name=output_prfix+model_format)
460
+ else:
461
+ st_tmp = st.clone()
462
+ model.expand_ncs(st_tmp, howtoname=howtoname)
463
+ output_prfix = args.output_prfix + "_ncs_expanded"
464
+ if args.pdb or args.cif:
465
+ fileio.write_model(st_tmp, output_prfix, pdb=args.pdb, cif=args.cif)
466
+ else:
467
+ fileio.write_model(st_tmp, file_name=output_prfix+model_format)
468
+ else:
469
+ logger.writeln("All operators are already expanded (marked as given). Exiting.")
470
+ else:
471
+ logger.writeln("No NCS operators found. Exiting.")
472
+
473
+ if len(st.assemblies) > 0: # should we support BIOMT?
474
+ pass
475
+ # symexpand()
476
+
477
+ def h_add(args):
478
+ st = fileio.read_structure(args.model)
479
+ model_format = fileio.check_model_format(args.model)
480
+
481
+ if not args.output:
482
+ tmp = fileio.splitext(os.path.basename(args.model))[0]
483
+ args.output = tmp + "_h" + model_format
484
+ logger.writeln("Output file: {}".format(args.output))
485
+
486
+ args.ligand = sum(args.ligand, []) if args.ligand else []
487
+ monlib = restraints.load_monomer_library(st,
488
+ monomer_dir=args.monlib,
489
+ cif_files=args.ligand)
490
+ model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
491
+ try:
492
+ restraints.add_hydrogens(st, monlib, args.pos)
493
+ except RuntimeError as e:
494
+ raise SystemExit("Error: {}".format(e))
495
+
496
+ fileio.write_model(st, file_name=args.output)
497
+ # h_add()
498
+
499
+ def add_op3(args):
500
+ st = fileio.read_structure(args.model)
501
+ model_format = fileio.check_model_format(args.model)
502
+
503
+ if not args.output:
504
+ tmp = fileio.splitext(os.path.basename(args.model))[0]
505
+ args.output = tmp + "_op3" + model_format
506
+ logger.writeln("Output file: {}".format(args.output))
507
+
508
+ args.ligand = sum(args.ligand, []) if args.ligand else []
509
+ monlib = restraints.load_monomer_library(st,
510
+ monomer_dir=args.monlib,
511
+ cif_files=args.ligand)
512
+ model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
513
+
514
+ for chain in st[0]:
515
+ p = chain.get_polymer()
516
+ if not p: continue
517
+ p_type = p.check_polymer_type()
518
+ if p_type not in (gemmi.PolymerType.Dna, gemmi.PolymerType.Rna): continue
519
+ r0 = p[0]
520
+ # TODO: alias
521
+ # TODO: altlocs
522
+ alt = "*"
523
+ if r0.find_atom("OP3", alt): continue
524
+ a_op1 = r0.find_atom("OP1", alt)
525
+ a_op2 = r0.find_atom("OP2", alt)
526
+ a_o5p = r0.find_atom("O5'", alt)
527
+ a_p = r0.find_atom("P", alt)
528
+ if None in (a_op1, a_op2, a_o5p, a_p):
529
+ logger.writeln(f"Error: atoms not found. skipping {chain.name}/{r0}")
530
+ continue
531
+ logger.writeln(f"Adding OP3 to {chain.name}/{r0}")
532
+ a_op3 = r0.add_atom(a_p) # inherit ADP and occupancy
533
+ a_op3.name = "OP3"
534
+ a_op3.element = gemmi.Element("O")
535
+ v1 = a_p.pos - a_op1.pos
536
+ v2 = a_p.pos - a_op2.pos
537
+ v3 = a_p.pos - a_o5p.pos
538
+ v = v1 + v2 + v3
539
+ a_op3.pos = a_p.pos + v / v.length() * 1.517
540
+
541
+ fileio.write_model(st, file_name=args.output)
542
+ # add_op3()
543
+
544
+ def read_map_and_oversample(map_in=None, mtz_in=None, mtz_labs=None, oversample_pixel=None):
545
+ if mtz_in is not None:
546
+ mtz = fileio.read_mmhkl(mtz_in)
547
+ lab_f, lab_phi = mtz_labs.split(",")
548
+ asu = mtz.get_f_phi(lab_f, lab_phi)
549
+ if oversample_pixel is not None:
550
+ d_min = numpy.min(asu.make_d_array())
551
+ sample_rate = d_min / oversample_pixel
552
+ else:
553
+ sample_rate = 3
554
+ gr = asu.transform_f_phi_to_map(sample_rate=sample_rate)
555
+ elif map_in is not None:
556
+ gr = fileio.read_ccp4_map(map_in)[0]
557
+ if oversample_pixel is not None:
558
+ asu = gemmi.transform_map_to_f_phi(gr).prepare_asu_data()
559
+ d_min = numpy.min(asu.make_d_array())
560
+ sample_rate = d_min / oversample_pixel
561
+ gr = asu.transform_f_phi_to_map(sample_rate=sample_rate)
562
+ else:
563
+ raise SystemExit("Invalid input")
564
+
565
+ if oversample_pixel is not None:
566
+ logger.writeln("--oversample_pixel= {} is requested.".format(oversample_pixel))
567
+ logger.writeln(" recalculated grid:")
568
+ logger.writeln(" {:4d} {:4d} {:4d}".format(*gr.shape))
569
+ logger.writeln(" spacings:")
570
+ logger.writeln(" {:.6f} {:.6f} {:.6f}".format(*gr.spacing))
571
+ #maps.write_ccp4_map("{}_oversampled.mrc".format(output_prefix), gr)
572
+
573
+ return gr
574
+ # read_map_and_oversample()
575
+
576
+ def map_peaks(args):
577
+ st = fileio.read_structure(args.model)
578
+ gr = read_map_and_oversample(map_in=args.map, mtz_in=args.mtz, mtz_labs=args.mtz_labels,
579
+ oversample_pixel=args.oversample_pixel)
580
+ gr_sigma = numpy.std(gr)
581
+ if args.abs_level is not None:
582
+ cutoff = args.abs_level
583
+ else:
584
+ cutoff = args.sigma_level * gr_sigma # assuming mean(gr) = 0
585
+
586
+ blobs = gemmi.find_blobs_by_flood_fill(gr, cutoff,
587
+ min_volume=args.min_volume, min_score=0)
588
+ blobs.extend(gemmi.find_blobs_by_flood_fill(gr, cutoff, negate=True,
589
+ min_volume=args.min_volume, min_score=0))
590
+ getpos = dict(peak=lambda x: x.peak_pos,
591
+ centroid=lambda x: x.centroid)[args.blob_pos]
592
+ st_peaks = model.st_from_positions([getpos(b) for b in blobs])
593
+ st_peaks.cell = st.cell
594
+ st_peaks.ncs = st.ncs
595
+ st_peaks.setup_cell_images()
596
+ logger.writeln("{} peaks detected".format(len(blobs)))
597
+ #st_peaks.write_pdb("peaks.pdb")
598
+
599
+ # Filter symmetry related
600
+ ns = gemmi.NeighborSearch(st_peaks[0], st_peaks.cell, 5.).populate()
601
+ cs = gemmi.ContactSearch(1.)
602
+ cs.ignore = gemmi.ContactSearch.Ignore.SameAsu
603
+ results = cs.find_contacts(ns)
604
+ del_idxes = set()
605
+ for r in results:
606
+ if r.partner1.residue.seqid.num not in del_idxes:
607
+ del_idxes.add(r.partner2.residue.seqid.num)
608
+ for i in reversed(sorted(del_idxes)):
609
+ del st_peaks[0][0][i]
610
+ del blobs[i]
611
+ #st_peaks.write_pdb("peaks_asu.pdb")
612
+ logger.writeln("{} peaks after removing symmetry equivalents".format(len(blobs)))
613
+
614
+ # Assign to nearest atom
615
+ ns = gemmi.NeighborSearch(st[0], st.cell, 10.).populate() # blob is rejected if > 10 A. ok?
616
+ peaks = []
617
+ for b in blobs:
618
+ bpos = getpos(b)
619
+ map_val = gr.interpolate_value(bpos)
620
+ if (args.max_volume is not None and b.volume > args.max_volume) or abs(map_val) < cutoff: continue
621
+ x = ns.find_nearest_atom(bpos)
622
+ if x is None: # this should not happen
623
+ logger.writeln("no nearest atom: value={:.2e} volume= {:.2f} pos= {}".format(map_val, b.volume, bpos))
624
+ continue
625
+ chain = st[0][x.chain_idx]
626
+ res = chain[x.residue_idx]
627
+ atom = res[x.atom_idx]
628
+ im = st.cell.find_nearest_image(atom.pos, bpos, gemmi.Asu.Any)
629
+ mpos = st.cell.find_nearest_pbc_position(atom.pos, bpos, im.sym_idx)
630
+ dist = atom.pos.dist(mpos)
631
+ peaks.append((map_val, b.volume, mpos, dist, chain, res, atom))
632
+
633
+ if len(peaks) == 0:
634
+ logger.writeln("No peaks found. Change parameter(s).")
635
+ return
636
+
637
+ # Print and write coot script
638
+ peaks.sort(reverse=True, key=lambda x:(abs(x[0]), x[1]))
639
+ for_coot = []
640
+ for_df = []
641
+ for i, p in enumerate(peaks):
642
+ map_val, volume, mpos, dist, chain, res, atom = p
643
+ mpos_str = "({: 7.2f},{: 7.2f},{: 7.2f})".format(mpos.x, mpos.y, mpos.z)
644
+ atom_name = atom.name + ("." + atom.altloc if atom.altloc != "\0" else "")
645
+ atom_str = "{}/{}/{}".format(chain.name, res.seqid, atom_name)
646
+ if args.abs_level is None:
647
+ map_val /= gr_sigma
648
+ lab_str = "Peak {:4d} value= {: .2e} volume= {:5.1f} pos= {} closest= {:10s} dist= {:.2f}".format(i+1, map_val, volume, mpos_str, atom_str, dist)
649
+ for_coot.append((lab_str, (mpos.x, mpos.y, mpos.z)))
650
+ for_df.append((map_val, volume, mpos.x, mpos.y, mpos.z, chain.name, str(res.seqid), atom_name, dist))
651
+ df = pandas.DataFrame(for_df, columns=["map_value" if args.abs_level is not None else "sigma_level",
652
+ "volume", "x", "y", "z", "chain", "residue", "atom", "dist"])
653
+ logger.writeln(df.to_string())
654
+ with open(args.output_prefix + ".json", "w") as ofs:
655
+ df.to_json(ofs, orient="records", indent=2)
656
+ logger.writeln("saved: {}".format(ofs.name))
657
+ coot_out = args.output_prefix + "_coot.py"
658
+ with open(coot_out, "w") as ofs:
659
+ ofs.write("""\
660
+ from __future__ import absolute_import, division, print_function
661
+ import gtk
662
+ class coot_serval_map_peak_list:
663
+ def __init__(self):
664
+ window = gtk.Window(gtk.WINDOW_TOPLEVEL)
665
+ window.set_title("Map peaks (Servalcat)")
666
+ window.set_default_size(600, 600)
667
+ scrolled_win = gtk.ScrolledWindow()
668
+ scrolled_win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
669
+ vbox = gtk.VBox(False, 2)
670
+ frame_vbox = gtk.VBox(False, 0)
671
+ frame_vbox.set_border_width(3)
672
+ self.btns = []
673
+ self.data = {}
674
+ self.add_data(frame_vbox)
675
+ scrolled_win.add_with_viewport(frame_vbox)
676
+ vbox.pack_start(scrolled_win, True, True, 0)
677
+ window.add(vbox)
678
+ window.show_all()
679
+ self.toggled(self.btns[0], 0)
680
+
681
+ def toggled(self, btn, i):
682
+ if btn.get_active():
683
+ set_rotation_centre(*self.data[i][1])
684
+ add_status_bar_text(self.data[i][0])
685
+
686
+ def add_data(self, vbox):
687
+ for i, d in enumerate(self.data):
688
+ self.btns.append(gtk.RadioButton(None if i == 0 else self.btns[0], d[0]))
689
+ vbox.pack_start(self.btns[-1], False, False, 0)
690
+ self.btns[-1].connect('toggled', self.toggled, i)
691
+
692
+ gui = coot_serval_map_peak_list()
693
+ """.format(for_coot))
694
+ logger.writeln("\nRun:")
695
+ logger.writeln("coot --script {}".format(coot_out))
696
+ # map_peaks()
697
+
698
+ def h_density_analysis(args):
699
+ #if args.source != "electron":
700
+ # raise SystemExit("Only electron source is supported.")
701
+ model_format = fileio.check_model_format(args.model)
702
+ st = fileio.read_structure(args.model)
703
+ if not st[0].has_hydrogen():
704
+ raise SystemExit("No hydrogen in model.")
705
+
706
+ if args.output_prefix is None:
707
+ args.output_prefix = fileio.splitext(os.path.basename(args.model))[0] + "_hana"
708
+
709
+ gr = read_map_and_oversample(map_in=args.map, mtz_in=args.mtz, mtz_labs=args.mtz_labels,
710
+ oversample_pixel=args.oversample_pixel)
711
+
712
+ if args.abs_level is not None:
713
+ cutoff = args.abs_level
714
+ else:
715
+ cutoff = args.sigma_level * numpy.std(gr) # assuming mean(gr) = 0
716
+
717
+ blobs = gemmi.find_blobs_by_flood_fill(gr, cutoff,
718
+ min_volume=args.min_volume, min_score=0)
719
+ getpos = dict(peak=lambda x: x.peak_pos,
720
+ centroid=lambda x: x.centroid)[args.blob_pos]
721
+
722
+ peaks = [getpos(b).tolist() for b in blobs]
723
+ kdtree = scipy.spatial.cKDTree(peaks)
724
+ found = []
725
+ n_hydr = 0
726
+ h_assigned = [0 for _ in range(len(blobs))]
727
+ st2 = st.clone()
728
+ for ic, chain in enumerate(st[0]):
729
+ for ir, res in enumerate(chain):
730
+ for ia, atom in reversed(list(enumerate(res))):
731
+ if not atom.is_hydrogen(): continue
732
+ n_hydr += 1
733
+ dist, idx = kdtree.query(atom.pos.tolist(), k=1, p=2)
734
+ map_val = gr.interpolate_value(getpos(blobs[idx]))
735
+ if dist < args.max_dist and blobs[idx].volume < args.max_volume and map_val > cutoff:
736
+ found.append((getpos(blobs[idx]), map_val, dist, blobs[idx].volume,
737
+ chain.name, str(res.seqid), res.name,
738
+ atom.name, atom.altloc.replace("\0","")))
739
+ h_assigned[idx] = 1
740
+ else:
741
+ del st2[0][ic][ir][ia]
742
+
743
+ found.sort(key=lambda x: x[1], reverse=True)
744
+ logger.writeln("")
745
+ logger.writeln("Found hydrogen peaks:")
746
+ logger.writeln("dist map vol atom")
747
+ for _, map_val, dist, volume, chain, resi, resn, atom, alt in found:
748
+ logger.writeln("{:.2f} {:.2f} {:.2f} {}/{} {}/{}{}".format(dist, map_val, volume,
749
+ chain, resn, resi,
750
+ atom, "."+alt if alt else ""))
751
+
752
+ logger.writeln("")
753
+ logger.writeln("Result:")
754
+ logger.writeln(" number of hydrogen in the model : {}".format(n_hydr))
755
+ logger.writeln(" number of peaks close to hydrogen: {} ({:.1%})".format(len(found), len(found)/n_hydr))
756
+ logger.writeln("")
757
+
758
+ st_peaks = model.st_from_positions([getpos(b) for b in blobs],
759
+ bs=[gr.interpolate_value(getpos(b)) for b in blobs],
760
+ qs=h_assigned)
761
+ fileio.write_model(st_peaks, file_name="{}_peaks.mmcif".format(args.output_prefix))
762
+ logger.writeln(" this file includes peak positions")
763
+ logger.writeln(" occ=1: hydrogen assigned, occ=0: unassigned.")
764
+ logger.writeln(" B: density value at {}".format(args.blob_pos))
765
+ logger.writeln("")
766
+
767
+ fileio.write_model(st2, file_name="{}_h_with_peak{}".format(args.output_prefix, model_format))
768
+ logger.writeln(" this file is a copy of input model, where hydrogen atoms without peaks are removed.")
769
+ # h_density_analysis()
770
+
771
+ def fix_link(args):
772
+ st = fileio.read_structure(args.model)
773
+ model_format = fileio.check_model_format(args.model)
774
+
775
+ if not args.output:
776
+ tmp = fileio.splitext(os.path.basename(args.model))[0]
777
+ args.output = tmp + "_fixlink" + model_format
778
+ logger.writeln("Output file: {}".format(args.output))
779
+
780
+ args.ligand = sum(args.ligand, []) if args.ligand else []
781
+ monlib = restraints.load_monomer_library(st,
782
+ monomer_dir=args.monlib,
783
+ cif_files=args.ligand)
784
+ model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
785
+ restraints.find_and_fix_links(st, monlib, bond_margin=args.bond_margin,
786
+ metal_margin=args.metal_margin)
787
+ fileio.write_model(st, file_name=args.output)
788
+ # fix_link()
789
+
790
+ def merge_models(args):
791
+ logger.writeln("Reading file 1: {}".format(args.models[0]))
792
+ st = fileio.read_structure(args.models[0])
793
+ logger.writeln(" chains {}".format(" ".join([c.name for c in st[0]])))
794
+
795
+ for i, f in enumerate(args.models[1:]):
796
+ logger.writeln("Reading file {:3d}: {}".format(i+2, f))
797
+ st2 = fileio.read_structure(f)
798
+ for c in st2[0]:
799
+ org_id = c.name
800
+ c2 = st[0].add_chain(c, unique_name=True)
801
+ if c.name != c2.name:
802
+ logger.writeln(" chain {} merged (ID changed to {})".format(c.name, c2.name))
803
+ else:
804
+ logger.writeln(" chain {} merged".format(c.name))
805
+
806
+ fileio.write_model(st, file_name=args.output)
807
+ # merge_models()
808
+
809
+ def merge_dicts(args):
810
+ fileio.merge_ligand_cif(args.cifs, args.output)
811
+ # merge_dicts()
812
+
813
+ def geometry(args):
814
+ if args.ligand: args.ligand = sum(args.ligand, [])
815
+ if not args.output_prefix: args.output_prefix = fileio.splitext(os.path.basename(args.model))[0] + "_geom"
816
+ keywords = []
817
+ if args.keywords or args.keyword_file:
818
+ if args.keywords: keywords = sum(args.keywords, [])
819
+ if args.keyword_file: keywords.extend(l for f in sum(args.keyword_file, []) for l in open(f))
820
+ params = refmac_keywords.parse_keywords(keywords)
821
+ st = fileio.read_structure(args.model)
822
+ if args.ignore_h:
823
+ st.remove_hydrogens()
824
+ try:
825
+ monlib = restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
826
+ stop_for_unknowns=True, params=params)
827
+ except RuntimeError as e:
828
+ raise SystemExit("Error: {}".format(e))
829
+
830
+ model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
831
+ restraints.find_and_fix_links(st, monlib)
832
+ try:
833
+ topo, _ = restraints.prepare_topology(st, monlib, h_change=gemmi.HydrogenChange.NoChange,
834
+ check_hydrogen=True, params=params)
835
+ except RuntimeError as e:
836
+ raise SystemExit("Error: {}".format(e))
837
+
838
+ if args.selection:
839
+ sel = gemmi.Selection(args.selection)
840
+ atom_pos = [-1 for _ in range(st[0].count_atom_sites())]
841
+ n = 0
842
+ for chain in sel.chains(st[0]):
843
+ for res in sel.residues(chain):
844
+ for atom in sel.atoms(res):
845
+ atom_pos[atom.serial-1] = n
846
+ n += 1
847
+ logger.writeln("Using selection '{}': {} atoms out of {}".format(args.selection, n, len(atom_pos)))
848
+ else:
849
+ atom_pos = None
850
+
851
+ geom = Geom(st, topo, monlib, params=params, atom_pos=atom_pos, use_nucleus=args.nucleus)
852
+ for k in geom.outlier_sigmas: geom.outlier_sigmas[k] = args.sigma
853
+ geom.setup_nonbonded(True)
854
+ ret = geom.show_model_stats()
855
+
856
+ with open(args.output_prefix + "_summary.json", "w") as ofs:
857
+ ret["summary"].to_json(ofs, indent=2)
858
+ logger.writeln("saved: {}".format(ofs.name))
859
+ with open(args.output_prefix + "_outliers.json", "w") as ofs:
860
+ for k in ret["outliers"]:
861
+ ret["outliers"][k] = ret["outliers"][k].to_dict(orient="records")
862
+ json.dump(ret["outliers"], ofs, indent=2)
863
+ logger.writeln("saved: {}".format(ofs.name))
864
+
865
+ if args.check_skew:
866
+ logger.writeln("\nChecking skewness of bond length deviation")
867
+ # better to ignore hydrogen
868
+ tab = geom.geom.reporting.get_bond_outliers(use_nucleus=geom.use_nucleus, min_z=0)
869
+ for a in "atom1", "atom2":
870
+ tab[a] = [str(geom.lookup[x]) for x in tab[a]]
871
+ df = pandas.DataFrame(tab)
872
+ df["dev"] = df["value"] - df["ideal"]
873
+ df = df.reindex(df.dev.abs().sort_values(ascending=False).index)
874
+ logger.writeln("Bond length deviations:")
875
+ logger.writeln(df.to_string(max_rows=20))
876
+ q1, q2, q3 = numpy.percentile(df["dev"], [25, 50, 75])
877
+ sk2 = (q1 + q3 - 2 * q2) / (q3 - q1)
878
+ logger.writeln("bond_dev_median= {:.6f}".format(q2))
879
+ logger.writeln("bond_dev_skew= {:.4f}".format(df["dev"].skew()))
880
+ logger.writeln("bond_dev_sk2= {:.4f}".format(sk2))
881
+ with open(args.output_prefix + "_bond_dev.html", "w") as ofs:
882
+ ofs.write("""\
883
+ <html>
884
+ <head>
885
+ <meta charset="utf-8" />
886
+ <script src="https://cdn.plot.ly/plotly-2.20.0.min.js" charset="utf-8"></script>
887
+ </head>
888
+ <body>
889
+ <div id="hist"></div>
890
+ <script>
891
+ var trace = {
892
+ x: %s,
893
+ type: 'histogram'
894
+ };
895
+ var layout = {
896
+ title: "median: %.4f, sk2: %.4f",
897
+ xaxis: {title: "bond distance - ideal"},
898
+ yaxis: {title: "count"},
899
+ shapes: [{
900
+ type: 'line',
901
+ yref: 'paper',
902
+ x0: 0, y0: 0,
903
+ x1: 0, y1: 1}]
904
+ };
905
+ target = document.getElementById('hist');
906
+ Plotly.newPlot(target, [trace], layout);
907
+ </script>
908
+ </body>
909
+ </html>
910
+ """ % (str(list(df.dev)), q2, sk2))
911
+ logger.writeln("check histogram: {}".format(ofs.name))
912
+
913
+ # Note that this modifies st
914
+ if args.per_atom_score_as_b:
915
+ model_format = fileio.check_model_format(args.model)
916
+ peratom = geom.geom.reporting.per_atom_score(len(geom.atoms), geom.use_nucleus, "mean")
917
+ for i, score in enumerate(peratom["total"]):
918
+ geom.atoms[i].b_iso = score
919
+ fileio.write_model(st, file_name="{}_per_atom_score{}".format(args.output_prefix, model_format))
920
+ # geometry()
921
+
922
+ def adp_stats(args):
923
+ if not args.output_prefix: args.output_prefix = fileio.splitext(os.path.basename(args.model))[0] + "_adp"
924
+ st = fileio.read_structure(args.model)
925
+ model.adp_analysis(st)
926
+ b_all = [cra.atom.b_iso for cra in st[0].all() if cra.atom.occ > 0]
927
+
928
+ # bin width from Freedman–Diaconis rule
929
+ qs = numpy.quantile(b_all, [0, 0.25, 0.75, 1])
930
+ bin_h = 2 * (qs[2] - qs[1]) / len(b_all)**(1/3.)
931
+
932
+ # for plotly
933
+ traces = []
934
+ traces.append("x: [%s], type: 'histogram', name: 'All', xbins: {size: %f}"
935
+ % (",".join("%.2f"%x for x in b_all), bin_h))
936
+ if len(st[0]) > 1:
937
+ b_chain = {}
938
+ for c in st[0]:
939
+ b_chain.setdefault(c.name, []).extend(a.b_iso for r in c for a in r if a.occ > 0)
940
+ for c in b_chain:
941
+ bs = ",".join("%.2f" % x for x in b_chain[c])
942
+ traces.append("x: [%s], type: 'histogram', name: 'Chain %s'" % (bs, c))
943
+ with open(args.output_prefix + "_hist.html", "w") as ofs:
944
+ ofs.write("""\
945
+ <html>
946
+ <head>
947
+ <meta charset="utf-8" />
948
+ <script src="https://cdn.plot.ly/plotly-2.20.0.min.js" charset="utf-8"></script>
949
+ </head>
950
+ <body>
951
+ <div id="hist"></div>
952
+ <script>
953
+ """)
954
+ for i, t in enumerate(traces):
955
+ ofs.write("var trace%d = {%s};\n" % (i+1, t))
956
+ ofs.write("""\
957
+ var layout = {
958
+ title: "isotropic B histogram",
959
+ xaxis: {title: "B"},
960
+ yaxis: {title: "count"},
961
+ barmode: "stack"
962
+ };
963
+ target = document.getElementById('hist');
964
+ Plotly.newPlot(target, [%s], layout);
965
+ </script>
966
+ </body>
967
+ </html>
968
+ """ % (",".join("trace%d" % (i+1) for i in range(len(traces)))))
969
+ logger.writeln("check histogram: {}".format(ofs.name))
970
+ # adp_stats()
971
+
972
+ def show_power(args):
973
+ maps_in = []
974
+ if args.map:
975
+ print(args.map)
976
+ print(sum(args.map, []))
977
+ maps_in = [(f,) for f in sum(args.map, [])]
978
+
979
+ if args.halfmaps:
980
+ args.halfmaps = sum(args.halfmaps, [])
981
+ if len(args.halfmaps)%2 != 0:
982
+ raise RuntimeError("Number of half maps is not even.")
983
+ maps_in.extend([(args.halfmaps[2*i],args.halfmaps[2*i+1]) for i in range(len(args.halfmaps)//2)])
984
+
985
+ if args.mask:
986
+ mask = fileio.read_ccp4_map(args.mask)[0]
987
+ else:
988
+ mask = None
989
+
990
+ hkldata = None
991
+ labs = []
992
+ for mapin in maps_in: # TODO rewrite in faster way
993
+ ms = [fileio.read_ccp4_map(f) for f in mapin]
994
+ d_min = args.resolution
995
+ if d_min is None:
996
+ d_min = maps.nyquist_resolution(ms[0][0])
997
+ logger.writeln("WARNING: --resolution is not specified. Using Nyquist resolution: {:.2f}".format(d_min))
998
+ tmp = maps.mask_and_fft_maps(ms, d_min, mask)
999
+ labs.append("F{:02d}".format(len(labs)+1))
1000
+ tmp.df.rename(columns=dict(FP=labs[-1]), inplace=True)
1001
+ if hkldata is None:
1002
+ hkldata = tmp
1003
+ else:
1004
+ if hkldata.cell.parameters != tmp.cell.parameters: raise RuntimeError("Different unit cell!")
1005
+ hkldata.merge(tmp.df[["H","K","L",labs[-1]]])
1006
+
1007
+ if not labs:
1008
+ raise SystemExit("No map files given. Exiting.")
1009
+
1010
+ hkldata.setup_relion_binning()
1011
+
1012
+ ofs = open(args.output_prefix+".log", "w")
1013
+ ofs.write("Input:\n")
1014
+ for i in range(len(maps_in)):
1015
+ ofs.write("{} from {}\n".format(labs[i], " ".join(maps_in[i])))
1016
+ ofs.write("\n")
1017
+
1018
+ ofs.write("""$TABLE: Power spectrum :
1019
+ $GRAPHS
1020
+ : log10(Mn(|F|^2)) :A:1,{}:
1021
+ $$
1022
+ 1/resol^2 n d_max d_min {}
1023
+ $$
1024
+ $$
1025
+ """.format(",".join([str(i+5) for i in range(len(labs))]), " ".join(labs)))
1026
+ print(hkldata.df)
1027
+ abssqr = dict((lab, numpy.abs(hkldata.df[lab].to_numpy())**2) for lab in labs)
1028
+ for i_bin, idxes in hkldata.binned():
1029
+ bin_d_min = hkldata.binned_df.d_min[i_bin]
1030
+ bin_d_max = hkldata.binned_df.d_max[i_bin]
1031
+ ofs.write("{:.4f} {:7d} {:7.3f} {:7.3f}".format(1/bin_d_min**2, len(idxes), bin_d_max, bin_d_min,))
1032
+ for lab in labs:
1033
+ pwr = numpy.log10(numpy.average(abssqr[lab][idxes]))
1034
+ ofs.write(" {:.4e}".format(pwr))
1035
+ ofs.write("\n")
1036
+ ofs.write("$$\n")
1037
+ ofs.close()
1038
+ # show_power()
1039
+
1040
+ def fcalc(args):
1041
+ if (args.auto_box_with_padding, args.cell).count(None) == 0:
1042
+ raise SystemExit("Error: you cannot specify both --auto_box_with_padding and --cell")
1043
+
1044
+ if args.ligand: args.ligand = sum(args.ligand, [])
1045
+ if not args.output_prefix: args.output_prefix = "{}_fcalc_{}".format(fileio.splitext(os.path.basename(args.model))[0], args.source)
1046
+
1047
+ st = fileio.read_structure(args.model)
1048
+ if not args.keep_charges:
1049
+ model.remove_charge([st])
1050
+ model.check_atomsf([st], args.source)
1051
+ if not args.no_expand_ncs:
1052
+ model.expand_ncs(st)
1053
+
1054
+ if args.cell is not None:
1055
+ st.cell = gemmi.UnitCell(*args.cell)
1056
+ elif args.auto_box_with_padding is not None:
1057
+ st.cell = model.box_from_model(st[0], args.auto_box_with_padding)
1058
+ st.spacegroup_hm = "P 1"
1059
+ logger.writeln("Box size from the model with padding of {}: {}".format(args.auto_box_with_padding, st.cell.parameters))
1060
+
1061
+ if not st.cell.is_crystal():
1062
+ raise SystemExit("ERROR: No unit cell information. Give --cell or --auto_box_with_padding.")
1063
+
1064
+ if args.source=="electron" and st[0].has_hydrogen():
1065
+ monlib = restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
1066
+ stop_for_unknowns=False)
1067
+ else:
1068
+ monlib = None
1069
+
1070
+ if args.method == "fft":
1071
+ fc_asu = model.calc_fc_fft(st, args.resolution, cutoff=args.cutoff, rate=args.rate,
1072
+ mott_bethe=args.source=="electron",
1073
+ monlib=monlib, source=args.source)
1074
+ else:
1075
+ fc_asu = model.calc_fc_direct(st, args.resolution, source=args.source,
1076
+ mott_bethe=args.source=="electron", monlib=monlib)
1077
+
1078
+ hkldata = hkl.hkldata_from_asu_data(fc_asu, "FC")
1079
+ if args.as_intensity:
1080
+ hkldata.df["IC"] = numpy.abs(hkldata.df.FC)**2
1081
+ labout = ["IC"]
1082
+ if args.add_dummy_sigma:
1083
+ hkldata.df["SIGIC"] = 1.
1084
+ labout.append("SIGIC")
1085
+ else:
1086
+ labout = ["FC"]
1087
+ if args.add_dummy_sigma:
1088
+ hkldata.df["SIGFC"] = 1.
1089
+ labout.append("SIGFC")
1090
+
1091
+ hkldata.write_mtz(args.output_prefix+".mtz", labout, types=dict(IC="J", SIGIC="Q", SIGFC="Q"))
1092
+ # fcalc()
1093
+
1094
+ def nemap(args):
1095
+ from servalcat.spa import fofc
1096
+
1097
+ if (args.trim or args.trim_mtz) and args.mask is None:
1098
+ raise SystemExit("\nError: You need to give --mask as you requested --trim or --trim_mtz.\n")
1099
+
1100
+ if args.mask:
1101
+ mask = fileio.read_ccp4_map(args.mask)[0]
1102
+ else:
1103
+ mask = None
1104
+
1105
+ halfmaps = fileio.read_halfmaps(args.halfmaps, pixel_size=args.pixel_size)
1106
+ if args.resolution is None:
1107
+ args.resolution = maps.nyquist_resolution(halfmaps[0][0])
1108
+ logger.writeln("WARNING: --resolution is not specified. Using Nyquist resolution: {:.2f}".format(args.resolution))
1109
+
1110
+ d_min = args.resolution
1111
+ if args.local_fourier_weighting_with > 0:
1112
+ d_min = 1 / (args.local_fourier_weighting_with + 1 / d_min)
1113
+ logger.writeln("adjusting d_min= {:.2f} for local correlation".format(d_min))
1114
+ hkldata = maps.mask_and_fft_maps(halfmaps, d_min, mask)
1115
+
1116
+ if args.local_fourier_weighting_with > 0:
1117
+ asu1 = hkldata.as_asu_data("F_map1")
1118
+ asu2 = hkldata.as_asu_data("F_map2")
1119
+ size = asu1.get_size_for_hkl(sample_rate=3)
1120
+ logger.writeln("using grid {}".format(size))
1121
+ gr1 = asu1.get_f_phi_on_grid(size)
1122
+ gr2 = asu2.get_f_phi_on_grid(size)
1123
+ kernel = ext.hard_sphere_kernel_recgrid(size, asu1.unit_cell, args.local_fourier_weighting_with)
1124
+ cc = maps.local_cc(gr1, gr2, kernel.array.real, method="simple")
1125
+ cc.array[cc.array < 0] = 0 # negative cc cannot be used anyway
1126
+ cc.array[:] = 2 * cc.array.real / (1 + cc.array.real) # to full map cc
1127
+ hkldata.df["cc"] = numpy.real(cc.get_value_by_hkl(hkldata.miller_array()))
1128
+ grf = type(gr1)((gr1.array + gr2.array) / 2, gr1.unit_cell, gr1.spacegroup)
1129
+ var_f = maps.local_var(grf, kernel.array.real, method="simple")
1130
+ hkldata.df["var_f"] = numpy.real(var_f.get_value_by_hkl(hkldata.miller_array()))
1131
+ if args.B is not None:
1132
+ k2_l = numpy.exp(-args.B / hkldata.d_spacings()**2 / 2)
1133
+ hkldata.df.cc = k2_l * hkldata.df.cc / (1 + (k2_l - 1) * hkldata.df.cc)
1134
+ hkldata.df["FWT"] = hkldata.df.FP * numpy.sqrt(hkldata.df.cc / hkldata.df.var_f)
1135
+ hkldata.df["kernel"] = numpy.real(kernel.get_value_by_hkl(hkldata.miller_array()))
1136
+ hkldata.write_mtz(args.output_prefix+"_cc.mtz", ["cc", "kernel"])
1137
+ hkldata = hkldata.copy(d_min=args.resolution)
1138
+ map_labs = ["FWT"]
1139
+ else:
1140
+ hkldata.setup_relion_binning()
1141
+ maps.calc_noise_var_from_halfmaps(hkldata)
1142
+ map_labs = fofc.calc_maps(hkldata, B=args.B, has_halfmaps=True, half1_only=args.half1_only,
1143
+ no_fsc_weights=args.no_fsc_weights, sharpening_b=args.sharpening_b)
1144
+ fofc.write_files(hkldata, map_labs, grid_start=halfmaps[0][1], stats_str=None,
1145
+ mask=mask, output_prefix=args.output_prefix,
1146
+ trim_map=args.trim, trim_mtz=args.trim_mtz)
1147
+ # nemap()
1148
+
1149
+ def blur(args):
1150
+ if args.output_prefix is None:
1151
+ args.output_prefix = fileio.splitext(os.path.basename(args.hklin))[0]
1152
+
1153
+ if fileio.is_mmhkl_file(args.hklin):
1154
+ mtz = fileio.read_mmhkl(args.hklin)
1155
+ hkl.blur_mtz(mtz, args.B)
1156
+ suffix = ("_blur" if args.B > 0 else "_sharpen") + "_{:.2f}.mtz".format(abs(args.B))
1157
+ mtz.write_to_file(args.output_prefix+suffix)
1158
+ logger.writeln("Written: {}".format(args.output_prefix+suffix))
1159
+ else:
1160
+ raise SystemExit("ERROR: Unsupported file type: {}".format(args.hklin))
1161
+ # blur()
1162
+
1163
+ def mask_from_model(args):
1164
+ st = fileio.read_structure(args.model) # TODO option to (or not to) expand NCS
1165
+ if args.selection:
1166
+ gemmi.Selection(args.selection).remove_not_selected(st)
1167
+ gr, grid_start, _ = fileio.read_ccp4_map(args.map)
1168
+ mask = maps.mask_from_model(st, args.radius, soft_edge=args.soft_edge, grid=gr)
1169
+ maps.write_ccp4_map(args.output, mask, grid_start=grid_start)
1170
+ # mask_from_model()
1171
+
1172
+ def applymask(args):
1173
+ if args.output_prefix is None:
1174
+ args.output_prefix = fileio.splitext(os.path.basename(args.map))[0] + "_masked"
1175
+
1176
+ grid, grid_start, _ = fileio.read_ccp4_map(args.map)
1177
+ mask = fileio.read_ccp4_map(args.mask)[0]
1178
+ logger.writeln("Applying mask")
1179
+ logger.writeln(" mask min: {:.3f} max: {:.3f}".format(numpy.min(mask), numpy.max(mask)))
1180
+ grid.array[:] *= mask.array
1181
+
1182
+ if args.normalize:
1183
+ masked = grid.array[mask.array>args.mask_cutoff]
1184
+ masked_mean = numpy.average(masked)
1185
+ masked_std = numpy.std(masked)
1186
+ logger.writeln("Normalizing map values within mask")
1187
+ logger.writeln(" masked volume: {} mean: {:.3e} sd: {:.3e}".format(len(masked), masked_mean, masked_std))
1188
+ grid.array[:] = (grid.array - masked_mean) / masked_std
1189
+
1190
+ maps.write_ccp4_map(args.output_prefix+".mrc", grid,
1191
+ grid_start=grid_start,
1192
+ mask_for_extent=mask.array if args.trim else None,
1193
+ mask_threshold=args.mask_cutoff)
1194
+ # applymask()
1195
+
1196
+ def map2mtz(args):
1197
+ if args.output is None:
1198
+ args.output = fileio.splitext(os.path.basename(args.map))[0] + "_fft.mtz"
1199
+ grid, grid_start, grid_shape = fileio.read_ccp4_map(args.map)
1200
+ if args.resolution is None:
1201
+ args.resolution = maps.nyquist_resolution(grid)
1202
+ logger.writeln("WARNING: --resolution is not specified. Using Nyquist resolution: {:.2f}".format(args.resolution))
1203
+
1204
+ if grid_start != (0,0,0) or grid.shape != tuple(grid_shape):
1205
+ # If only subregion of whole grid in map, unit cell needs to be re-defined.
1206
+ if grid.shape != tuple(grid_shape):
1207
+ new_abc = [grid.unit_cell.parameters[i] * grid_shape[i] / grid.shape[i] for i in range(3)]
1208
+ cell = gemmi.UnitCell(*new_abc, *grid.unit_cell.parameters[3:])
1209
+ logger.writeln("Changing unit cell to {}".format(cell.parameters))
1210
+ else:
1211
+ cell = grid.unit_cell
1212
+ grid = gemmi.FloatGrid(grid.get_subarray(grid_start, grid_shape),
1213
+ cell, grid.spacegroup)
1214
+
1215
+ f_grid = gemmi.transform_map_to_f_phi(grid)
1216
+ asudata = f_grid.prepare_asu_data(dmin=args.resolution, with_000=True)
1217
+ hkldata = hkl.hkldata_from_asu_data(asudata, "F")
1218
+ if grid_start != (0,0,0):
1219
+ shifts = grid.get_position(*grid_start)
1220
+ hkldata.translate("F", shifts)
1221
+ logger.writeln("Applying phase shift with translation {}".format(shifts.tolist()))
1222
+ hkldata.write_mtz(args.output, ["F"])
1223
+ # map2mtz()
1224
+
1225
+ def sm2mm(args):
1226
+ if args.output_prefix is None:
1227
+ args.output_prefix = fileio.splitext(args.files[0])[0]
1228
+ st, mtz = fileio.read_small_molecule_files(args.files)
1229
+ if st is not None:
1230
+ fileio.write_model(st, prefix=args.output_prefix, pdb=True, cif=True)
1231
+ if mtz is not None:
1232
+ mtz_out = args.output_prefix + ".mtz"
1233
+ logger.writeln("Writing MTZ file: {}".format(mtz_out))
1234
+ mtz.write_to_file(mtz_out)
1235
+ # sm2mm()
1236
+
1237
+ def seq(args):
1238
+ wrap_width = 100
1239
+ seqs = []
1240
+ if args.seq:
1241
+ args.seq = sum(args.seq, [])
1242
+ for sf in args.seq:
1243
+ seqs.extend(fileio.read_sequence_file(sf))
1244
+
1245
+ st = fileio.read_structure(args.model) # TODO option to (or not to) expand NCS
1246
+ model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
1247
+ for chain in st[0]:
1248
+ p = chain.get_polymer()
1249
+ if not p: continue
1250
+ p_type = p.check_polymer_type()
1251
+ if p_type in (gemmi.PolymerType.SaccharideD, gemmi.PolymerType.SaccharideL): continue
1252
+ p_seq = gemmi.one_letter_code(p.extract_sequence())
1253
+ results = []
1254
+ for name, seq in seqs:
1255
+ # what if DnaRnaHybrid?
1256
+ kind = {gemmi.PolymerType.Dna: gemmi.ResidueKind.DNA,
1257
+ gemmi.PolymerType.Rna: gemmi.ResidueKind.RNA}.get(p_type, gemmi.ResidueKind.AA)
1258
+ s = [gemmi.expand_one_letter(x, kind) for x in seq]
1259
+ if None in s: continue
1260
+ results.append([name, gemmi.align_sequence_to_polymer(s, p, p_type), seq])
1261
+
1262
+ if results:
1263
+ logger.writeln("Chain: {}".format(chain.name))
1264
+ logger.writeln(" polymer type: {}".format(str(p_type).replace("PolymerType.", "")))
1265
+ name, al, s1 = max(results, key=lambda x: x[1].score)
1266
+ logger.writeln(" match: {}".format(name))
1267
+ logger.writeln(" score: {}".format(al.score))
1268
+ p1, p2 = al.add_gaps(s1, 1), al.add_gaps(p_seq, 2)
1269
+ unkseq = [x.start() for x in re.finditer("\-", p1)]
1270
+ mismatches = [x.start() for x in re.finditer("\.", al.match_string)]
1271
+ if mismatches or unkseq:
1272
+ idxes = {x.start(): i for i, x in enumerate(re.finditer("[^-]", p2))}
1273
+ seqnums = [str(x.seqid) for x in p]
1274
+ if mismatches:
1275
+ logger.write(" mismatches: ")
1276
+ logger.writeln(", ".join("{}({}>{})".format(seqnums[idxes[i]], p1[i], p2[i]) for i in mismatches))
1277
+ if unkseq:
1278
+ logger.write(" unknown sequence: ")
1279
+ logger.writeln(", ".join("{}({})".format(seqnums[idxes[i]], p2[i]) for i in unkseq))
1280
+
1281
+ logger.writeln("")
1282
+ for i in range(0, len(p1), wrap_width):
1283
+ logger.writeln(" seq. {}".format(p1[i:i+wrap_width]))
1284
+ logger.writeln(" {}".format(al.match_string[i:i+wrap_width]))
1285
+ logger.writeln(" model {}\n".format(p2[i:i+wrap_width]))
1286
+ else:
1287
+ logger.writeln("> Chain: {}".format(chain.name))
1288
+ logger.writeln(gemmi.one_letter_code(p.extract_sequence()))
1289
+ logger.writeln("")
1290
+ # seq()
1291
+
1292
+ def dnarna(args):
1293
+ import scipy.spatial.transform
1294
+ rna_res = {"A":"DA", "G":"DG", "C":"DC", "U":"DT"}
1295
+ dna_res = {"DA":"A", "DG":"G", "DC":"C", "DT":"U"}
1296
+ if args.chains: args.chains = sum(args.chains, [])
1297
+ model_format = fileio.check_model_format(args.model)
1298
+ if not args.output:
1299
+ args.output = fileio.splitext(os.path.basename(args.model))[0] + "_conv" + model_format
1300
+ st = fileio.read_structure(args.model)
1301
+ if st[0].has_hydrogen():
1302
+ logger.writeln("Hydrogen atoms are detected. I cannot take care of them, so I will remove them.")
1303
+ st.remove_hydrogens()
1304
+ for chain in st[0]:
1305
+ if args.chains and chain.name not in args.chains:
1306
+ continue
1307
+ for res in chain:
1308
+ alt = "*" # XXX
1309
+ if res.name in rna_res and args.to_dna:
1310
+ logger.writeln(f"Changing {chain.name}/{res.seqid} {res.name} to DNA")
1311
+ res.name = rna_res[res.name]
1312
+ res.remove_atom("O2'", alt)
1313
+ if res.name == "DT":
1314
+ C4 = res.find_atom("C4", alt)
1315
+ C5 = res.find_atom("C5", alt)
1316
+ C6 = res.find_atom("C6", alt)
1317
+ v1 = C5.pos - C4.pos
1318
+ v2 = C5.pos - C6.pos
1319
+ v = v1 + v2
1320
+ res.add_atom(C5)
1321
+ res[-1].name = "C7"
1322
+ res[-1].pos = C5.pos + v / v.length() * 1.5
1323
+ elif res.name in dna_res and args.to_rna:
1324
+ logger.writeln(f"Changing {chain.name}/{res.seqid} {res.name} to RNA")
1325
+ res.name = dna_res[res.name]
1326
+ C1p = numpy.array(res.find_atom("C1'", alt).pos.tolist())
1327
+ C2p = numpy.array(res.find_atom("C2'", alt).pos.tolist())
1328
+ C3p = numpy.array(res.find_atom("C3'", alt).pos.tolist())
1329
+ rotvec = C2p - C3p
1330
+ rotvec /= numpy.linalg.norm(rotvec)
1331
+ r = scipy.spatial.transform.Rotation.from_rotvec(-rotvec * 120,
1332
+ degrees=True)
1333
+ rotated = r.apply(C1p - C2p)
1334
+ rotated *= 1.411 / numpy.linalg.norm(rotated)
1335
+ res.add_atom(res.find_atom("O3'", alt))
1336
+ res[-1].name = "O2'"
1337
+ res[-1].pos.fromlist(C2p + rotated)
1338
+ if res.name == "U":
1339
+ res.remove_atom("C7", alt)
1340
+ fileio.write_model(st, file_name=args.output)
1341
+ # dnarna()
1342
+
1343
+ def show(args):
1344
+ for filename in args.files:
1345
+ ext = fileio.splitext(filename)[1]
1346
+ if ext in (".mrc", ".ccp4", ".map"):
1347
+ fileio.read_ccp4_map(filename)
1348
+ logger.writeln("\n")
1349
+ # show()
1350
+
1351
+ def json2csv(args):
1352
+ if not args.output_prefix:
1353
+ args.output_prefix = fileio.splitext(os.path.basename(args.json))[0]
1354
+
1355
+ df = pandas.read_json(args.json)
1356
+ df.to_csv(args.output_prefix+".csv", index=False)
1357
+ logger.writeln("Output: {}".format(args.output_prefix+".csv"))
1358
+ # json2csv()
1359
+
1360
+ def main(args):
1361
+ comms = dict(show=show,
1362
+ json2csv=json2csv,
1363
+ symmodel=symmodel,
1364
+ helical_biomt=helical_biomt,
1365
+ expand=symexpand,
1366
+ h_add=h_add,
1367
+ add_op3=add_op3,
1368
+ map_peaks=map_peaks,
1369
+ h_density=h_density_analysis,
1370
+ fix_link=fix_link,
1371
+ merge_models=merge_models,
1372
+ merge_dicts=merge_dicts,
1373
+ geom=geometry,
1374
+ adp=adp_stats,
1375
+ power=show_power,
1376
+ fcalc=fcalc,
1377
+ nemap=nemap,
1378
+ blur=blur,
1379
+ mask_from_model=mask_from_model,
1380
+ applymask=applymask,
1381
+ map2mtz=map2mtz,
1382
+ sm2mm=sm2mm,
1383
+ seq=seq,
1384
+ dnarna=dnarna)
1385
+
1386
+ com = args.subcommand
1387
+ f = comms.get(com)
1388
+ if f:
1389
+ return f(args)
1390
+ else:
1391
+ raise SystemExit("Unknown subcommand: {}".format(com))
1392
+ # main()
1393
+
1394
+ if __name__ == "__main__":
1395
+ import sys
1396
+ args = parse_args(sys.argv[1:])
1397
+ main(args)