Coverage for PyFHD/gridding/visibility_grid.py: 92%

250 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-07-01 10:58 +0800

1import numpy as np 

2from numpy.typing import NDArray 

3from PyFHD.gridding.gridding_utils import ( 

4 interpolate_kernel, 

5 baseline_grid_locations, 

6 grid_beam_per_baseline, 

7 conjugate_mirror, 

8) 

9from PyFHD.pyfhd_tools.pyfhd_utils import weight_invert, rebin, l_m_n, idl_argunique 

10from logging import Logger 

11import h5py 

12 

13 

14def visibility_grid( 

15 visibility: NDArray[np.complex128], 

16 vis_weights: NDArray[np.float64], 

17 obs: dict, 

18 psf: dict | h5py.File, 

19 params: dict, 

20 polarization: int, 

21 pyfhd_config: dict, 

22 logger: Logger, 

23 uniform_flag: bool = False, 

24 no_conjugate: bool = False, 

25 model: NDArray[np.complex128] | None = None, 

26 fi_use: NDArray[np.integer] | None = None, 

27 bi_use: NDArray[np.integer] | None = None, 

28 verbose_logging=True, 

29) -> dict: 

30 """ 

31 Put visibilities on a discrete, hyperresolved 2D plane in {u,v}-space with the Fourier-transform of the 

32 beam sensitivity as the kernel (or spreading function). This can done per frequency to create 3D {u,v,f} 

33 cubes that can generate power spectrum statistics, or this can be done once for all frequencies to create 

34 a single 2D {u,v} plane for continuum images. These {u,v} planes are the slant-orthographic projection of 

35 the sky when Fourier transformed. 

36 

37 Gridding is done for calibrated data visibilities, (optionally) simulated model visibilities, weights, and 

38 variances. Weights and variances are crucial for propogating uncertainty estimates in the power spectrum 

39 space and for properly weighted images. The model is crucial for subtraction. 

40 

41 The kernel is a extremely hyperresolved look-up table, which is (optionally) interpolated even further. 

42 Since the {u,v} pixels are discrete and the baseline locations are not, the kernel will populate the pixels 

43 in a unique way for each individual baseline. This code is optimized to provide the best estimate for each 

44 baseline whilst maintaining speed. 

45 

46 Parameters 

47 ---------- 

48 visibility : NDArray[np.complex128] 

49 Calibrated and flagged data visibilities 

50 vis_weights : NDArray[np.float64] 

51 Weights (flags) of the visibilities 

52 obs : dict 

53 Observation metadata dictionary 

54 psf : dict | h5py.File 

55 Beam metadata dictionary 

56 params : dict 

57 Visibility metadata dictionary 

58 polarization : int 

59 Index of the current polarization 

60 pyfhd_config : dict 

61 PyFHD's configuration dictionary containing all the options for a run 

62 logger : Logger 

63 PyFHD's logger 

64 uniform_flag : bool, optional 

65 Grid a number count for contributing baselines per pixel, by default False 

66 no_conjugate : bool, optional 

67 Do not perform the conjugate mirror to fill half of the {u,v} plane, by default False 

68 model : NDArray[np.complex128] | None, optional 

69 Simulated model visibilites, by default None 

70 fi_use : NDArray[np.integer] | None, optional 

71 Frequency index array for gridding, i.e. gridding all frequencies for continuum images, by default None 

72 bi_use : NDArray[np.integer] | None, optional 

73 Baseline index array for gridding, i.e even vs odd time stamps, by default None 

74 verbose_logging : bool, optional 

75 If True, will log the gridding process, by default True 

76 Set to False if you're doing gridding per frequency (such as when creating HEALPIX files). 

77 

78 Returns 

79 ------- 

80 gridding_dict : dict 

81 A dictionary with all the gridded {u,v} planes, updated observation metadata dic, and the number of 

82 visibilties that where gridded. 

83 

84 Raises 

85 ------ 

86 ValueError 

87 Raised in the case the model provided was not a NumPy Array when a model is not None 

88 """ 

89 

90 # Get information from the data structures 

