servalcat 0.4.131__cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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.
- servalcat/__init__.py +10 -0
- servalcat/__main__.py +120 -0
- servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
- servalcat/refine/__init__.py +0 -0
- servalcat/refine/cgsolve.py +100 -0
- servalcat/refine/refine.py +1162 -0
- servalcat/refine/refine_geom.py +245 -0
- servalcat/refine/refine_spa.py +400 -0
- servalcat/refine/refine_xtal.py +339 -0
- servalcat/refine/spa.py +151 -0
- servalcat/refine/xtal.py +312 -0
- servalcat/refmac/__init__.py +0 -0
- servalcat/refmac/exte.py +191 -0
- servalcat/refmac/refmac_keywords.py +660 -0
- servalcat/refmac/refmac_wrapper.py +423 -0
- servalcat/spa/__init__.py +0 -0
- servalcat/spa/fofc.py +488 -0
- servalcat/spa/fsc.py +391 -0
- servalcat/spa/localcc.py +197 -0
- servalcat/spa/realspcc_from_var.py +128 -0
- servalcat/spa/run_refmac.py +979 -0
- servalcat/spa/shift_maps.py +293 -0
- servalcat/spa/shiftback.py +137 -0
- servalcat/spa/translate.py +129 -0
- servalcat/utils/__init__.py +35 -0
- servalcat/utils/commands.py +1629 -0
- servalcat/utils/fileio.py +836 -0
- servalcat/utils/generate_operators.py +296 -0
- servalcat/utils/hkl.py +811 -0
- servalcat/utils/logger.py +140 -0
- servalcat/utils/maps.py +345 -0
- servalcat/utils/model.py +933 -0
- servalcat/utils/refmac.py +759 -0
- servalcat/utils/restraints.py +888 -0
- servalcat/utils/symmetry.py +298 -0
- servalcat/xtal/__init__.py +0 -0
- servalcat/xtal/french_wilson.py +262 -0
- servalcat/xtal/run_refmac_small.py +240 -0
- servalcat/xtal/sigmaa.py +1954 -0
- servalcat/xtal/twin.py +316 -0
- servalcat-0.4.131.dist-info/METADATA +60 -0
- servalcat-0.4.131.dist-info/RECORD +45 -0
- servalcat-0.4.131.dist-info/WHEEL +6 -0
- servalcat-0.4.131.dist-info/entry_points.txt +4 -0
- servalcat-0.4.131.dist-info/licenses/LICENSE +373 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Care starting grid!!
|
|
2
|
+
"""
|
|
3
|
+
Author: "Keitaro Yamashita, Garib N. Murshudov"
|
|
4
|
+
MRC Laboratory of Molecular Biology
|
|
5
|
+
|
|
6
|
+
This software is released under the
|
|
7
|
+
Mozilla Public License, version 2.0; see LICENSE.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import absolute_import, division, print_function, generators
|
|
10
|
+
import os
|
|
11
|
+
import gemmi
|
|
12
|
+
import numpy
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
from servalcat.utils import logger
|
|
16
|
+
from servalcat import utils
|
|
17
|
+
|
|
18
|
+
def add_arguments(parser):
|
|
19
|
+
parser.description = 'Trim maps and shift models into a small new box.'
|
|
20
|
+
parser.epilog = 'If --mask is provided, a boundary is decided using the mask and --padding. Otherwise the model is used.'
|
|
21
|
+
parser.add_argument('--maps', nargs="+", action="append",
|
|
22
|
+
help='Input map file(s)')
|
|
23
|
+
parser.add_argument('--mask',
|
|
24
|
+
help='Mask file')
|
|
25
|
+
parser.add_argument('--pixel_size', type=float,
|
|
26
|
+
help='Override pixel size (A)')
|
|
27
|
+
parser.add_argument('--model', nargs="+", action="append",
|
|
28
|
+
help='Input atomic model file(s)')
|
|
29
|
+
parser.add_argument("--no_expand_ncs", action='store_true',
|
|
30
|
+
help="Do not expand strict NCS in MTRIX or _struct_ncs_oper")
|
|
31
|
+
parser.add_argument('--padding', type=float, default=10.0,
|
|
32
|
+
help='padding in angstrom unit (default: %(default).1f)')
|
|
33
|
+
parser.add_argument('--mask_cutoff', type=float, default=0.5,
|
|
34
|
+
help='Mask value cutoff to define boundary (default: %(default).1f)')
|
|
35
|
+
parser.add_argument('--noncubic',
|
|
36
|
+
action='store_true')
|
|
37
|
+
parser.add_argument('--noncentered',
|
|
38
|
+
action='store_true',
|
|
39
|
+
help='If specified non-centered trimming is performed. Not recommended if having some symmetry')
|
|
40
|
+
parser.add_argument('--no_shift',
|
|
41
|
+
action='store_true',
|
|
42
|
+
help='If specified resultant maps will have shifted origin and overlap with the input maps.')
|
|
43
|
+
parser.add_argument('--no_shift_keep_cell',
|
|
44
|
+
action='store_true',
|
|
45
|
+
help='Keep original unit cell when --no_shift is given')
|
|
46
|
+
parser.add_argument('--force_cell', type=float, nargs=6,
|
|
47
|
+
metavar=("a", "b", "c", "alpha", "beta", "gamma"),
|
|
48
|
+
help='Use specified unit cell parameter')
|
|
49
|
+
parser.add_argument('--disable_cell_check', action='store_true',
|
|
50
|
+
help="Turn off unit cell consistency test")
|
|
51
|
+
parser.add_argument("--shifts", help="Specify shifts.json to use precalculated parameters")
|
|
52
|
+
# add_arguments()
|
|
53
|
+
|
|
54
|
+
def parse_args(arg_list):
|
|
55
|
+
parser = argparse.ArgumentParser()
|
|
56
|
+
add_arguments(parser)
|
|
57
|
+
return parser.parse_args(arg_list)
|
|
58
|
+
# parse_args()
|
|
59
|
+
|
|
60
|
+
def check_maps(map_files, pixel_size=None, disable_cell_check=False):
|
|
61
|
+
logger.writeln("Input map files:")
|
|
62
|
+
params = []
|
|
63
|
+
for f in map_files:
|
|
64
|
+
g, gs, _ = utils.fileio.read_ccp4_map(f, pixel_size=pixel_size)
|
|
65
|
+
params.append((g.unit_cell.parameters, g.shape, g.spacing, gs))
|
|
66
|
+
|
|
67
|
+
shapes = set([x[1] for x in params])
|
|
68
|
+
if len(shapes) > 1:
|
|
69
|
+
raise RuntimeError("Error: different grid size included")
|
|
70
|
+
|
|
71
|
+
starts = set([tuple(x[3]) for x in params])
|
|
72
|
+
if len(starts) > 1:
|
|
73
|
+
raise RuntimeError("Error: different origins included")
|
|
74
|
+
|
|
75
|
+
cells = set([x[0] for x in params])
|
|
76
|
+
if len(cells) > 1:
|
|
77
|
+
if disable_cell_check:
|
|
78
|
+
logger.writeln("WARNING: Cells are different. Using the first one.")
|
|
79
|
+
else:
|
|
80
|
+
raise RuntimeError("Error: different cell parameters included")
|
|
81
|
+
|
|
82
|
+
return params[0]
|
|
83
|
+
# check_maps()
|
|
84
|
+
|
|
85
|
+
def write_shifts_json(filename, cell, shape, new_cell, new_shape, starts, shifts):
|
|
86
|
+
with open(filename, "w") as ofs:
|
|
87
|
+
json.dump(dict(cell=cell,
|
|
88
|
+
grid=shape,
|
|
89
|
+
new_cell=new_cell,
|
|
90
|
+
new_grid=new_shape,
|
|
91
|
+
starts=starts,
|
|
92
|
+
shifts=shifts),
|
|
93
|
+
ofs, indent=2)
|
|
94
|
+
# write_shifts_json()
|
|
95
|
+
|
|
96
|
+
def determine_shape_and_shift(mask, grid_start, padding, sts=None, mask_cutoff=1e-5, noncentered=False, noncubic=False,
|
|
97
|
+
json_out="trim_shifts.json"):
|
|
98
|
+
# XXX need to think more carefully.
|
|
99
|
+
# probably we need a special class for non-crystallographic maps (reset unit cell with box size)
|
|
100
|
+
# negative grid_start with mask (not model) may be problematic
|
|
101
|
+
grid_shape = numpy.array(mask.shape)
|
|
102
|
+
spacing = numpy.array(mask.spacing)
|
|
103
|
+
grid_start = numpy.array(grid_start)
|
|
104
|
+
grid_end = grid_start + grid_shape # for indexing grid_end-1 is the end
|
|
105
|
+
cell = mask.unit_cell
|
|
106
|
+
logger.writeln("Original grid start: {:4d} {:4d} {:4d}".format(*grid_start))
|
|
107
|
+
logger.writeln(" grid end: {:4d} {:4d} {:4d}".format(*(grid_end-1)))
|
|
108
|
+
if sts:
|
|
109
|
+
# here model is used to define region and mask content is ignored.
|
|
110
|
+
allpos = numpy.array([cra.atom.pos.tolist() for st in sts for cra in st[0].all()])
|
|
111
|
+
minp = mask.get_nearest_point(gemmi.Position(*numpy.min(allpos, axis=0)))
|
|
112
|
+
maxp = mask.get_nearest_point(gemmi.Position(*numpy.max(allpos, axis=0)))
|
|
113
|
+
tmp = [(minp.u, maxp.u), (minp.v, maxp.v), (minp.w, maxp.w)] - grid_start[:,None]
|
|
114
|
+
else:
|
|
115
|
+
tmp = numpy.where(mask.array>mask_cutoff) - grid_start[:,None]
|
|
116
|
+
|
|
117
|
+
tmp += (grid_shape[:,None]*numpy.floor(1-tmp/grid_shape[:,None])).astype(int) + grid_start[:,None]
|
|
118
|
+
limits = [(min(x), max(x)) for x in tmp.tolist()]
|
|
119
|
+
|
|
120
|
+
p = numpy.ceil(padding / spacing).astype(int).tolist()
|
|
121
|
+
logger.writeln("Limits: {} {} {}".format(*limits))
|
|
122
|
+
logger.writeln("Padding: {} {} {}".format(*p))
|
|
123
|
+
slices = [0, 0, 0]
|
|
124
|
+
if noncentered:
|
|
125
|
+
logger.writeln("Non-centered trimming will be performed.")
|
|
126
|
+
for i in range(3):
|
|
127
|
+
start = max(grid_start[i], limits[i][0]-p[i])
|
|
128
|
+
stop = min(limits[i][1]+p[i]+1, grid_end[i])
|
|
129
|
+
if (stop-start)%2 == 1:
|
|
130
|
+
if start > 0: start -= 1
|
|
131
|
+
elif stop < grid_end[i]: stop += 1
|
|
132
|
+
slices[i] = slice(start, stop)
|
|
133
|
+
else:
|
|
134
|
+
logger.writeln("Centered trimming will be performed.")
|
|
135
|
+
for i in range(3):
|
|
136
|
+
ctr = (grid_shape[i]-1)/2 + grid_start[i]
|
|
137
|
+
rad = max(ctr-(limits[i][0]-p[i]), (limits[i][1]+p[i])-ctr)
|
|
138
|
+
logger.writeln("Rad{}= {}".format(i, rad))
|
|
139
|
+
if rad < grid_shape[i]/2:
|
|
140
|
+
slices[i] = slice(int(ctr-rad), int(ctr+rad)+1, None)
|
|
141
|
+
else:
|
|
142
|
+
slices[i] = slice(grid_start[i], grid_end[i], None)
|
|
143
|
+
|
|
144
|
+
logger.writeln("Slices: {}".format(slices))
|
|
145
|
+
if not noncubic:
|
|
146
|
+
# only works when input grid is cubic; otherwise need to expand
|
|
147
|
+
if len(set(mask.shape)) > 1:
|
|
148
|
+
raise RuntimeError("Input grid is not cubic. Try --noncubic")
|
|
149
|
+
min_s = min([slices[i].start for i in range(3)])
|
|
150
|
+
max_s = max([slices[i].stop for i in range(3)])
|
|
151
|
+
slices = [slice(min_s, max_s, None) for i in range(3)]
|
|
152
|
+
logger.writeln("Cubic Slices: {}".format(slices))
|
|
153
|
+
|
|
154
|
+
starts = [slices[i].start for i in range(3)]
|
|
155
|
+
|
|
156
|
+
# Shifts for model
|
|
157
|
+
shifts = -mask.get_position(slices[0].start, slices[1].start, slices[2].start)
|
|
158
|
+
logger.writeln("Shift for model: {} {} {}".format(*shifts.tolist()))
|
|
159
|
+
|
|
160
|
+
new_shape = [slices[0].stop-slices[0].start,
|
|
161
|
+
slices[1].stop-slices[1].start,
|
|
162
|
+
slices[2].stop-slices[2].start]
|
|
163
|
+
tmp = mask.get_position(*new_shape)
|
|
164
|
+
new_cell = gemmi.UnitCell(tmp[0], tmp[1], tmp[2], cell.alpha, cell.beta, cell.gamma)
|
|
165
|
+
|
|
166
|
+
logger.writeln("New Cell: {:.4f} {:.4f} {:.4f} {:.3f} {:.3f} {:.3f}".format(*new_cell.parameters))
|
|
167
|
+
logger.writeln("New grid: {} {} {}".format(*new_shape))
|
|
168
|
+
|
|
169
|
+
if json_out:
|
|
170
|
+
write_shifts_json(json_out,
|
|
171
|
+
cell=mask.unit_cell.parameters, shape=mask.shape,
|
|
172
|
+
new_cell=new_cell.parameters, new_shape=list(map(int, new_shape)),
|
|
173
|
+
starts=list(map(int, starts)),
|
|
174
|
+
shifts=shifts.tolist())
|
|
175
|
+
|
|
176
|
+
return new_cell, new_shape, starts, shifts
|
|
177
|
+
# determine_shape_and_shift()
|
|
178
|
+
|
|
179
|
+
def main(args):
|
|
180
|
+
if not args.maps and not args.shifts:
|
|
181
|
+
raise SystemExit("Give --maps or --shifts")
|
|
182
|
+
|
|
183
|
+
if not args.mask and args.model and not args.shifts and args.padding <= 0:
|
|
184
|
+
raise SystemExit("--padding must be > 0 if you want to create a mask from the model.")
|
|
185
|
+
|
|
186
|
+
if args.no_shift_keep_cell and not args.no_shift:
|
|
187
|
+
logger.writeln("WARNING: --no_shift_keep_cell has no effect when --no_shift not given")
|
|
188
|
+
|
|
189
|
+
if args.maps:
|
|
190
|
+
args.maps = sum(args.maps, [])
|
|
191
|
+
else:
|
|
192
|
+
args.maps = []
|
|
193
|
+
|
|
194
|
+
if args.shifts:
|
|
195
|
+
if args.noncubic or args.noncentered or args.mask:
|
|
196
|
+
raise SystemExit("You cannot specify --noncubic/--noncentered/--mask if --shifts given")
|
|
197
|
+
if not args.maps and not args.model:
|
|
198
|
+
raise SystemExit("Give --maps or --model")
|
|
199
|
+
info = json.load(open(args.shifts))
|
|
200
|
+
cell = info["cell"]
|
|
201
|
+
elif args.maps:
|
|
202
|
+
cell, grid_shape, spacing, grid_start = check_maps(args.maps, args.pixel_size, args.disable_cell_check)
|
|
203
|
+
|
|
204
|
+
if args.force_cell:
|
|
205
|
+
cell = args.force_cell
|
|
206
|
+
|
|
207
|
+
cell = gemmi.UnitCell(*cell)
|
|
208
|
+
sts, cif_refs = [], []
|
|
209
|
+
sts_for_mask = []
|
|
210
|
+
if args.model:
|
|
211
|
+
args.model = sum(args.model, [])
|
|
212
|
+
for m in args.model:
|
|
213
|
+
st, cif_ref = utils.fileio.read_structure_from_pdb_and_mmcif(m)
|
|
214
|
+
st.spacegroup_hm = "P1"
|
|
215
|
+
st.cell = cell
|
|
216
|
+
# XXX here we do not want moving into box (with "crystallographic" translation) at least for --no_shift
|
|
217
|
+
sts.append(st)
|
|
218
|
+
cif_refs.append(cif_ref)
|
|
219
|
+
|
|
220
|
+
if args.mask:
|
|
221
|
+
logger.writeln("Using mask to decide border: {}".format(args.mask))
|
|
222
|
+
mask = utils.fileio.read_ccp4_map(args.mask)[0]
|
|
223
|
+
assert mask.shape == grid_shape
|
|
224
|
+
mask.set_unit_cell(cell)
|
|
225
|
+
mask.spacegroup = gemmi.SpaceGroup(1)
|
|
226
|
+
if args.mask not in args.maps: # need to check with normalized paths?
|
|
227
|
+
args.maps.append(args.mask)
|
|
228
|
+
elif args.model and not args.shifts:
|
|
229
|
+
logger.writeln("Using model to decide border: {}".format(args.model))
|
|
230
|
+
mask = gemmi.FloatGrid(*grid_shape) # this is just dummy. values will not be seen.
|
|
231
|
+
mask.set_unit_cell(cell)
|
|
232
|
+
mask.spacegroup = gemmi.SpaceGroup(1)
|
|
233
|
+
for st in sts:
|
|
234
|
+
if len(st.ncs) > 0 and not args.no_expand_ncs:
|
|
235
|
+
st = st.clone()
|
|
236
|
+
utils.model.expand_ncs(st)
|
|
237
|
+
sts_for_mask.append(st)
|
|
238
|
+
elif not args.shifts:
|
|
239
|
+
raise SystemExit("Give mask or model")
|
|
240
|
+
|
|
241
|
+
if not args.shifts:
|
|
242
|
+
new_cell, new_shape, starts, shifts = determine_shape_and_shift(mask=mask, grid_start=grid_start,
|
|
243
|
+
sts=sts_for_mask,
|
|
244
|
+
padding=args.padding,
|
|
245
|
+
mask_cutoff=args.mask_cutoff,
|
|
246
|
+
noncentered=args.noncentered,
|
|
247
|
+
noncubic=args.noncubic)
|
|
248
|
+
else:
|
|
249
|
+
new_cell = gemmi.UnitCell(*info["new_cell"])
|
|
250
|
+
new_shape, starts = info["new_grid"], info["starts"]
|
|
251
|
+
shifts = gemmi.Position(*info["shifts"])
|
|
252
|
+
|
|
253
|
+
if args.model and not args.no_shift:
|
|
254
|
+
for i, st in enumerate(sts):
|
|
255
|
+
st.cell = new_cell
|
|
256
|
+
# apply shifts to model
|
|
257
|
+
for mol in st:
|
|
258
|
+
for cra in mol.all():
|
|
259
|
+
cra.atom.pos += shifts
|
|
260
|
+
|
|
261
|
+
# apply shifts to translations
|
|
262
|
+
if len(st.ncs) > 0:
|
|
263
|
+
new_ops = utils.symmetry.apply_shift_for_ncsops(st.ncs, shifts)
|
|
264
|
+
st.ncs.clear()
|
|
265
|
+
st.ncs.extend(new_ops)
|
|
266
|
+
st.setup_cell_images()
|
|
267
|
+
spext = utils.fileio.splitext(os.path.basename(args.model[i]))
|
|
268
|
+
if len(st.ncs) > 0 and not args.no_expand_ncs:
|
|
269
|
+
logger.writeln(" Writing symmetry expanded model for shifted model")
|
|
270
|
+
utils.symmetry.write_symmetry_expanded_model(st, spext[0]+"_trimmed_local_expanded",
|
|
271
|
+
pdb=True, cif=True)
|
|
272
|
+
|
|
273
|
+
# save files
|
|
274
|
+
utils.fileio.write_model(st, file_name=spext[0]+"_trimmed"+spext[1], cif_ref=cif_refs[i])
|
|
275
|
+
|
|
276
|
+
for f in args.maps:
|
|
277
|
+
logger.writeln("Slicing {}".format(f))
|
|
278
|
+
outf = os.path.basename(utils.fileio.splitext(f)[0])+"_trimmed.mrc"
|
|
279
|
+
g = utils.fileio.read_ccp4_map(f, pixel_size=args.pixel_size)[0]
|
|
280
|
+
if args.no_shift:
|
|
281
|
+
utils.maps.write_ccp4_map(outf, g, cell=cell, sg=g.spacegroup,
|
|
282
|
+
grid_start=starts, grid_shape=new_shape,
|
|
283
|
+
update_cell=not args.no_shift_keep_cell)
|
|
284
|
+
else:
|
|
285
|
+
newg = g.get_subarray(starts, new_shape)
|
|
286
|
+
utils.maps.write_ccp4_map(outf, newg, cell=new_cell, sg=g.spacegroup)
|
|
287
|
+
|
|
288
|
+
# main()
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
import sys
|
|
292
|
+
args = parse_args(sys.argv[1:])
|
|
293
|
+
main(args)
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
import os
|
|
10
|
+
import gemmi
|
|
11
|
+
import numpy
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
from servalcat.utils import logger
|
|
15
|
+
from servalcat import utils
|
|
16
|
+
|
|
17
|
+
def add_arguments(parser):
|
|
18
|
+
parser.description = 'Shift back for Refmac local refinement results'
|
|
19
|
+
parser.add_argument('--model',
|
|
20
|
+
help='Atomic model file that needs shift back')
|
|
21
|
+
parser.add_argument('--refine_mtz',
|
|
22
|
+
help='Local-refined Refmac mtz file that needs shift back')
|
|
23
|
+
parser.add_argument('--shifts',
|
|
24
|
+
required=True,
|
|
25
|
+
default="shifts.json",
|
|
26
|
+
help='Shift information file')
|
|
27
|
+
parser.add_argument('--output_prefix',
|
|
28
|
+
help='output file prefix')
|
|
29
|
+
# add_arguments()
|
|
30
|
+
|
|
31
|
+
def parse_args(arg_list):
|
|
32
|
+
parser = argparse.ArgumentParser()
|
|
33
|
+
add_arguments(parser)
|
|
34
|
+
return parser.parse_args(arg_list)
|
|
35
|
+
# parse_args()
|
|
36
|
+
|
|
37
|
+
def refmac_mtz_in_original_cell(org_cell, org_grid_size, new_grid_size, shifts, mtz_in, mtz_out):
|
|
38
|
+
targets = (("FWT", "PHWT"), ("DELFWT", "PHDELWT"))
|
|
39
|
+
|
|
40
|
+
shifts_frac = org_cell.fractionalize(gemmi.Position(*shifts)).tolist()
|
|
41
|
+
|
|
42
|
+
# Output mtz
|
|
43
|
+
mtz = gemmi.Mtz()
|
|
44
|
+
mtz.spacegroup = gemmi.SpaceGroup("P1")
|
|
45
|
+
mtz.cell = org_cell
|
|
46
|
+
mtz.add_dataset('HKL_base')
|
|
47
|
+
for l in ["H", "K", "L"]: mtz.add_column(l, "H")
|
|
48
|
+
|
|
49
|
+
data = None
|
|
50
|
+
for i in range(len(targets)):
|
|
51
|
+
d_min, m = utils.fileio.read_map_from_mtz(mtz_in, targets[i], new_grid_size)
|
|
52
|
+
F = numpy.fft.fftn(m, org_grid_size).conj()
|
|
53
|
+
grid = gemmi.ReciprocalComplexGrid(F.astype(numpy.complex64), mtz.cell, mtz.spacegroup)
|
|
54
|
+
asu = grid.prepare_asu_data(dmin=d_min)
|
|
55
|
+
if data is None:
|
|
56
|
+
data = numpy.empty((len(asu), 3+2*len(targets)))
|
|
57
|
+
data[:,:3] = asu.miller_array
|
|
58
|
+
|
|
59
|
+
shift_factor = numpy.exp(-2j*numpy.pi*numpy.dot(asu.miller_array, shifts_frac))
|
|
60
|
+
F_shift = asu.value_array * shift_factor
|
|
61
|
+
data[:,3+2*i] = numpy.absolute(F_shift)
|
|
62
|
+
data[:,3+2*i+1] = numpy.angle(F_shift, deg=True)
|
|
63
|
+
|
|
64
|
+
mtz.add_column(targets[i][0], "F")
|
|
65
|
+
mtz.add_column(targets[i][1], "P")
|
|
66
|
+
|
|
67
|
+
mtz.set_data(data)
|
|
68
|
+
mtz.write_to_file(mtz_out)
|
|
69
|
+
# refmac_mtz_in_original_cell()
|
|
70
|
+
|
|
71
|
+
def shift_back_model(st, shifts):
|
|
72
|
+
# shifts must be Vec3 object
|
|
73
|
+
|
|
74
|
+
for model in st:
|
|
75
|
+
for cra in model.all():
|
|
76
|
+
cra.atom.pos -= shifts
|
|
77
|
+
|
|
78
|
+
if len(st.ncs) > 0:
|
|
79
|
+
for n in st.ncs:
|
|
80
|
+
newv = n.tr.vec - shifts + n.tr.mat.multiply(shifts)
|
|
81
|
+
n.tr.vec.fromlist(newv.tolist())
|
|
82
|
+
# shift_back_model()
|
|
83
|
+
|
|
84
|
+
def shift_back_tls(tlsgroups, shifts):
|
|
85
|
+
for g in tlsgroups:
|
|
86
|
+
if g["origin"] is not None:
|
|
87
|
+
g["origin"] -= shifts
|
|
88
|
+
# shift_back_tls()
|
|
89
|
+
|
|
90
|
+
def shift_back(xyz_in, shifts_json, refine_mtz=None, out_prefix=None):
|
|
91
|
+
logger.writeln("Reading shifts info from {}".format(shifts_json))
|
|
92
|
+
info = json.load(open(shifts_json))
|
|
93
|
+
for k in info:
|
|
94
|
+
logger.writeln(" {}= {}".format(k, info[k]))
|
|
95
|
+
|
|
96
|
+
org_cell = gemmi.UnitCell(*info["cell"])
|
|
97
|
+
shifts = gemmi.Position(*info["shifts"])
|
|
98
|
+
|
|
99
|
+
if refine_mtz:
|
|
100
|
+
logger.writeln("Transforming MTZ: {}".format(refine_mtz))
|
|
101
|
+
if out_prefix:
|
|
102
|
+
mtz_out = out_prefix+".mtz"
|
|
103
|
+
else:
|
|
104
|
+
mtz_out = utils.fileio.splitext(os.path.basename(refine_mtz))[0] + "_shiftback.mtz"
|
|
105
|
+
|
|
106
|
+
refmac_mtz_in_original_cell(org_cell,
|
|
107
|
+
info["grid"],
|
|
108
|
+
info["new_grid"],
|
|
109
|
+
info["shifts"],
|
|
110
|
+
refine_mtz,
|
|
111
|
+
mtz_out)
|
|
112
|
+
|
|
113
|
+
if xyz_in:
|
|
114
|
+
logger.writeln("Shifting back model: {}".format(xyz_in))
|
|
115
|
+
st, cif_ref = utils.fileio.read_structure_from_pdb_and_mmcif(xyz_in)
|
|
116
|
+
|
|
117
|
+
st.cell = org_cell
|
|
118
|
+
shift_back_model(st, shifts)
|
|
119
|
+
prefix = out_prefix if out_prefix else utils.fileio.splitext(os.path.basename(xyz_in))[0] + "_shiftback"
|
|
120
|
+
utils.fileio.write_model(st, prefix,
|
|
121
|
+
pdb=True, cif=True, cif_ref=cif_ref)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# shift_back()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main(args):
|
|
128
|
+
if not args.model and not args.refine_mtz:
|
|
129
|
+
raise SystemExit("ERROR: give --model and/or --refine_mtz")
|
|
130
|
+
|
|
131
|
+
shift_back(args.model, args.shifts, args.refine_mtz, args.output_prefix)
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
import sys
|
|
135
|
+
args = parse_args(sys.argv[1:])
|
|
136
|
+
main(args)
|
|
137
|
+
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
import gemmi
|
|
10
|
+
import numpy
|
|
11
|
+
import pandas
|
|
12
|
+
from servalcat.utils import logger
|
|
13
|
+
from servalcat import utils
|
|
14
|
+
from servalcat import spa
|
|
15
|
+
|
|
16
|
+
def add_arguments(parser):
|
|
17
|
+
parser.description = 'Find translation of the model in the map'
|
|
18
|
+
parser.add_argument('--model',
|
|
19
|
+
required=True,
|
|
20
|
+
help="")
|
|
21
|
+
group = parser.add_mutually_exclusive_group()
|
|
22
|
+
group.add_argument("--halfmaps", nargs=2, help="Input half map files")
|
|
23
|
+
group.add_argument("--map", help="Use this only if you really do not have half maps.")
|
|
24
|
+
parser.add_argument('--mask',
|
|
25
|
+
help='Mask file')
|
|
26
|
+
parser.add_argument('--pixel_size', type=float,
|
|
27
|
+
help='Override pixel size (A)')
|
|
28
|
+
parser.add_argument('-d', '--resolution',
|
|
29
|
+
type=float,
|
|
30
|
+
required=True,
|
|
31
|
+
help='')
|
|
32
|
+
parser.add_argument('--no_interpolation', action="store_true",
|
|
33
|
+
help="No interpolation in peak finding of translation function")
|
|
34
|
+
parser.add_argument('-o', '--output_prefix', default="translated")
|
|
35
|
+
|
|
36
|
+
# add_arguments()
|
|
37
|
+
|
|
38
|
+
def parse_args(arg_list):
|
|
39
|
+
parser = argparse.ArgumentParser()
|
|
40
|
+
add_arguments(parser)
|
|
41
|
+
return parser.parse_args(arg_list)
|
|
42
|
+
# parse_args()
|
|
43
|
+
|
|
44
|
+
def calc_fsc(hkldata, lab1, lab2):
|
|
45
|
+
stats = hkldata.binned_df["ml"][["d_min", "d_max"]].copy()
|
|
46
|
+
stats["ncoeffs"] = 0
|
|
47
|
+
stats["fsc"] = 0.
|
|
48
|
+
for i_bin, idxes in hkldata.binned("ml"):
|
|
49
|
+
stats.loc[i_bin, "ncoeffs"] = len(idxes)
|
|
50
|
+
stats.loc[i_bin, "fsc"] = numpy.real(numpy.corrcoef(hkldata.df[lab1].to_numpy()[idxes],
|
|
51
|
+
hkldata.df[lab2].to_numpy()[idxes])[1,0])
|
|
52
|
+
|
|
53
|
+
sum_n = sum(stats.ncoeffs)
|
|
54
|
+
fscavg = sum(stats.ncoeffs*stats.fsc)/sum_n
|
|
55
|
+
return stats, fscavg
|
|
56
|
+
# calc_fsc()
|
|
57
|
+
|
|
58
|
+
def find_peak(tf_map, ini_pos):
|
|
59
|
+
logger.writeln("Finding peak using interpolation..")
|
|
60
|
+
|
|
61
|
+
x = tf_map.unit_cell.fractionalize(ini_pos)
|
|
62
|
+
logger.writeln(" x0: [{}, {}, {}]".format(*x.tolist()))
|
|
63
|
+
logger.writeln(" f0: {}".format(-tf_map.interpolate_value(x, order=3)))
|
|
64
|
+
|
|
65
|
+
res = scipy.optimize.minimize(fun=lambda x:-tf_map.interpolate_value(gemmi.Fractional(*x), order=3),
|
|
66
|
+
x0=x.tolist(),
|
|
67
|
+
jac=lambda x:-numpy.array(tf_map.tricubic_interpolation_der(gemmi.Fractional(*x))[1:]))
|
|
68
|
+
logger.writeln(str(res))
|
|
69
|
+
final_pos = tf_map.unit_cell.orthogonalize(gemmi.Fractional(*res.x))
|
|
70
|
+
logger.writeln(" Move from initial: [{:.3f}, {:.3f}, {:.3f}] A".format(*(final_pos-ini_pos).tolist()))
|
|
71
|
+
return final_pos
|
|
72
|
+
# find_peak()
|
|
73
|
+
|
|
74
|
+
def main(args):
|
|
75
|
+
if args.halfmaps:
|
|
76
|
+
maps = utils.fileio.read_halfmaps(args.halfmaps, pixel_size=args.pixel_size)
|
|
77
|
+
assert maps[0][0].shape == maps[1][0].shape
|
|
78
|
+
assert maps[0][0].unit_cell == maps[1][0].unit_cell
|
|
79
|
+
assert maps[0][1] == maps[1][1]
|
|
80
|
+
else:
|
|
81
|
+
maps = [utils.fileio.read_ccp4_map(args.map, pixel_size=args.pixel_size)]
|
|
82
|
+
|
|
83
|
+
model_format = utils.fileio.check_model_format(args.model)
|
|
84
|
+
st = utils.fileio.read_structure(args.model)
|
|
85
|
+
st.cell = maps[0][0].unit_cell
|
|
86
|
+
st.spacegroup_hm = "P1"
|
|
87
|
+
|
|
88
|
+
if args.mask:
|
|
89
|
+
mask = utils.fileio.read_ccp4_map(args.mask)[0]
|
|
90
|
+
else:
|
|
91
|
+
mask = None
|
|
92
|
+
|
|
93
|
+
hkldata = utils.maps.mask_and_fft_maps(maps, args.resolution, mask=None)
|
|
94
|
+
hkldata.df["FC"] = utils.model.calc_fc_fft(st, args.resolution - 1e-6, source="electron",
|
|
95
|
+
miller_array=hkldata.miller_array())
|
|
96
|
+
hkldata.setup_relion_binning("ml")
|
|
97
|
+
|
|
98
|
+
stats, fscavg = calc_fsc(hkldata, "FP", "FC")
|
|
99
|
+
logger.writeln(stats.to_string())
|
|
100
|
+
logger.writeln("FSCaverage before translation = {:.4f}".format(fscavg))
|
|
101
|
+
|
|
102
|
+
hkldata.df["TF"] = hkldata.df.FP.to_numpy() * numpy.conj(hkldata.df.FC.to_numpy())
|
|
103
|
+
|
|
104
|
+
tf_map = hkldata.fft_map("TF")
|
|
105
|
+
max_idx = numpy.unravel_index(numpy.argmax(tf_map), tf_map.shape)
|
|
106
|
+
shift = tf_map.get_position(*max_idx)
|
|
107
|
+
|
|
108
|
+
if not args.no_interpolation:
|
|
109
|
+
shift = utils.maps.optimize_peak(tf_map, shift)
|
|
110
|
+
|
|
111
|
+
logger.writeln("shift= {:.4f}, {:.4f}, {:.4f} ".format(*shift))
|
|
112
|
+
|
|
113
|
+
# phase shift for translation
|
|
114
|
+
hkldata.df.FC *= numpy.exp(2.j*numpy.pi*numpy.dot(hkldata.miller_array(),
|
|
115
|
+
hkldata.cell.fractionalize(shift).tolist()))
|
|
116
|
+
stats, fscavg = calc_fsc(hkldata, "FP", "FC")
|
|
117
|
+
logger.writeln(stats.to_string())
|
|
118
|
+
logger.writeln("FSCaverage after translation = {:.4f}".format(fscavg))
|
|
119
|
+
|
|
120
|
+
tr = gemmi.Transform(gemmi.Mat33(), shift)
|
|
121
|
+
st[0].transform_pos_and_adp(tr)
|
|
122
|
+
utils.model.translate_into_box(st)
|
|
123
|
+
utils.fileio.write_model(st, file_name=args.output_prefix+model_format)
|
|
124
|
+
# main()
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
import sys
|
|
128
|
+
args = parse_args(sys.argv[1:])
|
|
129
|
+
main(args)
|
|
@@ -0,0 +1,35 @@
|
|
|
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 . import logger
|
|
10
|
+
from . import symmetry
|
|
11
|
+
from . import fileio
|
|
12
|
+
from . import hkl
|
|
13
|
+
from . import model
|
|
14
|
+
from . import maps
|
|
15
|
+
from . import refmac
|
|
16
|
+
from . import restraints
|
|
17
|
+
from . import commands
|
|
18
|
+
|
|
19
|
+
def make_loggraph_str(df, main_title, title_labs, s2=None, float_format=None):
|
|
20
|
+
if s2 is not None:
|
|
21
|
+
df = df.copy()
|
|
22
|
+
df.insert(0, "1/resol^2", s2)
|
|
23
|
+
ret = "$TABLE: {} :\n".format(main_title)
|
|
24
|
+
ret += "$GRAPHS\n"
|
|
25
|
+
all_labs = list(df.columns)
|
|
26
|
+
for t, labs in title_labs:
|
|
27
|
+
if s2 is not None: labs = ["1/resol^2"] + labs
|
|
28
|
+
ret += ": {} :A:{}:\n".format(t, ",".join(str(all_labs.index(l)+1) for l in labs))
|
|
29
|
+
ret += "$$\n"
|
|
30
|
+
lines = df.to_string(index=False, index_names=False, header=True, float_format=float_format).splitlines()
|
|
31
|
+
ret += lines[0] + "\n$$\n$$\n"
|
|
32
|
+
ret += "\n".join(lines[1:]) + "\n$$\n"
|
|
33
|
+
return ret
|
|
34
|
+
# make_loggraph_str()
|
|
35
|
+
|