Commit 21ab7bf6 authored by Martin Reinecke's avatar Martin Reinecke
Browse files

Merge branch 'new_data_structure' into 'ducc0'

New data structure

See merge request !39
parents 7634e14d 392889d3
Pipeline #82315 passed with stages
in 12 minutes and 48 seconds
0.6.0:
- general:
- multi-threading improvements contributed by Peter Bell
- wgridder:
- new, smaller internal data structure
0.5.0:
- wgridder:
- internally used grid size is now chosen automatically, and the parameters
"nu" and "nv" are ignored; they will be removed in ducc1.
0.3.0: 0.3.0:
- general: - general:
- The package should now be installable from PyPI via pip even on MacOS. - The package should now be installable from PyPI via pip even on MacOS.
......
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright(C) 2019-2020 Max-Planck-Society
from time import time
import ducc0.wgridder as wgridder
import matplotlib.pyplot as plt
import numpy as np
def get_indices(name):
from os.path import join
from casacore.tables import table
with table(join(name, 'POLARIZATION'), readonly=True, ack=False) as t:
pol = list(t.getcol('CORR_TYPE')[0])
if set(pol) <= set([5, 6, 7, 8]):
ind = [pol.index(5), pol.index(8)]
else:
ind = [pol.index(9), pol.index(12)]
return ind
def determine_weighting(t):
fullwgt = False
weightcol = "WEIGHT"
try:
t.getcol("WEIGHT_SPECTRUM", startrow=0, nrow=1)
weightcol = "WEIGHT_SPECTRUM"
fullwgt = True
except:
pass
return fullwgt, weightcol
def extra_checks(t):
if len(set(t.getcol('FIELD_ID'))) != 1:
raise RuntimeError
if len(set(t.getcol('DATA_DESC_ID'))) != 1:
raise RuntimeError
def read_ms_i(name):
# Assumptions:
# - Only one field
# - Only one spectral window
# - Flag both LL and RR if one is flagged
from os.path import join
from casacore.tables import table
with table(join(name, 'SPECTRAL_WINDOW'), readonly=True, ack=False) as t:
freq = t.getcol('CHAN_FREQ')[0]
nchan = freq.shape[0]
ind = get_indices(name)
with table(name, readonly=True, ack=False) as t:
fullwgt, weightcol = determine_weighting(t)
extra_checks(t)
nrow = t.nrows()
active_rows = np.ones(nrow, dtype=np.bool)
active_channels = np.zeros(nchan, dtype=np.bool)
step = max(1, nrow//100) # how many rows to read in every step
# determine which subset of rows/channels we need to input
start = 0
while start < nrow:
stop = min(nrow, start+step)
tflags = t.getcol('FLAG', startrow=start, nrow=stop-start)
ncorr = tflags.shape[2]
tflags = tflags[..., ind]
tflags = np.any(tflags.astype(np.bool), axis=-1)
twgt = t.getcol(weightcol, startrow=start, nrow=stop-start)[..., ind]
twgt = 1/np.sum(1/twgt, axis=-1)
tflags[twgt==0] = True
active_rows[start:stop] = np.invert(np.all(tflags, axis=-1))
active_channels = np.logical_or(active_channels, np.invert(np.all(tflags, axis=0)))
start = stop
nrealrows, nrealchan = np.sum(active_rows), np.sum(active_channels)
start, realstart = 0, 0
vis = np.empty((nrealrows, nrealchan), dtype=np.complex64)
wgtshp = (nrealrows, nrealchan) if fullwgt else (nrealrows,)
wgt = np.empty(wgtshp, dtype=np.float32)
flags = np.empty((nrealrows, nrealchan), dtype=np.bool)
while start < nrow:
stop = min(nrow, start+step)
realstop = realstart+np.sum(active_rows[start:stop])
if realstop > realstart:
allrows = stop-start == realstop-realstart
tvis = t.getcol("DATA", startrow=start, nrow=stop-start)[..., ind]
tvis = np.sum(tvis, axis=-1)
if not allrows:
tvis = tvis[active_rows[start:stop]]
tvis = tvis[:, active_channels]
tflags = t.getcol('FLAG', startrow=start, nrow=stop-start)[..., ind]
tflags = np.any(tflags.astype(np.bool), axis=-1)
if not allrows:
tflags = tflags[active_rows[start:stop]]
tflags = tflags[:, active_channels]
twgt = t.getcol(weightcol, startrow=start, nrow=stop-start)[..., ind]
twgt = 1/np.sum(1/twgt, axis=-1)
if not allrows:
twgt = twgt[active_rows[start:stop]]
if fullwgt:
twgt = twgt[:, active_channels]
tflags[twgt==0] = True
vis[realstart:realstop] = tvis
wgt[realstart:realstop] = twgt
flags[realstart:realstop] = tflags
start, realstart = stop, realstop
uvw = t.getcol("UVW")[active_rows]
print('# Rows: {} ({} fully flagged)'.format(nrow, nrow-vis.shape[0]))
print('# Channels: {} ({} fully flagged)'.format(nchan, nchan-vis.shape[1]))
print('# Correlations: {}'.format(ncorr))
print('Full weights' if fullwgt else 'Row-only weights')
nflagged = np.sum(flags) + (nrow-nrealrows)*nchan + (nchan-nrealchan)*nrow
print("{} % flagged".format(nflagged/(nrow*nchan)*100))
freq = freq[active_channels]
# blow up wgt to the right dimensions if necessary
if not fullwgt:
wgt = np.broadcast_to(wgt.reshape((-1,1)), vis.shape)
return (np.ascontiguousarray(uvw),
np.ascontiguousarray(freq),
np.ascontiguousarray(vis),
np.ascontiguousarray(wgt) if fullwgt else wgt,
1-flags.astype(np.uint8))
def main():
# ms, fov_deg = '/home/martin/ms/supernovashell.55.7+3.4.spw0.ms', 2.
# ms, fov_deg = '/home/martin/ms/1052736496-averaged.ms', 45.
ms, fov_deg = '/home/martin/ms/1052735056.ms', 45.
# ms, fov_deg = '/home/martin/ms/cleaned_G330.89-0.36.ms', 2.
uvw, freq, vis, wgt, flags = read_ms_i(ms)
npixdirty = 1200
DEG2RAD = np.pi/180
pixsize = fov_deg/npixdirty*DEG2RAD
nthreads = 2
epsilon = 1e-4
print('Start gridding...')
do_wstacking = True
t0 = time()
dirty = wgridder.ms2dirty(uvw, freq, vis, wgt, npixdirty, npixdirty, pixsize,
pixsize, 0, 0, epsilon, do_wstacking, nthreads, verbosity=1, mask=flags)
print('Done')
t = time() - t0
print("{} s".format(t))
t0 = time()
_ = wgridder.dirty2ms(uvw, freq, dirty, wgt, pixsize,
pixsize, 0, 0, epsilon, do_wstacking, nthreads, verbosity=1, mask=flags)
print('Done')
t = time() - t0
print("{} s".format(t))
print("{} visibilities/thread/s".format(np.sum(wgt != 0)/nthreads/t))
plt.imshow(dirty.T, origin='lower')
plt.show()
if __name__ == "__main__":
main()
This diff is collapsed.
...@@ -69,8 +69,7 @@ def explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, xpixsize, ypixsize, ...@@ -69,8 +69,7 @@ def explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, xpixsize, ypixsize,
@pmp("nxdirty", (30, 128)) @pmp("nxdirty", (30, 128))
@pmp("nydirty", (128, 250)) @pmp("nydirty", (128, 250))
@pmp("ofactor", (0, 1.2, 1.5, 1.7, 2.0)) @pmp("nrow", (1, 2, 27))
@pmp("nrow", (2, 27))
@pmp("nchan", (1, 5)) @pmp("nchan", (1, 5))
@pmp("epsilon", (1e-1, 1e-3, 1e-5)) @pmp("epsilon", (1e-1, 1e-3, 1e-5))
@pmp("singleprec", (True, False)) @pmp("singleprec", (True, False))
...@@ -78,7 +77,7 @@ def explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, xpixsize, ypixsize, ...@@ -78,7 +77,7 @@ def explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, xpixsize, ypixsize,
@pmp("use_wgt", (True, False)) @pmp("use_wgt", (True, False))
@pmp("use_mask", (False, True)) @pmp("use_mask", (False, True))
@pmp("nthreads", (1, 2, 7)) @pmp("nthreads", (1, 2, 7))
def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, def test_adjointness_ms2dirty(nxdirty, nydirty, nrow, nchan, epsilon,
singleprec, wstacking, use_wgt, nthreads, use_mask): singleprec, wstacking, use_wgt, nthreads, use_mask):
if singleprec and epsilon < 5e-5: if singleprec and epsilon < 5e-5:
pytest.skip() pytest.skip()
...@@ -92,13 +91,7 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, ...@@ -92,13 +91,7 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon,
wgt = rng.uniform(0.9, 1.1, (nrow, nchan)) if use_wgt else None wgt = rng.uniform(0.9, 1.1, (nrow, nchan)) if use_wgt else None
mask = (rng.uniform(0, 1, (nrow, nchan)) > 0.5).astype(np.uint8) if use_mask else None mask = (rng.uniform(0, 1, (nrow, nchan)) > 0.5).astype(np.uint8) if use_mask else None
dirty = rng.random((nxdirty, nydirty))-0.5 dirty = rng.random((nxdirty, nydirty))-0.5
nu, nv = int(nxdirty*ofactor)+1, int(nydirty*ofactor)+1 nu = nv = 0
if nu & 1:
nu += 1
if nv & 1:
nv += 1
if ofactor == 0:
nu = nv = 0
if singleprec: if singleprec:
ms = ms.astype("c8") ms = ms.astype("c8")
dirty = dirty.astype("f4") dirty = dirty.astype("f4")
...@@ -116,7 +109,6 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, ...@@ -116,7 +109,6 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon,
@pmp('nxdirty', [16, 64]) @pmp('nxdirty', [16, 64])
@pmp('nydirty', [64]) @pmp('nydirty', [64])
@pmp('ofactor', [0, 1.2, 1.4, 1.7, 2])
@pmp("nrow", (1, 2, 27)) @pmp("nrow", (1, 2, 27))
@pmp("nchan", (1, 5)) @pmp("nchan", (1, 5))
@pmp("epsilon", (1e-2, 1e-3, 1e-4, 1e-7)) @pmp("epsilon", (1e-2, 1e-3, 1e-4, 1e-7))
...@@ -125,8 +117,8 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, ...@@ -125,8 +117,8 @@ def test_adjointness_ms2dirty(nxdirty, nydirty, ofactor, nrow, nchan, epsilon,
@pmp("use_wgt", (True,)) @pmp("use_wgt", (True,))
@pmp("use_mask", (True,)) @pmp("use_mask", (True,))
@pmp("nthreads", (1, 2, 7)) @pmp("nthreads", (1, 2, 7))
@pmp("fov", (1., 20.)) @pmp("fov", (0.001, 0.01, 0.1, 1., 20.))
def test_ms2dirty_against_wdft2(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, singleprec, wstacking, use_wgt, use_mask, fov, nthreads): def test_ms2dirty_against_wdft2(nxdirty, nydirty, nrow, nchan, epsilon, singleprec, wstacking, use_wgt, use_mask, fov, nthreads):
if singleprec and epsilon < 5e-5: if singleprec and epsilon < 5e-5:
pytest.skip() pytest.skip()
rng = np.random.default_rng(42) rng = np.random.default_rng(42)
...@@ -139,13 +131,7 @@ def test_ms2dirty_against_wdft2(nxdirty, nydirty, ofactor, nrow, nchan, epsilon, ...@@ -139,13 +131,7 @@ def test_ms2dirty_against_wdft2(nxdirty, nydirty, ofactor, nrow, nchan, epsilon,
wgt = rng.uniform(0.9, 1.1, (nrow, 1)) if use_wgt else None wgt = rng.uniform(0.9, 1.1, (nrow, 1)) if use_wgt else None
mask = (rng.uniform(0, 1, (nrow, nchan)) > 0.5).astype(np.uint8) if use_mask else None mask = (rng.uniform(0, 1, (nrow, nchan)) > 0.5).astype(np.uint8) if use_mask else None
wgt = np.broadcast_to(wgt, (nrow, nchan)) if use_wgt else None wgt = np.broadcast_to(wgt, (nrow, nchan)) if use_wgt else None
nu, nv = int(nxdirty*ofactor)+1, int(nydirty*ofactor)+1 nu = nv = 0
if nu & 1:
nu += 1
if nv & 1:
nv += 1
if ofactor == 0:
nu = nv = 0
if singleprec: if singleprec:
ms = ms.astype("c8") ms = ms.astype("c8")
if wgt is not None: if wgt is not None:
......
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright(C) 2020 Max-Planck-Society
import ducc0.wgridder as ng
import numpy as np
import pytest
from numpy.testing import assert_allclose
pmp = pytest.mark.parametrize
def _l2error(a, b):
return np.sqrt(np.sum(np.abs(a-b)**2)/np.maximum(np.sum(np.abs(a)**2),
np.sum(np.abs(b)**2)))
def explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, xpixsize, ypixsize,
apply_w):
speedoflight = 299792458.
x, y = np.meshgrid(*[-ss/2 + np.arange(ss) for ss in [nxdirty, nydirty]],
indexing='ij')
x *= xpixsize
y *= ypixsize
res = np.zeros((nxdirty, nydirty))
eps = x**2+y**2
if apply_w:
nm1 = -eps/(np.sqrt(1.-eps)+1.)
n = nm1+1
else:
nm1 = 0.
n = 1.
for row in range(ms.shape[0]):
for chan in range(ms.shape[1]):
phase = (freq[chan]/speedoflight *
(x*uvw[row, 0] + y*uvw[row, 1] - uvw[row, 2]*nm1))
if wgt is None:
res += (ms[row, chan]*np.exp(2j*np.pi*phase)).real
else:
res += (ms[row, chan]*wgt[row, chan]
* np.exp(2j*np.pi*phase)).real
return res/n
@pmp('nxdirty', [16, 64])
@pmp('nydirty', [64])
@pmp("nrow", (1, 100))
@pmp("nchan", (1, 7))
@pmp("epsilon", list(10.**np.linspace(-2.,-12.,100)))
@pmp("singleprec", (False,))
@pmp("wstacking", (True,))
@pmp("use_wgt", (True,))
@pmp("nthreads", (1, 10))
@pmp("fov", (10.,))
def test_ms2dirty_against_wdft2(nxdirty, nydirty, nrow, nchan, epsilon, singleprec, wstacking, use_wgt, fov, nthreads):
if singleprec and epsilon < 5e-5:
return
rng = np.random.default_rng()
pixsizex = fov*np.pi/180/nxdirty
pixsizey = fov*np.pi/180/nydirty*1.1
speedoflight, f0 = 299792458., 1e9
freq = f0 + np.arange(nchan)*(f0/nchan)
uvw = (rng.random((nrow, 3))-0.5)/(pixsizex*f0/speedoflight)
ms = rng.random((nrow, nchan))-0.5 + 1j*(rng.random((nrow, nchan))-0.5)
wgt = rng.random((nrow, 1)) if use_wgt else None
wgt = np.broadcast_to(wgt, (nrow, nchan)) if use_wgt else None
if singleprec:
ms = ms.astype("c8")
if wgt is not None:
wgt = wgt.astype("f4")
try:
dirty = ng.ms2dirty(uvw, freq, ms, wgt, nxdirty, nydirty, pixsizex,
pixsizey, 0, 0, epsilon, wstacking, nthreads, 0).astype("f8")
except:
# no matching kernel was found
pytest.skip()
ref = explicit_gridder(uvw, freq, ms, wgt, nxdirty, nydirty, pixsizex, pixsizey, wstacking)
assert_allclose(_l2error(dirty, ref), 0, atol=epsilon)
...@@ -36,8 +36,8 @@ auto None = py::none(); ...@@ -36,8 +36,8 @@ auto None = py::none();
template<typename T> py::array ms2dirty2(const py::array &uvw_, template<typename T> py::array ms2dirty2(const py::array &uvw_,
const py::array &freq_, const py::array &ms_, const py::object &wgt_, const py::object &mask_, const py::array &freq_, const py::array &ms_, const py::object &wgt_, const py::object &mask_,
size_t npix_x, size_t npix_y, double pixsize_x, double pixsize_y, size_t nu, size_t npix_x, size_t npix_y, double pixsize_x, double pixsize_y,
size_t nv, double epsilon, bool do_wgridding, size_t nthreads, double epsilon, bool do_wgridding, size_t nthreads,
size_t verbosity) size_t verbosity)
{ {
auto uvw = to_mav<double,2>(uvw_, false); auto uvw = to_mav<double,2>(uvw_, false);
...@@ -51,30 +51,30 @@ template<typename T> py::array ms2dirty2(const py::array &uvw_, ...@@ -51,30 +51,30 @@ template<typename T> py::array ms2dirty2(const py::array &uvw_,
auto dirty2 = to_mav<T,2>(dirty, true); auto dirty2 = to_mav<T,2>(dirty, true);
{ {
py::gil_scoped_release release; py::gil_scoped_release release;
ms2dirty(uvw,freq,ms,wgt2,mask2,pixsize_x,pixsize_y,nu,nv,epsilon, ms2dirty(uvw,freq,ms,wgt2,mask2,pixsize_x,pixsize_y,epsilon,
do_wgridding,nthreads,dirty2,verbosity); do_wgridding,nthreads,dirty2,verbosity);
} }
return move(dirty); return move(dirty);
} }
py::array Pyms2dirty(const py::array &uvw, py::array Pyms2dirty(const py::array &uvw,
const py::array &freq, const py::array &ms, const py::object &wgt, const py::array &freq, const py::array &ms, const py::object &wgt,
size_t npix_x, size_t npix_y, double pixsize_x, double pixsize_y, size_t nu, size_t npix_x, size_t npix_y, double pixsize_x, double pixsize_y, size_t /*nu*/,
size_t nv, double epsilon, bool do_wgridding, size_t nthreads, size_t /*nv*/, double epsilon, bool do_wgridding, size_t nthreads,
size_t verbosity, const py::object &mask) size_t verbosity, const py::object &mask)
{ {
if (isPyarr<complex<float>>(ms)) if (isPyarr<complex<float>>(ms))
return ms2dirty2<float>(uvw, freq, ms, wgt, mask, npix_x, npix_y, return ms2dirty2<float>(uvw, freq, ms, wgt, mask, npix_x, npix_y,
pixsize_x, pixsize_y, nu, nv, epsilon, do_wgridding, nthreads, verbosity); pixsize_x, pixsize_y, epsilon, do_wgridding, nthreads, verbosity);
if (isPyarr<complex<double>>(ms)) if (isPyarr<complex<double>>(ms))
return ms2dirty2<double>(uvw, freq, ms, wgt, mask, npix_x, npix_y, return ms2dirty2<double>(uvw, freq, ms, wgt, mask, npix_x, npix_y,
pixsize_x, pixsize_y, nu, nv, epsilon, do_wgridding, nthreads, verbosity); pixsize_x, pixsize_y, epsilon, do_wgridding, nthreads, verbosity);
MR_fail("type matching failed: 'ms' has neither type 'c8' nor 'c16'"); MR_fail("type matching failed: 'ms' has neither type 'c8' nor 'c16'");
} }
constexpr auto ms2dirty_DS = R"""( constexpr auto ms2dirty_DS = R"""(
Converts an MS object to dirty image. Converts an MS object to dirty image.
Parameters Parameters
========== ----------
uvw: np.array((nrows, 3), dtype=np.float64) uvw: np.array((nrows, 3), dtype=np.float64)
UVW coordinates from the measurement set UVW coordinates from the measurement set
freq: np.array((nchan,), dtype=np.float64) freq: np.array((nchan,), dtype=np.float64)
...@@ -90,14 +90,7 @@ npix_x, npix_y: int ...@@ -90,14 +90,7 @@ npix_x, npix_y: int
pixsize_x, pixsize_y: float pixsize_x, pixsize_y: float
angular pixel size (in radians) of the dirty image angular pixel size (in radians) of the dirty image
nu, nv: int nu, nv: int
dimensions of the (oversampled) intermediate uv grid obsolete, ignored
These values must be >= 1.2*the dimensions of the dirty image; tupical
oversampling values lie between 1.5 and 2.
Increasing the oversampling factor decreases the kernel support width
required for the desired accuracy, so it typically reduces run-time; on the
other hand, this will increase memory consumption.
If at least one of these two values is 0, the library will automatically
pick values that result in a fast computation.
epsilon: float epsilon: float
accuracy at which the computation should be done. Must be larger than 2e-13. accuracy at which the computation should be done. Must be larger than 2e-13.
If `ms` has type np.complex64, it must be larger than 1e-5. If `ms` has type np.complex64, it must be larger than 1e-5.
...@@ -114,14 +107,19 @@ mask: np.array((nrows, nchan), dtype=np.uint8), optional ...@@ -114,14 +107,19 @@ mask: np.array((nrows, nchan), dtype=np.uint8), optional
If present, only visibilities are processed for which mask!=0 If present, only visibilities are processed for which mask!=0
Returns Returns
======= -------
np.array((nxdirty, nydirty), dtype=float of same precision as `ms`) np.array((nxdirty, nydirty), dtype=float of same precision as `ms`)
the dirty image the dirty image
Notes
-----
The input arrays should be contiguous and in C memory order.
Other strides will work, but can degrade performance significantly.
)"""; )""";
template<typename T> py::array dirty2ms2(const py::array &uvw_, template<typename T> py::array dirty2ms2(const py::array &uvw_,
const py::array &freq_, const py::array &dirty_, const py::object &wgt_, const py::object &mask_, const py::array &freq_, const py::array &dirty_, const py::object &wgt_, const py::object &mask_,
double pixsize_x, double pixsize_y, size_t nu, size_t nv, double epsilon, double pixsize_x, double pixsize_y, double epsilon,
bool do_wgridding, size_t nthreads, size_t verbosity) bool do_wgridding, size_t nthreads, size_t verbosity)
{ {
auto uvw = to_mav<double,2>(uvw_, false); auto uvw = to_mav<double,2>(uvw_, false);
...@@ -135,29 +133,29 @@ template<typename T> py::array dirty2ms2(const py::array &uvw_, ...@@ -135,29 +133,29 @@ template<typename T> py::array dirty2ms2(const py::array &uvw_,
auto ms2 = to_mav<complex<T>,2>(ms, true); auto ms2 = to_mav<complex<T>,2>(ms, true);
{ {
py::gil_scoped_release release; py::gil_scoped_release release;
dirty2ms(uvw,freq,dirty,wgt2,mask2,pixsize_x,pixsize_y,nu,nv,epsilon, dirty2ms(uvw,freq,dirty,wgt2,mask2,pixsize_x,pixsize_y,epsilon,
do_wgridding,nthreads,ms2,verbosity); do_wgridding,nthreads,ms2,verbosity);
} }
return move(ms); return move(ms);
} }
py::array Pydirty2ms(const py::array &uvw, py::array Pydirty2ms(const py::array &uvw,
const py::array &freq, const py::array &dirty, const py::object &wgt, const py::array &freq, const py::array &dirty, const py::object &wgt,
double pixsize_x, double pixsize_y, size_t nu, size_t nv, double epsilon, double pixsize_x, double pixsize_y, size_t /*nu*/, size_t /*nv*/, double epsilon,
bool do_wgridding, size_t nthreads, size_t verbosity, const py::object &mask) bool do_wgridding, size_t nthreads, size_t verbosity, const py::object &mask)
{ {
if (isPyarr<float>(dirty)) if (isPyarr<float>(dirty))
return dirty2ms2<float>(uvw, freq, dirty, wgt, mask, return dirty2ms2<float>(uvw, freq, dirty, wgt, mask,
pixsize_x, pixsize_y, nu, nv, epsilon, do_wgridding, nthreads, verbosity); pixsize_x, pixsize_y, epsilon, do_wgridding, nthreads, verbosity);
if (isPyarr<double>(dirty)) if (isPyarr<double>(dirty))
return dirty2ms2<double>(uvw, freq, dirty, wgt, mask, return dirty2ms2<double>(uvw, freq, dirty, wgt, mask,
pixsize_x, pixsize_y, nu, nv, epsilon, do_wgridding, nthreads, verbosity); pixsize_x, pixsize_y, epsilon, do_wgridding, nthreads, verbosity);
MR_fail("type matching failed: 'dirty' has neither type 'f4' nor 'f8'"); MR_fail("type matching failed: 'dirty' has neither type 'f4' nor 'f8'");
} }
constexpr auto dirty2ms_DS = R"""( constexpr auto dirty2ms_DS = R"""(
Converts a dirty image to an MS object. Converts a dirty image to an MS object.
Parameters Parameters
========== ----------
uvw: np.array((nrows, 3), dtype=np.float64) uvw: np.array((nrows, 3), dtype=np.float64)
UVW coordinates from the measurement set UVW coordinates from the measurement set
freq: np.array((nchan,), dtype=np.float64) freq: np.array((nchan,), dtype=np.float64)
...@@ -171,14 +169,7 @@ wgt: np.array((nrows, nchan), same dtype as `dirty`), optional ...@@ -171,14 +169,7 @@ wgt: np.array((nrows, nchan), same dtype as `dirty`), optional
pixsize_x, pixsize_y: float pixsize_x, pixsize_y: float
angular pixel size (in radians) of the dirty image angular pixel size (in radians) of the dirty image
nu, nv: int nu, nv: int
dimensions of the (oversampled) intermediate uv grid obsolete, ignored
These values must be >= 1.2*the dimensions of the dirty image; tupical
oversampling values lie between 1.5 and 2.
Increasing the oversampling factor decreases the kernel support width
required for the desired accuracy, so it typically reduces run-time; on the
other hand, this will increase memory consumption.
If at least one of these two values is 0, the library will automatically
pick values that result in a fast computation.
epsilon: float epsilon: float
accuracy at which the computation should be done. Must be larger than 2e-13. accuracy at which the computation should be done. Must be larger than 2e-13.
If `dirty` has type np.float32, it must be larger than 1e-5. If `dirty` has type np.float32, it must be larger than 1e-5.
...@@ -195,9 +186,14 @@ mask: np.array((nrows, nchan), dtype=np.uint8), optional ...@@ -195,9 +186,14 @@ mask: np.array((nrows, nchan), dtype=np.uint8), optional
If present, only visibilities are processed for which mask!=0 If present, only visibilities are processed for which mask!=0
Returns Returns
======= -------
np.array((nrows, nchan,), dtype=complex of same precision as `dirty`) np.array((nrows, nchan,), dtype=complex of same precision as `dirty`)