91 dimension = int(obs["dimension"]) 

92 elements = int(obs["elements"]) 

93 interp_flag = pyfhd_config["interpolate_kernel"] 

94 n_vis_arr = obs["nf_vis"] 

95 if fi_use is None: 

96 fi_use = np.nonzero(obs["baseline_info"]["freq_use"])[0] 

97 n_f_use = fi_use.size 

98 freq_bin_i = obs["baseline_info"]["fbin_i"] 

99 freq_bin_i = freq_bin_i[fi_use] 

100 

101 # For each unflagged baseline, get the minimum contributing pixel number for gridding 

102 # and the 2D derivatives for bilinear interpolation 

103 baselines_dict = baseline_grid_locations( 

104 obs, 

105 psf, 

106 params, 

107 vis_weights, 

108 logger, 

109 bi_use=bi_use, 

110 fi_use=fi_use, 

111 mask_mirror_indices=pyfhd_config["mask_mirror_indices"], 

112 interp_flag=interp_flag, 

113 ) 

114 # Extract data from the returned dictionary 

115 bin_n = baselines_dict["bin_n"] 

116 bin_i = baselines_dict["bin_i"] 

117 n_bin_use = baselines_dict["n_bin_use"] 

118 ri = baselines_dict["ri"] 

119 xmin = baselines_dict["xmin"] 

120 ymin = baselines_dict["ymin"] 

121 x_offset = baselines_dict["x_offset"] 

122 y_offset = baselines_dict["y_offset"] 

123 if bi_use is None: 

124 bi_use = baselines_dict["bi_use"] 

125 if interp_flag: 

126 dx0dy0_arr = baselines_dict["dx0dy0_arr"] 

127 dx0dy1_arr = baselines_dict["dx0dy1_arr"] 

128 dx1dy0_arr = baselines_dict["dx1dy0_arr"] 

129 dx1dy1_arr = baselines_dict["dx1dy1_arr"] 

130 

131 # Instead of checking the visibilitity pointer we just take the vis_inds_use from visibility 

132 rows, cols = np.meshgrid(fi_use, bi_use) 

133 vis_arr_use = visibility[rows, cols].T 

134 # Model_flag has been removed in favor of just the model taking advantage that the model default is None 

135 # If it has been specified at all with anything other than None or False, then it should be a numpy array 

136 # if it isn't exit 

137 if model is not None: 

138 if isinstance(model, np.ndarray): 138 ↛ 142line 138 didn't jump to line 142 because the condition on line 138 was always true

139 model_use = model[rows, cols].T 

140 model_return = np.zeros((elements, dimension), dtype=np.complex128) 

141 else: 

142 raise ValueError( 

143 "Your model must be a numpy array when used as an argument" 

144 ) 

145 

146 # Now with the information we need, retrieve more data from the structures 

147 frequency_array = obs["baseline_info"]["freq"] 

148 frequency_array = frequency_array[fi_use] 

149 psf_dim = psf["dim"] 

150 psf_resolution = psf["resolution"] 

151 group_arr = psf["id"] 

152 if isinstance(psf, h5py.File): 

153 psf_dim = psf_dim[0] 

154 psf_resolution = psf_resolution[0] 

155 group_arr = group_arr[:] 

156 n_baselines = obs["n_baselines"] 

157 n_samples = obs["n_time"] 

158 # New group_arr code that is consistent with the FHD version 

159 # We go upto n_baselines in the case we have less baselines in the 

160 # observation than the transferred in beam. In most cases we're only 

161 # using one beam so this is fine, you might have issues if you're using 

162 # many beams across the baselines with this code. It passes the tests where 

163 # beams have been done per baseline so if you get an error, sorry! 

164 group_arr_baselines = np.min([n_baselines, group_arr.shape[-1]]) 

165 group_arr = group_arr[polarization, freq_bin_i, :group_arr_baselines] 

166 # REBIN in IDL when expanding dimensions repeats 2D arrays after expanding 

167 group_arr = np.expand_dims(rebin(group_arr, (n_f_use, group_arr_baselines)), axis=0) 

