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.
Files changed (45) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +1162 -0
  7. servalcat/refine/refine_geom.py +245 -0
  8. servalcat/refine/refine_spa.py +400 -0
  9. servalcat/refine/refine_xtal.py +339 -0
  10. servalcat/refine/spa.py +151 -0
  11. servalcat/refine/xtal.py +312 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +191 -0
  14. servalcat/refmac/refmac_keywords.py +660 -0
  15. servalcat/refmac/refmac_wrapper.py +423 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +488 -0
  18. servalcat/spa/fsc.py +391 -0
  19. servalcat/spa/localcc.py +197 -0
  20. servalcat/spa/realspcc_from_var.py +128 -0
  21. servalcat/spa/run_refmac.py +979 -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 +1629 -0
  27. servalcat/utils/fileio.py +836 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +811 -0
  30. servalcat/utils/logger.py +140 -0
  31. servalcat/utils/maps.py +345 -0
  32. servalcat/utils/model.py +933 -0
  33. servalcat/utils/refmac.py +759 -0
  34. servalcat/utils/restraints.py +888 -0
  35. servalcat/utils/symmetry.py +298 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +262 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1954 -0
  40. servalcat/xtal/twin.py +316 -0
  41. servalcat-0.4.131.dist-info/METADATA +60 -0
  42. servalcat-0.4.131.dist-info/RECORD +45 -0
  43. servalcat-0.4.131.dist-info/WHEEL +6 -0
  44. servalcat-0.4.131.dist-info/entry_points.txt +4 -0
  45. servalcat-0.4.131.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,140 @@
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 sys
10
+ import datetime
11
+ import platform
12
+ import getpass
13
+ import traceback
14
+ import shlex
15
+ import servalcat
16
+
17
+ class Logger(object):
18
+ def __init__(self, file_out=None, append=True):
19
+ self.ofs = None
20
+ self.stopped = False
21
+ self.prefix = ""
22
+ if file_out:
23
+ self.set_file(file_out, append)
24
+ # __init__()
25
+ def stop_logging(self): self.stopped = True
26
+ def start_logging(self): self.stopped = False
27
+ def set_prefix(self, p): self.prefix = p
28
+ def clear_prefix(self): self.prefix = ""
29
+
30
+ def set_file(self, file_out, append=True):
31
+ try:
32
+ self.ofs = open(file_out, "a" if append else "w")
33
+ except:
34
+ print("Error: Cannot open log file to write")
35
+ # set_file()
36
+
37
+ def write(self, l, end="", flush=True, fs=None, print_fs=sys.stdout):
38
+ if self.stopped: return
39
+ if self.prefix:
40
+ l = "".join(self.prefix + x for x in l.splitlines(keepends=True))
41
+ print(l, end=end, file=print_fs, flush=flush)
42
+ for f in (self.ofs, fs):
43
+ if f is not None:
44
+ f.write(l)
45
+ f.write(end)
46
+ if flush: f.flush()
47
+ # write()
48
+
49
+ def writeln(self, l, flush=True, fs=None, print_fs=sys.stdout):
50
+ self.write(l, end="\n", flush=flush, fs=fs, print_fs=print_fs)
51
+ # writeln()
52
+
53
+ def error(self, l, end="\n", flush=True, fs=None):
54
+ self.write(l, end, flush, fs, print_fs=sys.stderr)
55
+ # error()
56
+
57
+ def close(self):
58
+ if self.ofs is not None:
59
+ self.ofs.close()
60
+ self.ofs = None
61
+ # close()
62
+
63
+ def flush(self): # to act as a file object
64
+ if self.ofs:
65
+ self.ofs.flush()
66
+ # class Logger
67
+
68
+ _logger = Logger() # singleton
69
+ set_file = _logger.set_file
70
+ write = _logger.write
71
+ writeln = _logger.writeln
72
+ error = _logger.error
73
+ close = _logger.close
74
+ flush = _logger.flush
75
+ stop = _logger.stop_logging
76
+ start = _logger.start_logging
77
+ set_prefix = _logger.set_prefix
78
+ clear_prefix = _logger.clear_prefix
79
+
80
+ def with_prefix(prefix):
81
+ class WithPrefix(object): # should keep original prefix and restore?
82
+ def __enter__(self):
83
+ _logger.set_prefix(prefix)
84
+ return _logger
85
+ def __exit__(self, exc_type, exc_val, exc_tb):
86
+ _logger.clear_prefix()
87
+ return WithPrefix()
88
+
89
+ def silent():
90
+ class Silent(object):
91
+ def write(self, *args, **kwargs):
92
+ pass
93
+ def flush(self):
94
+ pass
95
+ return Silent()
96
+
97
+ def dependency_versions():
98
+ import gemmi
99
+ import scipy
100
+ import numpy
101
+ import pandas
102
+ return dict(gemmi=gemmi.__version__,
103
+ scipy=scipy.version.full_version,
104
+ numpy=numpy.version.full_version,
105
+ pandas=pandas.__version__)
106
+ # dependency_versions()
107
+
108
+ def versions_str():
109
+ tmpl = "Servalcat {servalcat} with Python {python} ({deps})"
110
+ return tmpl.format(servalcat=servalcat.__version__,
111
+ python=platform.python_version(),
112
+ deps=", ".join([x[0]+" "+x[1] for x in dependency_versions().items()]))
113
+ # versions_str()
114
+
115
+ def write_header(command="servalcat"):
116
+ writeln("# Servalcat ver. {} (Python {})".format(servalcat.__version__, platform.python_version()))
117
+ writeln("# Library vers. {}".format(", ".join([x[0]+" "+x[1] for x in dependency_versions().items()])))
118
+ writeln("# Started on {}".format(datetime.datetime.now()))
119
+ writeln("# Host: {} User: {}".format(platform.node(), getpass.getuser()))
120
+ writeln("# Command-line:")
121
+ writeln("# {} {}".format(command, " ".join(map(lambda x: shlex.quote(x), sys.argv[1:]))))
122
+ # write_header()
123
+
124
+ def exit_success():
125
+ _logger.writeln("\n# Finished on {}\n".format(datetime.datetime.now()))
126
+
127
+ def handle_exception(exc_type, exc_value, exc_traceback):
128
+ if issubclass(exc_type, KeyboardInterrupt):
129
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
130
+ return
131
+
132
+ name = type(exc_value).__name__ if hasattr(type(exc_value), "__name__") else "(unknown)"
133
+ #_logger.writeln("Uncaught exception: {}: {}".format(name, exc_value))
134
+ _logger.error("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
135
+ _logger.writeln("# Abnormally finished on {}\n".format(datetime.datetime.now()))
136
+ _logger.close()
137
+
138
+ # handle_exception()
139
+
140
+ sys.excepthook = handle_exception
@@ -0,0 +1,345 @@
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 copy
12
+ import scipy.optimize
13
+ import scipy.signal
14
+ from servalcat.utils import logger
15
+ from servalcat.utils import hkl
16
+ from servalcat import ext
17
+
18
+ def new_grid_like(gr):
19
+ return type(gr)(gr.array*0, gr.unit_cell, gr.spacegroup)
20
+ # new_grid_like()
21
+
22
+ def copy_maps(maps):
23
+ return [[type(m[0])(m[0].array, m[0].unit_cell, m[0].spacegroup)]+copy.deepcopy(m[1:]) for m in maps]
24
+
25
+ def mask_from_model(st, radius, soft_edge=0, grid=None, unit_cell=None, spacegroup=None, grid_shape=None,
26
+ ignore_hydrogen=True):
27
+ if grid is not None:
28
+ mask = new_grid_like(grid)
29
+ else:
30
+ mask = gemmi.FloatGrid(grid_shape)
31
+ mask.set_unit_cell(unit_cell)
32
+ mask.spacegroup = spacegroup
33
+
34
+ if ignore_hydrogen and st[0].has_hydrogen():
35
+ st = st.clone()
36
+ st.remove_hydrogens()
37
+
38
+ if soft_edge > 0:
39
+ ext.soft_mask_from_model(mask, st[0], radius, soft_edge)
40
+ else:
41
+ mask.mask_points_in_constant_radius(st[0], radius, 1.)
42
+ return mask
43
+ # mask_from_model()
44
+
45
+ def test_mask_with_model(mask, st, mask_threshold=.5, inclusion_cutoff=.8):
46
+ logger.writeln("Testing mask with model..")
47
+ n_all = 0
48
+ n_out = 0
49
+ for cra in st[0].all():
50
+ v = mask.interpolate_value(cra.atom.pos)
51
+ n_all += 1
52
+ if v < mask_threshold:
53
+ logger.writeln(" WARNING: mask value at {} = {:.1f}".format(cra, v))
54
+ n_out += 1
55
+
56
+ logger.writeln(" n_atoms= {} n_out_of_mask= {} ({:.2%})".format(n_all, n_out, n_out/n_all if n_all>0 else 0))
57
+ return 1 - n_out/n_all > inclusion_cutoff
58
+ # test_mask_with_model()
59
+
60
+ def check_symmetry_related_map_values(st, grid, cc_cutoff=0.9):
61
+ logger.writeln("Checking if model and map symmetry match.")
62
+ mapvals = numpy.array([grid.interpolate_value(cra.atom.pos) for cra in st[0].all()])
63
+ ret = False # return True if bad
64
+ for op in st.ncs:
65
+ if op.given: continue
66
+ st2 = st.clone()
67
+ st2[0].transform_pos_and_adp(op.tr)
68
+ mapvals2 = numpy.array([grid.interpolate_value(cra.atom.pos) for cra in st2[0].all()])
69
+ cc = numpy.corrcoef(mapvals, mapvals2)[0,1]
70
+ logger.writeln(" CC_map(pos_model, pos_model_ncs{})= {:.4f}".format(op.id, cc))
71
+ if cc < cc_cutoff: ret = True
72
+ return ret
73
+ # check_symmetry_related_map_values()
74
+
75
+ def half2full(map_h1, map_h2):
76
+ assert map_h1.shape == map_h2.shape
77
+ assert map_h1.unit_cell == map_h2.unit_cell
78
+ tmp = (map_h1.array + map_h2.array)/2.
79
+ gr = gemmi.FloatGrid(tmp, map_h1.unit_cell, map_h1.spacegroup)
80
+ return gr
81
+ # half2full()
82
+
83
+ def nyquist_resolution(map_grid):
84
+ grid_shape = map_grid.shape
85
+ rec_cell = map_grid.unit_cell.reciprocal().parameters
86
+ resolutions = [2./rec_cell[i]/grid_shape[i] for i in (0,1,2)]
87
+ return max(resolutions)
88
+ # nyquist_resolution()
89
+
90
+ def sharpen_mask_unsharpen(maps, mask, d_min, b=None):
91
+ assert len(maps) < 3
92
+ if b is None and len(maps) != 2:
93
+ raise RuntimeError("Cannot determine sharpening")
94
+
95
+ hkldata = mask_and_fft_maps(maps, d_min)
96
+ normalizer = numpy.ones(len(hkldata.df.index))
97
+ if len(maps) == 2:
98
+ labs = ["F_map1", "F_map2"]
99
+ else:
100
+ labs = ["FP"]
101
+
102
+ # 1. Sharpen
103
+ if b is None:
104
+ hkldata.setup_relion_binning("ml")
105
+ calc_noise_var_from_halfmaps(hkldata)
106
+ logger.writeln("""$TABLE: Normalizing before masking:
107
+ $GRAPHS: ln(Mn(|F|)) :A:1,2:
108
+ : Normalizer :A:1,3:
109
+ : FSC(full) :A:1,4:
110
+ $$ 1/resol^2 ln(Mn(|F|)) normalizer FSC $$
111
+ $$""")
112
+ for i_bin, idxes in hkldata.binned("ml"):
113
+ bin_d_min = hkldata.binned_df["ml"].d_min[i_bin]
114
+ Fo = hkldata.df.FP.to_numpy()[idxes]
115
+ FSCfull = hkldata.binned_df["ml"].FSCfull[i_bin]
116
+ sig_fo = numpy.std(Fo)
117
+ if FSCfull > 0:
118
+ n_fo = sig_fo * numpy.sqrt(FSCfull)
119
+ else:
120
+ n_fo = sig_fo # XXX not a right way
121
+
122
+ normalizer[idxes] = n_fo
123
+ for lab in labs: hkldata.df.loc[idxes, lab] /= n_fo
124
+ logger.writeln("{:.4f} {:.2f} {:.3f} {:.4f}".format(1/bin_d_min**2,
125
+ numpy.log(numpy.average(numpy.abs(Fo))),
126
+ n_fo, FSCfull))
127
+
128
+ logger.writeln("$$")
129
+
130
+ else:
131
+ logger.writeln("Sharpening B before masking= {}".format(b))
132
+ normalizer[:] = hkldata.debye_waller_factors(b_iso=b)
133
+ for lab in labs: hkldata.df[lab] /= normalizer
134
+
135
+ # 2. Mask, FFT, and unsharpen
136
+ for lab in labs:
137
+ m = hkldata.fft_map(lab, grid_size=mask.shape)
138
+ m.array[:] *= mask
139
+ #write_ccp4_map("debug_{}.ccp4".format(lab), new_maps[-1][0])
140
+ rg = gemmi.transform_map_to_f_phi(m)
141
+ hkldata.df[lab] = rg.get_value_by_hkl(hkldata.miller_array()) * normalizer
142
+
143
+ # TODO can return here for most use cases?
144
+
145
+ new_maps = []
146
+ for i, lab in enumerate(labs):
147
+ m = hkldata.fft_map(lab, grid_size=mask.shape)
148
+ new_maps.append([m]+maps[i][1:])
149
+
150
+ return new_maps
151
+ # sharpen_mask_unsharpen()
152
+
153
+ def mask_and_fft_maps(maps, d_min, mask=None, with_000=True):
154
+ assert len(maps) <= 2
155
+ hkldata = None
156
+ for i, m in enumerate(maps):
157
+ if len(maps) == 2:
158
+ lab = "F_map{}".format(i+1)
159
+ else:
160
+ lab = "FP"
161
+ g = m[0]
162
+ if mask is not None:
163
+ g.array[:] *= mask
164
+ f_grid = gemmi.transform_map_to_f_phi(g)
165
+ if hkldata is None:
166
+ asudata = f_grid.prepare_asu_data(dmin=d_min, with_000=with_000)
167
+ hkldata = hkl.hkldata_from_asu_data(asudata, lab)
168
+ else:
169
+ hkldata.df[lab] = f_grid.get_value_by_hkl(hkldata.miller_array())
170
+
171
+ if len(maps) == 2:
172
+ hkldata.df["FP"] = (hkldata.df.F_map1 + hkldata.df.F_map2)/2.
173
+
174
+ return hkldata
175
+ # mask_and_fft_maps()
176
+
177
+ def calc_noise_var_from_halfmaps(hkldata):
178
+ hkldata.binned_df["ml"]["var_noise"] = 0.
179
+ hkldata.binned_df["ml"]["var_signal"] = 0.
180
+ hkldata.binned_df["ml"]["FSCfull"] = 0.
181
+
182
+ logger.writeln("Bin Ncoeffs d_max d_min FSChalf var.noise")
183
+ for i_bin, idxes in hkldata.binned("ml"):
184
+ bin_d_min = hkldata.binned_df["ml"].d_min[i_bin]
185
+ bin_d_max = hkldata.binned_df["ml"].d_max[i_bin]
186
+
187
+ sel1 = hkldata.df.F_map1.to_numpy()[idxes]
188
+ sel2 = hkldata.df.F_map2.to_numpy()[idxes]
189
+
190
+ if sel1.size < 3:
191
+ logger.writeln("WARNING: skipping bin {} with size= {}".format(i_bin, sel1.size))
192
+ continue
193
+
194
+ fsc = numpy.real(numpy.corrcoef(sel1, sel2)[1,0])
195
+ varn = numpy.var(sel1-sel2)/4
196
+ vart = numpy.var(sel1+sel2)/4
197
+ logger.writeln("{:3d} {:7d} {:7.3f} {:7.3f} {:.4f} {:e}".format(i_bin, sel1.size, bin_d_max, bin_d_min,
198
+ fsc, varn))
199
+ hkldata.binned_df["ml"].loc[i_bin, "var_noise"] = varn
200
+ hkldata.binned_df["ml"].loc[i_bin, "var_signal"] = vart-varn
201
+ hkldata.binned_df["ml"].loc[i_bin, "FSCfull"] = 2*fsc/(1+fsc)
202
+ # calc_noise_var_from_halfmaps()
203
+
204
+ def write_ccp4_map(filename, array, cell=None, sg=None, mask_for_extent=None, mask_threshold=0.5, mask_padding=5,
205
+ grid_start=None, grid_shape=None, update_cell=False):
206
+ """
207
+ - If mask_for_extent is set: grid_shape is ignored
208
+ - grid_shape must be specified together with grid_start.
209
+ - mask_padding unit: px
210
+ """
211
+ logger.writeln("Writing map file: {}".format(filename))
212
+ ccp4 = gemmi.Ccp4Map()
213
+
214
+ if type(array) == numpy.ndarray:
215
+ # TODO check dtype
216
+ if sg is None: sg = gemmi.SpaceGroup(1)
217
+ ccp4.grid = gemmi.FloatGrid(array, cell, sg)
218
+ else:
219
+ # TODO check type
220
+ ccp4.grid = array
221
+ if cell is not None: ccp4.grid.set_unit_cell(cell)
222
+ if sg is not None: ccp4.grid.spacegroup = sg
223
+
224
+ ccp4.update_ccp4_header(2, True) # float, update stats
225
+
226
+ if mask_for_extent is not None: # want to crop part of map using mask
227
+ tmp = numpy.where(mask_for_extent.array > mask_threshold)
228
+ if grid_start is not None:
229
+ grid_start = numpy.array(grid_start)[:,None]
230
+ shape = numpy.array(ccp4.grid.shape)[:,None]
231
+ tmp -= grid_start
232
+ tmp += (shape*numpy.floor(1-tmp/shape)).astype(int) + grid_start
233
+
234
+ l = [(min(x)-mask_padding, max(x)+mask_padding) for x in tmp]
235
+ grid_start = [l[i][0] for i in range(3)]
236
+ grid_shape = [l[i][1]-l[i][0]+1 for i in range(3)]
237
+
238
+ if grid_start is not None: # want to change origin
239
+ new_shape = ccp4.grid.shape if grid_shape is None else grid_shape
240
+ logger.writeln(" setting starting grid: {} {} {}".format(*grid_start))
241
+ logger.writeln(" setting new shape: {} {} {}".format(*new_shape))
242
+
243
+ new_cell = ccp4.grid.unit_cell
244
+ if update_cell:
245
+ abc = [new_cell.parameters[i]*new_shape[i]/ccp4.grid.shape[i] for i in range(3)]
246
+ new_cell = gemmi.UnitCell(abc[0], abc[1], abc[2],
247
+ new_cell.alpha, new_cell.beta, new_cell.gamma)
248
+ logger.writeln(" setting new cell: {:6.2f} {:6.2f} {:6.2f} {:5.1f} {:5.1f} {:5.1f}".format(*new_cell.parameters))
249
+ cell_grid = new_shape
250
+ else:
251
+ cell_grid = ccp4.grid.shape
252
+
253
+ new_grid = gemmi.FloatGrid(ccp4.grid.get_subarray(grid_start, new_shape),
254
+ new_cell,
255
+ ccp4.grid.spacegroup)
256
+ ccp4 = gemmi.Ccp4Map()
257
+ ccp4.grid = new_grid
258
+ ccp4.update_ccp4_header(2, True) # float, update stats
259
+ for i in range(3):
260
+ ccp4.set_header_i32(5+i, grid_start[i])
261
+ ccp4.set_header_i32(1+i, new_shape[i])
262
+ ccp4.set_header_i32(8+i, cell_grid[i])
263
+
264
+ ccp4.write_ccp4_map(filename)
265
+ # write_ccp4_map()
266
+
267
+ def optimize_peak(grid, ini_pos):
268
+ logger.writeln("Finding peak using interpolation..")
269
+ x = grid.unit_cell.fractionalize(ini_pos)
270
+ logger.writeln(" x0: [{}, {}, {}]".format(*x.tolist()))
271
+ logger.writeln(" f0: {}".format(-grid.interpolate_value(x, order=3)))
272
+
273
+ res = scipy.optimize.minimize(fun=lambda x:-grid.interpolate_value(gemmi.Fractional(*x), order=3),
274
+ x0=x.tolist(),
275
+ jac=lambda x:-numpy.array(grid.tricubic_interpolation_der(gemmi.Fractional(*x))[1:])
276
+ )
277
+ logger.writeln(str(res))
278
+ final_pos = grid.unit_cell.orthogonalize(gemmi.Fractional(*res.x))
279
+ logger.writeln(" Move from initial: [{:.3f}, {:.3f}, {:.3f}] A".format(*(final_pos-ini_pos).tolist()))
280
+ return final_pos
281
+ # optimize_peak()
282
+
283
+ def raised_cosine_kernel(r1, dr=2):
284
+ assert r1 > 2
285
+ assert dr >= 0
286
+ assert r1 > dr
287
+
288
+ boxsize = 2 * r1 + 1
289
+ x, y, z = numpy.meshgrid(range(boxsize), range(boxsize), range(boxsize))
290
+ cen = boxsize // 2
291
+ r0 = r1 - dr
292
+ d = numpy.sqrt((x-cen)**2+(y-cen)**2+(z-cen)**2)
293
+ kern = 0.5 + 0.5 * numpy.cos(numpy.pi * (d - r0) / (r1 - r0))
294
+ kern[d<=r0] = 1
295
+ kern[d>=r1] = 0
296
+ kern /= numpy.sum(kern)
297
+ return kern
298
+ # raised_cosine_kernel()
299
+
300
+ def fft_convolve_simple(in1, in2):
301
+ if numpy.iscomplexobj(in1):
302
+ return numpy.fft.ifftn(numpy.fft.fftn(in1) * numpy.fft.fftn(in2))
303
+ else:
304
+ return numpy.fft.irfftn(numpy.fft.rfftn(in1) * numpy.fft.rfftn(in2))
305
+
306
+ def local_var(grid, kernel, method="scipy"):
307
+ if method == "scipy":
308
+ convolve = lambda x, y: scipy.signal.fftconvolve(x, y, "same")
309
+ else:
310
+ convolve = fft_convolve_simple
311
+ mean_x2 = convolve(numpy.abs(grid.array)**2, kernel)
312
+ mean_x = convolve(grid.array, kernel)
313
+ var_x = new_grid_like(grid)
314
+ var_x.array[:] = mean_x2 - numpy.abs(mean_x)**2
315
+ var_x.array[var_x.array<0] = 0 # due to loss of significance
316
+ return var_x
317
+ # local_var()
318
+
319
+ def local_cc(map1, map2, kernel, method="scipy"):
320
+ # maps may be real space grid or reciprocal space grid
321
+ if method == "scipy":
322
+ convolve = lambda x, y: scipy.signal.fftconvolve(x, y, "same")
323
+ else:
324
+ convolve = fft_convolve_simple
325
+
326
+ localcc = new_grid_like(map1)
327
+ mean_1_sqr = convolve(numpy.abs(map1.array)**2, kernel)
328
+ mean_2_sqr = convolve(numpy.abs(map2.array)**2, kernel)
329
+ mean_1 = convolve(map1.array, kernel)
330
+ mean_2 = convolve(map2.array, kernel)
331
+ mean_12 = convolve(map1.array * numpy.conj(map2.array), kernel)
332
+ var_1 = mean_1_sqr - numpy.abs(mean_1)**2
333
+ var_2 = mean_2_sqr - numpy.abs(mean_2)**2
334
+ bad_sel = (var_1 <= 0) | (var_2 <= 0)
335
+ var_1_2 = var_1 * var_2
336
+ var_1_2[bad_sel] = 1 # to avoid division by zero
337
+ covar_12 = mean_12 - mean_1 * numpy.conj(mean_2)
338
+ localcc.array[:] = covar_12 / numpy.sqrt(var_1_2)
339
+ localcc.array[bad_sel] = 0.
340
+ if numpy.iscomplexobj(localcc.array):
341
+ localcc.array[:] = localcc.array.real
342
+ localcc.array[localcc.array > 1] = 1.
343
+ localcc.array[localcc.array < -1] = -1.
344
+ return localcc
345
+ # local_cc()