168 group_arr = np.repeat(group_arr, n_samples, axis=0) 

169 group_arr = np.reshape(group_arr, (n_f_use, n_samples * group_arr_baselines)) 

170 n_freq_use = frequency_array.size 

171 psf_dim2 = 2 * psf_dim 

172 psf_dim3 = psf_dim**2 

173 bi_use_reduced = bi_use % n_baselines 

174 

175 # Flags have been defined in the function definition 

176 # Instead of reading the flags and then setting them. 

177 

178 if pyfhd_config["beam_per_baseline"]: 

179 # Initialization for gridding operation via a low-res beam kernel, calculated per 

180 # baseline using offsets from image-space delays 

181 uu = params["uu"][bi_use] 

182 vv = params["vv"][bi_use] 

183 ww = params["ww"][bi_use] 

184 x = (np.arange(dimension) - dimension / 2) * obs["kpix"] 

185 y = x.copy() 

186 psf_intermediate_res = np.min( 

187 [np.ceil(np.sqrt(psf_resolution) / 2) * 2, psf_resolution] 

188 ) 

189 psf_image_dim = psf["image_info"]["psf_image_dim"] 

190 if isinstance(psf_image_dim, h5py.Dataset): 190 ↛ 191line 190 didn't jump to line 191 because the condition on line 190 was never true

191 psf_image_dim = psf_image_dim[0] 

192 image_bot = int(-(psf_dim / 2) * psf_intermediate_res + psf_image_dim / 2) 

193 image_top = int( 

194 (psf_dim * psf_resolution - 1) 

195 - (psf_dim / 2) * psf_intermediate_res 

196 + psf_image_dim / 2 

197 ) 

198 l_mode, m_mode, n_tracked = l_m_n(obs, psf) 

199 n_tracked = np.zeros_like(n_tracked) 

200 

201 # Initialize uv-arrays 

202 image_uv = np.zeros((elements, dimension), dtype=np.complex128) 

203 weights = np.zeros((elements, dimension), dtype=np.complex128) 

204 variance = np.zeros((elements, dimension)) 

205 uniform_filter = np.zeros((elements, dimension)) 

206 

207 # If the uniform gridding has been activated we need to activate the uniform filter and switch off mapping if it has been activated 

208 if pyfhd_config["grid_uniform"]: 

209 uniform_flag = True 

210 

211 conj_i = np.where(params["vv"][bi_use] > 0)[0] 

212 if conj_i.size > 0: 212 ↛ 222line 212 didn't jump to line 222 because the condition on line 212 was always true

213 if pyfhd_config["beam_per_baseline"]: 

214 uu[conj_i] = -uu[conj_i] 

215 vv[conj_i] = -vv[conj_i] 

216 ww[conj_i] = -ww[conj_i] 

217 vis_arr_use[:, conj_i] = np.conj(vis_arr_use[:, conj_i]) 

218 if model is not None: 

219 model_use[:, conj_i] = np.conj(model_use[:, conj_i]) 

220 

221 # Return if all baselines have been flagged 

222 if n_bin_use == 0: 222 ↛ 223line 222 didn't jump to line 223 because the condition on line 222 was never true

223 logger.error("All data has been flagged") 

224 return {} 

225 

226 n_vis = np.sum(bin_n) 

227 for fi in range(n_f_use): 

228 n_vis_arr[fi_use[fi]] = np.sum(xmin[fi, :] > 0) 

229 obs["nf_vis"] = n_vis_arr 

230 

231 init_arr = np.zeros([psf_dim2, psf_dim2], dtype=np.complex128) 

232 

233 arr_type = init_arr.dtype 

234 if pyfhd_config["grid_spectral"]: 

235 # Spectral B and Spectral D shouldn't reference each other just in case 

236 spectral_A = np.zeros([elements, dimension], dtype=np.complex128) 

237 spectral_B = np.zeros([elements, dimension]) 

238 spectral_D = np.zeros([elements, dimension]) 

239 if model is not None: 239 ↛ 240line 239 didn't jump to line 240 because the condition on line 239 was never true

240 spectral_model_A = np.zeros([elements, dimension], dtype=np.complex128) 

241 

242 frequency_cache: dict[int, np.ndarray] = {} 

243 

244 for bi in range(n_bin_use): 

245 # Cycle through sets of visibilities which contribute to the same data/model uv-plane pixels, and perform 

246 # the gridding operation per set using each visibilities' hyperresolved kernel 

247 

248 # Select the indices of the visibilities which contribute to the same data/model uv-plane pixels 

249 inds = ri[ri[bin_i[bi]] : ri[bin_i[bi] + 1]] 

250 ind0 = inds[0] 

251 

252 # Select the pixel offsets of the hyperresolution uv-kernel of the selected visibilities 

253 x_off = x_offset.flat[inds] 

254 y_off = y_offset.flat[inds] 

255 

256 # Since all selected visibilities have the same minimum x,y pixel they contribute to, 

257 # reduce the array 

258 xmin_use = xmin.flat[ind0] 

259 ymin_use = ymin.flat[ind0] 

260 

261 # Find the frequency group per index 

262 freq_i = inds % n_freq_use 

263 fbin = freq_bin_i[freq_i] 

264 

265 # Calculate the number of selected visibilities and their baseline index 

266 vis_n = bin_n[bin_i[bi]] 

267 baseline_inds = bi_use_reduced[((inds / n_f_use) % n_baselines).astype(int)] 

268 

269 if interp_flag: 

270 # Calculate the interpolated kernel on the uv-grid given the derivatives to baseline locations 

271 # and the hyperresolved pre-calculated beam kernel 

272 

273 # Select the 2D derivatives to baseline locations 

274 dx1dy1 = dx1dy1_arr.flat[inds] 

275 dx1dy0 = dx1dy0_arr.flat[inds] 

276 dx0dy1 = dx0dy1_arr.flat[inds] 

277 dx0dy0 = dx0dy0_arr.flat[inds] 

278 

279 # Select the model/data visibility values of the set, each with a weight of 1 

280 if model is not None: 

281 model_box = model_use.flat[inds] 

282 vis_box = vis_arr_use.flat[inds] 

283 psf_weight = np.ones(vis_n) 

284 

285 box_matrix = np.zeros((vis_n, psf_dim3), dtype=arr_type) 

286 for ii in range(vis_n): 

287 if fbin[ii] not in frequency_cache: 

288 to_interp = psf["beam_ptr"][polarization, fbin[ii]] 

289 frequency_cache[fbin[ii]] = to_interp 

290 else: 

291 to_interp = frequency_cache[fbin[ii]] 

292 # For each visibility, calculate the kernel values on the static uv-grid given the 

293 # hyperresolved kernel and an interpolation involving the derivatives 

294 box_matrix[ii] = interpolate_kernel( 

295 to_interp, 

296 x_off[ii], 

297 y_off[ii], 

298 dx0dy0[ii], 

299 dx1dy0[ii], 

300 dx0dy1[ii], 

301 dx1dy1[ii], 

302 ) 

303 else: 

304 # Calculate the beam kernel at each baseline location given the hyperresolved pre-calculated 

305 # beam kernel 

306 

307 # Calculate a unique index for each kernel location and kernel type in order to reduce 

308 # operations if there are repeats 

309 group_id = group_arr.flat[inds] 

310 group_max = np.max(group_id) + 1 

311 xyf_i = ( 

312 x_off + y_off * psf_resolution + fbin * psf_resolution**2 

313 ) * group_max + group_id 

314 

315 # Calculate the unique number of kernel locations/types 

316 xyf_si = xyf_i.argsort(kind="stable") 

317 xyf_i = np.sort(xyf_i) 

318 xyf_ui = idl_argunique(xyf_i) 

319 n_xyf_bin = xyf_ui.size 

320 

321 # There might be a better selection criteria to determine which is most efficient 

322 if vis_n > 1.1 * n_xyf_bin and not pyfhd_config["beam_per_baseline"]: 

323 # If there are any baselines which use the same beam kernel and the same discretized location 

324 # given the hyperresolution, then reduce the number of gridding operations to only 

325 # non-repeated baselines 

326 inds = inds[xyf_si] 

327 inds_use = xyf_si[xyf_ui] 

328 freq_i = freq_i[inds_use] 

329 

330 x_off = x_off[inds_use] 

331 y_off = y_off[inds_use] 

332 fbin = fbin[inds_use] 

333 baseline_inds = baseline_inds[inds_use] 

334 if n_xyf_bin > 1: 

335 xyf_ui0 = np.insert(xyf_ui[0 : n_xyf_bin - 1] + 1, 0, 0) 

336 else: 

337 xyf_ui0 = np.array([0]) 

338 psf_weight = xyf_ui - xyf_ui0 + 1 

339 

340 vis_box1 = vis_arr_use.flat[inds] 

341 vis_box = vis_box1.flat[xyf_ui] 

342 if model is not None: 

343 model_box1 = model_use.flat[inds] 

344 model_box = model_box1.flat[xyf_ui] 

345 

346 # For the baselines which map to the same pixels and use the same beam, 

347 # add the underlying data/model pixels such that the gridding operation 

348 # only needs to be performed once for the set 

349 if xyf_ui0.size == 1: 

350 repeat_i = np.array([0]) 

351 else: 

352 repeat_i = np.where(psf_weight > 1)[0] 

353 xyf_ui = xyf_ui[repeat_i] 

354 xyf_ui0 = xyf_ui0[repeat_i] 

355 for rep_ii in range(repeat_i.size): 

356 vis_box[repeat_i[rep_ii]] = np.sum( 

357 vis_box1[xyf_ui0[rep_ii] : xyf_ui[rep_ii] + 1] 

358 ) 

359 if model is not None: 

360 model_box[repeat_i[rep_ii]] = np.sum( 

361 model_box1[xyf_ui0[rep_ii] : xyf_ui[rep_ii] + 1] 

362 ) 

363 vis_n = n_xyf_bin 

364 else: 

365 # If there are not enough baselines which use the same beam kernel and discretized 

366 # location to warrent reduction, then perform the gridding operation per baseline 

367 if model is not None: 

368 model_box = model_use.flat[inds] 

369 vis_box = vis_arr_use.flat[inds] 

370 psf_weight = np.ones(vis_n) 

371 # IDL had integer / integer i.e. 2015 / 336 == 5, used flooring divider instead in python 

372 # ALso do take note that each were very close always within 0.01 of their next number. 

373 # e.g. 2015 / 336 = 5.99, should ceiling be be used instead? 

374 bt_index = inds // n_freq_use 

375 

376 box_matrix = np.zeros((vis_n, psf_dim3), dtype=arr_type) 

377 if pyfhd_config["beam_per_baseline"]: 377 ↛ 380line 377 didn't jump to line 380 because the condition on line 377 was never true

378 # Make the beams on the fly with corrective phases given the baseline location for each visibility 

379 # to the static uv-grid 

380 box_matrix = grid_beam_per_baseline( 

381 psf, 

382 pyfhd_config, 

383 logger, 

384 uu, 

385 vv, 

386 ww, 

387 l_mode, 

388 m_mode, 

389 n_tracked, 

390 frequency_array, 

391 x, 

392 y, 

393 xmin_use, 

394 ymin_use, 

395 freq_i, 

396 bt_index, 

397 polarization, 

398 image_bot, 

399 image_top, 

400 psf_dim3, 

401 box_matrix, 

402 vis_n, 

403 ) 

404 else: 

405 for ii in range(vis_n): 

406 if fbin[ii] not in frequency_cache: 

407 box_mat = psf["beam_ptr"][polarization, fbin[ii]] 

408 frequency_cache[fbin[ii]] = box_mat 

409 else: 

410 box_mat = frequency_cache[fbin[ii]] 

411 # For each visibility, calculate the kernel values on the static uv-grid given the 

412 # hyperresolved kernel 

413 box_matrix[ii, :] = box_mat[y_off[ii], x_off[ii]] 

414 

415 # Calculate the conjugate transpose (dagger) of the uv-pixels that the current beam kernel contributes to 

416 box_matrix_dag = np.conj(box_matrix) 

417 

418 if pyfhd_config["grid_spectral"]: 

419 term_A_box = np.dot( 

420 np.transpose(box_matrix_dag), np.transpose((freq_i * vis_box) / n_vis) 

421 ) 

422 term_B_box = np.dot( 

423 np.transpose(box_matrix_dag), np.transpose(freq_i / n_vis) 

424 ) 

425 term_D_box = np.dot( 

426 np.transpose(box_matrix_dag), np.transpose(freq_i**2 / n_vis) 

427 ) 

428 spectral_A[ 

429 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

430 ].flat += term_A_box 

431 spectral_B[ 

432 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

433 ].flat += term_B_box.real 

434 spectral_D[ 

435 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

436 ].flat += term_D_box.real 

437 # del(term_A_box, term_B_box, term_D_box) 

438 if model is not None: 438 ↛ 439line 438 didn't jump to line 439 because the condition on line 438 was never true

439 term_Am_box = np.dot( 

440 np.transpose(box_matrix_dag), 

441 np.transpose((freq_i * model_box) / n_vis), 

442 ) 

443 spectral_model_A[ 

444 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

445 ] += term_Am_box 

446 

447 if model is not None: 

448 # If model visibilities are being gridded, calculate the product of the model vis and the beam kernel 

449 # for all vis which contribute to the same static uv-pixels, and add to the static uv-plane 

450 

451 # Ensure model_box is flat, sometimes odd shapes can come in from metadata 

452 model_box = model_box.flatten() 

453 box_arr = np.dot( 

454 np.transpose(box_matrix_dag), np.transpose(model_box / n_vis) 

455 ) 

456 model_return[ 

457 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

458 ].flat += box_arr 

459 

460 # Calculate the product of the data vis and the beam kernel 

461 # for all vis which contribute to the same static uv-pixels, and add to the static uv-plane 

462 

463 # Ensure vis_box is flat, sometimes odd shapes can come in from metadata 

464 vis_box = vis_box.flatten() 

465 box_arr = np.dot(np.transpose(box_matrix_dag), vis_box / n_vis) 

466 image_uv[ 

467 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

468 ].flat += box_arr 

469 del box_arr 

470 

471 if pyfhd_config["grid_weights"]: 471 ↛ 481line 471 didn't jump to line 481 because the condition on line 471 was always true

472 # If weight visibilities are being gridded, calculate the product the weight (1 per vis) and the beam kernel 

473 # for all vis which contribute to the same static uv-pixels, and add to the static uv-plane 

474 wts_box = np.dot( 

475 np.transpose(box_matrix_dag), np.transpose(psf_weight / n_vis) 

476 ) 

477 weights[ 

478 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

479 ].flat += wts_box 

480 

481 if pyfhd_config["grid_variance"]: 

482 # If variance visibilities are being gridded, calculate the product the weight (1 per vis) and the square 

483 # of the beam kernel for all vis which contribute to the same static uv-pixels, and add to the static uv-plane 

484 var_box = np.dot( 

485 np.transpose(np.abs(box_matrix_dag) ** 2), 

486 np.transpose(psf_weight / n_vis), 

487 ) 

488 variance[ 

489 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

490 ].flat += var_box 

491 

492 if uniform_flag: 

493 uniform_filter[ 

494 ymin_use : ymin_use + psf_dim, xmin_use : xmin_use + psf_dim 

495 ] += bin_n[bin_i[bi]] 

496 

497 if verbose_logging and ( 

498 (n_bin_use <= 10) 

499 or (bi in np.arange(n_bin_use // 10, n_bin_use, n_bin_use // 10)) 

500 ): 

501 logger.info( 

502 f"Gridding visibilities for baseline {bi} of {n_bin_use} for polarization {obs['pol_names'][polarization]}" 

503 ) 

504 

505 # Free Up Memory 

506 del ( 

507 vis_arr_use, 

508 xmin, 

509 ymin, 

510 ri, 

511 inds, 

512 x_offset, 

513 y_offset, 

514 bin_i, 

515 bin_n, 

516 frequency_cache, 

517 ) 

518 

519 if model is not None: 

520 del model_use 

521 

522 # Option to use spectral index information to scale the uv-plane 

523 if pyfhd_config["grid_spectral"]: 

524 spectral_uv = (spectral_A - n_vis * spectral_B * image_uv) * weight_invert( 

525 spectral_D - spectral_B**2 

526 ) 

527 if model is not None: 527 ↛ 528line 527 didn't jump to line 528 because the condition on line 527 was never true

528 spectral_model_uv = ( 

529 spectral_model_A - n_vis * spectral_B * model_return 

530 ) * weight_invert(spectral_D - spectral_B**2) 

531 if not no_conjugate: 531 ↛ 539line 531 didn't jump to line 539 because the condition on line 531 was always true

532 spectral_uv = (spectral_uv + conjugate_mirror(spectral_uv)) / 2 

533 if model is not None: 533 ↛ 534line 533 didn't jump to line 534 because the condition on line 533 was never true

534 spectral_model_uv = ( 

535 spectral_model_uv + conjugate_mirror(spectral_model_uv) 

536 ) / 2 

537 

538 # Option to apply a uniform weighted filter to all uv-planes 

539 if pyfhd_config["grid_uniform"]: 

540 filter_use = weight_invert(uniform_filter, threshold=1) 

541 wts_i = np.nonzero(filter_use) 

542 if wts_i[0].size > 0: 542 ↛ 545line 542 didn't jump to line 545 because the condition on line 542 was always true

543 filter_use /= np.mean(filter_use[wts_i]) 

544 else: 

545 filter_use /= np.mean(filter_use) 

546 image_uv *= weight_invert(filter_use) 

547 if pyfhd_config["grid_weights"]: 547 ↛ 549line 547 didn't jump to line 549 because the condition on line 547 was always true

548 weights *= weight_invert(filter_use) 

549 if pyfhd_config["grid_variance"]: 549 ↛ 551line 549 didn't jump to line 551 because the condition on line 549 was always true

550 variance *= weight_invert(filter_use) 

551 if model is not None: 551 ↛ 552line 551 didn't jump to line 552 because the condition on line 551 was never true

552 model_return *= weight_invert(filter_use) 

553 

554 if not no_conjugate: 

555 # The uv-plane is its own conjugate mirror about the x-axis, so fill in the rest of the uv-plane 

556 # using simple maths instead of extra gridding 

557 image_uv = (image_uv + conjugate_mirror(image_uv)) / 2 

558 if pyfhd_config["grid_weights"]: 558 ↛ 560line 558 didn't jump to line 560 because the condition on line 558 was always true

559 weights = (weights + conjugate_mirror(weights)) / 2 

560 if pyfhd_config["grid_variance"]: 

561 variance = (variance + conjugate_mirror(variance)) / 4 

562 if model is not None: 

563 model_return = (model_return + conjugate_mirror(model_return)) / 2 

564 if uniform_flag: 

565 uniform_filter = (uniform_filter + conjugate_mirror(uniform_filter)) / 2 

566 

567 # Arrange returns into a dictionary 

568 gridding_dict = { 

569 "image_uv": image_uv, 

570 "weights": weights, 

571 "variance": variance, 

572 "uniform_filter": uniform_filter, 

573 "obs": obs, 

574 "n_vis": n_vis, 

575 } 

576 

577 if pyfhd_config["grid_spectral"]: 

578 gridding_dict["spectral_uv"] = spectral_uv 

579 

580 if model is not None: 

581 gridding_dict["model_return"] = model_return 

582 if pyfhd_config["grid_spectral"]: 582 ↛ 583line 582 didn't jump to line 583 because the condition on line 582 was never true

583 gridding_dict["spectral_model_uv"] = spectral_model_uv 

584 

585 return gridding_dict