From 8e327d70436071b234ab0fd383cb1b40152d4f22 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 3 Feb 2021 11:36:14 +0100 Subject: [PATCH 01/37] Implement mosaicing response --- demo/mosaicing.py | 104 +++++++++++++++++++++++++++ resolve/__init__.py | 7 +- resolve/likelihood.py | 19 +++-- resolve/mosaicing/__init__.py | 1 + resolve/mosaicing/sky_slicer.py | 124 ++++++++++++++++++++++++++++++++ resolve/observation.py | 2 + resolve/primary_beam.py | 14 ++++ resolve/response.py | 11 +++ 8 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 demo/mosaicing.py create mode 100644 resolve/mosaicing/__init__.py create mode 100644 resolve/mosaicing/sky_slicer.py diff --git a/demo/mosaicing.py b/demo/mosaicing.py new file mode 100644 index 00000000..3d0ee31d --- /dev/null +++ b/demo/mosaicing.py @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2019-2020 Max-Planck-Society +# Author: Philipp Arras + +import numpy as np +import matplotlib.pyplot as plt + +import nifty7 as ift +import resolve as rve + + +def main(): + rve.set_nthreads(1) + rve.set_wgridding(False) + diffusefluxlevel = 20 + + interobs = { + f"extended{ii}": rve.Observation.load( + f"/data/mosaicing/extended/extended_field{ii}.npz" + ) + for ii in range(5, 135) + # for ii in [50, 58, 59, 68, 69, 70, 88] + # for ii in [68, 69, 70] + } + rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) + + # Plot individual dirty images + for kk, vv in interobs.items(): + npix = 500 + fov = 1 * rve.ARCMIN2RAD + domain = ift.RGSpace([npix, npix], [fov / npix, fov / npix]) + R = rve.StokesIResponse(vv, domain) + ift.single_plot(R.adjoint(vv.vis * vv.weight), name=f"dirty{kk}.png") + + xs, ys = [], [] + for vv in interobs.values(): + xs.append(vv.direction.phase_center[0]) + ys.append(vv.direction.phase_center[1]) + xs, ys = np.array(xs), np.array(ys) + # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel + global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 + global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 + margin = 2 * rve.ARCMIN2RAD + plt.scatter(xs, ys, label="Extended") + plt.scatter(*global_phase_center_casa, label="Phase center CASA") + plt.scatter(*global_phase_center, label="Phase center resolve") + xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin + yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin + print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") + plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) + plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) + plt.legend() + plt.savefig("pointings.png") + plt.close() + + # Assert all frequencies are the same + freq = list(interobs.values())[0].freq + for vv in interobs.values(): + assert np.all(vv.freq == freq) + extended_blockage = np.array([10.7, 0.75]) # m + extended_blockage *= np.mean(freq) / rve.SPEEDOFLIGHT + beam_func = lambda x: rve.alma_beam_func(*extended_blockage, x) + beam_directions = { + kk: rve.BeamDirection( + obs.direction.phase_center[0] - global_phase_center[0], + obs.direction.phase_center[1] - global_phase_center[1], + beam_func, + 0.05, + ) + for kk, obs in interobs.items() + } + + npix = np.array([1000, 1000]) + fov = np.array([xfov, yfov]) + dom = ift.RGSpace(npix, fov / npix) + logsky = ift.SimpleCorrelatedField( + dom, diffusefluxlevel, (1, 0.1), (5, 1), (1.2, 0.4), (0.2, 0.2), (-2, 0.5) + ) + skyslicer = rve.SkySlicer(logsky.target, beam_directions) + ift.extra.check_linear_operator(skyslicer) + ift.single_plot( + skyslicer.adjoint(ift.full(skyslicer.target, 1.0)), + name="debug_skyslicer_adjoint.png", + ) + lh = rve.ImagingLikelihood(interobs, skyslicer @ logsky.exp()) + + plotter = rve.Plotter("png", "plots") + plotter.add("logsky", logsky) + plotter.add("power spectrum logsky", logsky.power_spectrum) + plotter.add_histogram( + "normalized residuals (original weights)", lh.normalized_residual + ) + ham = ift.StandardHamiltonian(lh) + fld = 0.1 * ift.from_random(lh.domain) + state = rve.MinimizationState(fld, []) + mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=10)) + state = rve.simple_minimize(ham, state.mean, 0, mini) + plotter.plot("stage1", state) + state.save("stage1") + # state = rve.MinimizationState.load("stage1") + + +if __name__ == "__main__": + main() diff --git a/resolve/__init__.py b/resolve/__init__.py index fadc803f..5b1b670e 100644 --- a/resolve/__init__.py +++ b/resolve/__init__.py @@ -1,9 +1,12 @@ +from .antenna_positions import AntennaPositions from .calibration import calibration_distribution from .constants import * +from .direction import * from .fits import field2fits from .global_config import * from .likelihood import * from .minimization import Minimization, MinimizationState, simple_minimize +from .mosaicing import * from .mpi import onlymaster from .mpi_operators import * from .ms_import import ms2observations, ms_n_spectral_windows @@ -16,8 +19,8 @@ from .multi_frequency.operators import ( from .observation import Observation, tmin_tmax, unique_antennas, unique_times from .plotter import MfPlotter, Plotter from .points import PointInserter -from .polarization import polarization_matrix_exponential -from .primary_beam import vla_beam +from .polarization import Polarization, polarization_matrix_exponential +from .primary_beam import * from .response import MfResponse, ResponseDistributor, StokesIResponse, SingleResponse from .simple_operators import * from .util import ( diff --git a/resolve/likelihood.py b/resolve/likelihood.py index 44c3ad5e..e588b5e9 100644 --- a/resolve/likelihood.py +++ b/resolve/likelihood.py @@ -8,7 +8,7 @@ import nifty7 as ift from .observation import Observation from .response import FullPolResponse, MfResponse, StokesIResponse -from .util import my_assert_isinstance, my_asserteq, my_assert +from .util import my_assert, my_assert_isinstance, my_asserteq def _get_mask(observation): @@ -91,7 +91,7 @@ def ImagingLikelihood( Parameters ---------- - observation : Observation + observation : Observation or dict(Observation) Observation object from which observation.vis and potentially observation.weight is used for computing the likelihood. @@ -109,11 +109,12 @@ def ImagingLikelihood( calibration_operator : Operator Optional. Target needs to be the same as observation.vis. """ - my_assert_isinstance(observation, Observation) my_assert_isinstance(sky_operator, ift.Operator) sdom = sky_operator.target - if isinstance(sdom, ift.MultiDomain): + mosaicing = isinstance(observation, dict) + + if isinstance(sdom, ift.MultiDomain) and not mosaicing: if len(sdom["I"].shape) == 3: raise NotImplementedError( "Polarization and multi-frequency at the same time not supported yet." @@ -121,14 +122,18 @@ def ImagingLikelihood( else: R = FullPolResponse(observation, sky_operator.target) else: - if len(sdom.shape) == 3: + if not mosaicing and len(sdom.shape) == 3: R = MfResponse(observation, sdom[0], sdom[1]) else: R = StokesIResponse(observation, sdom) model_data = R @ sky_operator - if inverse_covariance_operator is None: - return _build_gauss_lh_nres(model_data, observation.vis, observation.weight) + if mosaicing: + vis = ift.MultiField.from_dict({kk: o.vis for kk, o in observation.items()}) + weight = ift.MultiField.from_dict({kk: o.weight for kk, o in observation.items()}) + else: + vis, weight = observation.vis, observation.weight + return _build_gauss_lh_nres(model_data, vis, weight) return _varcov(observation, model_data, inverse_covariance_operator) diff --git a/resolve/mosaicing/__init__.py b/resolve/mosaicing/__init__.py new file mode 100644 index 00000000..6802616c --- /dev/null +++ b/resolve/mosaicing/__init__.py @@ -0,0 +1 @@ +from .sky_slicer import * diff --git a/resolve/mosaicing/sky_slicer.py b/resolve/mosaicing/sky_slicer.py new file mode 100644 index 00000000..5fcf4253 --- /dev/null +++ b/resolve/mosaicing/sky_slicer.py @@ -0,0 +1,124 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2020 Max-Planck-Society +# Author: Philipp Arras + +import numpy as np + +import nifty7 as ift + + +class SkySlicer(ift.LinearOperator): + """Maps from the total sky domain to individual sky domains and applies the + primary beam pattern. + + Parameters + ---------- + domain : RGSpace + Two-dimensional RG-Space which serves as domain. The distances are + in pseudo-radian. + + beam_directions : dict(key: BeamDirection) + Dictionary of BeamDirection that contains the information of the + directions of the different observations and the beam pattern. + """ + + def __init__(self, domain, beam_directions): + self._bd = dict(beam_directions) + self._domain = ift.makeDomain(domain) + t, b, s = {}, {}, {} + for kk, vv in self._bd.items(): + t[kk], s[kk], b[kk] = vv.slice_target(self._domain) + self._beams = b + self._slices = s + self._target = ift.makeDomain(t) + self._capability = self.TIMES | self.ADJOINT_TIMES + + def apply(self, x, mode): + self._check_input(x, mode) + x = x.val + if mode == self.TIMES: + res = {} + for kk in self._target.keys(): + res[kk] = self._beams[kk].val * x[self._slices[kk]] + else: + res = np.zeros(self._domain.shape) + for kk, xx in x.items(): + res[self._slices[kk]] += xx * self._beams[kk].val + return ift.makeField(self._tgt(mode), res) + + +class BeamDirection: + """Represent direction information of one pointing. + + Parameters + ---------- + dx : float + Pointing offset in pseudo radian. + dy : float + Pointing offset in pseudo radian. + beam_func : function + Function that takes the two directions on the sky in pseudo radian and + returns beam pattern. + cutoff : float + Relative area of beam pattern that is cut off. 0 corresponds to no + cutoff at all. 1 corresponds to cut off everything. + """ + + def __init__(self, dx, dy, beam_func, cutoff): + self._dx, self._dy = float(dx), float(dy) + self._f = beam_func + self._cutoff = float(cutoff) + assert 0. <= cutoff < 1 + + def slice_target(self, domain): + """ + Parameters + ---------- + domain : RGSpace + Total sky domain. + """ + dom = ift.makeDomain(domain)[0] + dst = np.array(dom.distances) + assert abs(self._dx) < dst[0] * dom.shape[0] + assert abs(self._dy) < dst[1] * dom.shape[1] + npix = np.array(domain.shape) + + xs = np.linspace(0, max(npix * dst), num=4*max(npix)) + # Technically not 100% correct since we integrate circles here. + # The actual cutoff is lower than the one that is induced by the + # following computation. + ys = self._f(xs)*xs + cond = np.cumsum(ys)/np.sum(ys) + np.testing.assert_allclose(cond[-1], 1.) + ind = np.searchsorted(cond, 1.-self._cutoff) + fov = 2*xs[ind] + patch_npix = fov/dst + patch_npix = (np.ceil(patch_npix/2)*2).astype(int) + + shift = np.array([self._dx, self._dy]) + patch_center_unrounded = np.array(npix) / 2 + shift / dst + ipix_patch_center = np.round(patch_center_unrounded).astype(int) + # Or maybe use ceil? + assert np.all(patch_npix % 2 == 0) + mi = (ipix_patch_center - patch_npix / 2).astype(int) + ma = mi + patch_npix + + assert np.all(mi >= 0) + assert np.all(ma < npix) + slc = slice(mi[0], ma[0]), slice(mi[1], ma[1]) + tgt = ift.RGSpace(patch_npix, dst) + assert tgt.shape[0] % 2 == 0 + assert tgt.shape[1] % 2 == 0 + test = np.empty(dom.shape) + assert test[slc].shape == tgt.shape + + # Create coordinate field + mgrid = np.mgrid[: patch_npix[0], : patch_npix[1]].astype(float) + mgrid -= patch_npix[..., None, None] / 2 + # FIXME Add subpixel offset + # subpixel_offset = ipix_patch_center - patch_center_unrounded + # assert np.all(subpixel_offset < 1.0) + # assert np.all(subpixel_offset >= 0.0) + mgrid *= dst[..., None, None] + beam = ift.makeField(tgt, self._f(np.linalg.norm(mgrid, axis=0))) + return tgt, slc, beam diff --git a/resolve/observation.py b/resolve/observation.py index 69735a55..6285292d 100644 --- a/resolve/observation.py +++ b/resolve/observation.py @@ -48,6 +48,8 @@ class Observation: my_asserteq(vis.shape, (len(polarization), nrows, len(freq))) my_asserteq(nrows, vis.shape[1]) my_assert(np.all(weight >= 0.0)) + my_assert(np.all(np.isfinite(vis))) + my_assert(np.all(np.isfinite(weight))) vis.flags.writeable = False weight.flags.writeable = False diff --git a/resolve/primary_beam.py b/resolve/primary_beam.py index 71f8ebaa..b4a186dd 100644 --- a/resolve/primary_beam.py +++ b/resolve/primary_beam.py @@ -3,6 +3,7 @@ # Author: Philipp Arras import numpy as np +import scipy.special as sc import nifty7 as ift @@ -176,3 +177,16 @@ def vla_beam(domain, freq): beam = rweight * upper + (1 - rweight) * lower beam[beam < 0] = 0 return ift.makeOp(ift.makeField(dom, beam)) + + +def alma_beam_func(D, d, x): + assert isinstance(x, np.ndarray) + assert x.ndim < 3 + assert np.max(np.abs(x)) < np.pi/np.sqrt(2) + b = d / D + x = np.pi * D * x + mask = x == 0.0 + x[mask] = 1 + sol = 2 / (x * (1 - b ** 2)) * (sc.jn(1, x) - b * sc.jn(1, x * b)) + sol[mask] = 1 + return sol**2 diff --git a/resolve/response.py b/resolve/response.py index 89dbe374..f44931bc 100644 --- a/resolve/response.py +++ b/resolve/response.py @@ -2,6 +2,9 @@ # Copyright(C) 2019-2020 Max-Planck-Society # Author: Philipp Arras +from functools import reduce +from operator import add + import numpy as np from ducc0.wgridder.experimental import dirty2vis, vis2dirty @@ -14,6 +17,14 @@ from .util import my_assert, my_assert_isinstance, my_asserteq def StokesIResponse(observation, domain): + if isinstance(observation, dict): + # TODO Possibly add subpixel offset here + d = ift.MultiDomain.make(domain) + res = ( + StokesIResponse(o, d[kk]).ducktape(kk).ducktape_left(kk) + for kk, o in observation.items() + ) + return reduce(add, res) my_assert_isinstance(observation, Observation) domain = ift.DomainTuple.make(domain) my_asserteq(len(domain), 1) -- GitLab From 4b04558ce77c67a1e2269163a266fbedaad34418 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Thu, 4 Feb 2021 17:02:33 +0100 Subject: [PATCH 02/37] Add alma data script --- misc/convert_alma_data.py | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 misc/convert_alma_data.py diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py new file mode 100644 index 00000000..da327660 --- /dev/null +++ b/misc/convert_alma_data.py @@ -0,0 +1,61 @@ +import numpy as np +import resolve as rve +from os.path import join +from casacore.tables import table + +f = "extended.ms" +_CASACORE_TABLE_CFG = {"readonly": True, "ack": False} + +spectral_window = 0 +channel_slice = slice(1027, 1030) + +with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: + freq = np.array(t.getcol("CHAN_FREQ"))[spectral_window] +freq = freq[channel_slice] + +with table(join(f, "FIELD"), **_CASACORE_TABLE_CFG) as t: + field_directions = np.squeeze(t.getcol("DELAY_DIR")) +assert field_directions.ndim == 2 +assert field_directions.shape[1] == 2 + +with table(f, **_CASACORE_TABLE_CFG) as t: + wgt = np.array(t.getcol("WEIGHT"))[:, None] + wgt = np.repeat(wgt, len(freq), axis=1) + flag = np.array(t.getcol("FLAG"))[:, channel_slice] + wgt[flag] = 0. + datadescid = np.array(t.getcol("DATA_DESC_ID")) + wgt[datadescid != spectral_window] = 0. + del(flag, spectral_window) + + vis = np.array(t.getcol("DATA"))[:, channel_slice] + uvw = np.array(t.getcol("UVW")) + field = np.array(t.getcol("FIELD_ID")) + + # Average polarization + assert vis.shape[2] == 2 + assert wgt.shape[2] == 2 + vis = np.sum(wgt*vis, axis=-1) + wgt = np.sum(wgt, axis=2) + vis /= wgt + +for ifield in set(field): + ww = wgt.copy() + ww[field != ifield] = 0. + + # Clean up rows + ind = np.any(ww != 0., axis=1) + myuvw = uvw[ind] + mywgt = wgt[ind] + myvis = vis[ind] + + # Clean up freqs + assert ww.ndim == 2 + ind = np.any(mywgt != 0., axis=0) + mywgt = mywgt[:, ind] + myvis = myvis[:, ind] + myfreq = freq[ind] + assert len(myfreq) == 3 + print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) + + obs = rve.Observation(rve.AntennaPositions(myuvw), myvis[None], mywgt[None], rve.Polarization.trivial(), myfreq, rve.Direction(field_directions[ifield], 2000)) + obs.save(f"extended_field{ifield}.npz", compress=False) -- GitLab From d3ee790e6bab7c36dc5efe2e7a1315d86491c571 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Thu, 4 Feb 2021 17:09:48 +0100 Subject: [PATCH 03/37] Save np arrays as contiguous --- misc/convert_alma_data.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index da327660..e24bc4b6 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -1,9 +1,12 @@ -import numpy as np -import resolve as rve +import sys from os.path import join + +import numpy as np from casacore.tables import table -f = "extended.ms" +import resolve as rve + +f = sys.argv[1] _CASACORE_TABLE_CFG = {"readonly": True, "ack": False} spectral_window = 0 @@ -22,10 +25,10 @@ with table(f, **_CASACORE_TABLE_CFG) as t: wgt = np.array(t.getcol("WEIGHT"))[:, None] wgt = np.repeat(wgt, len(freq), axis=1) flag = np.array(t.getcol("FLAG"))[:, channel_slice] - wgt[flag] = 0. + wgt[flag] = 0.0 datadescid = np.array(t.getcol("DATA_DESC_ID")) - wgt[datadescid != spectral_window] = 0. - del(flag, spectral_window) + wgt[datadescid != spectral_window] = 0.0 + del (flag, spectral_window) vis = np.array(t.getcol("DATA"))[:, channel_slice] uvw = np.array(t.getcol("UVW")) @@ -34,28 +37,35 @@ with table(f, **_CASACORE_TABLE_CFG) as t: # Average polarization assert vis.shape[2] == 2 assert wgt.shape[2] == 2 - vis = np.sum(wgt*vis, axis=-1) + vis = np.sum(wgt * vis, axis=-1) wgt = np.sum(wgt, axis=2) vis /= wgt for ifield in set(field): ww = wgt.copy() - ww[field != ifield] = 0. + ww[field != ifield] = 0.0 # Clean up rows - ind = np.any(ww != 0., axis=1) + ind = np.any(ww != 0.0, axis=1) myuvw = uvw[ind] mywgt = wgt[ind] myvis = vis[ind] # Clean up freqs assert ww.ndim == 2 - ind = np.any(mywgt != 0., axis=0) + ind = np.any(mywgt != 0.0, axis=0) mywgt = mywgt[:, ind] myvis = myvis[:, ind] myfreq = freq[ind] assert len(myfreq) == 3 print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) - obs = rve.Observation(rve.AntennaPositions(myuvw), myvis[None], mywgt[None], rve.Polarization.trivial(), myfreq, rve.Direction(field_directions[ifield], 2000)) + obs = rve.Observation( + rve.AntennaPositions(np.ascontiguousarray(myuvw)), + np.ascontiguousarray(myvis[None]), + np.ascontiguousarray(mywgt[None]), + rve.Polarization.trivial(), + myfreq, + rve.Direction(field_directions[ifield], 2000), + ) obs.save(f"extended_field{ifield}.npz", compress=False) -- GitLab From 96ccf287aa5b25b9e1e7b5d89136f120c9f6ff09 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 11:03:36 +0100 Subject: [PATCH 04/37] Add debugging info --- demo/mosaicing.py | 18 ++++++++---------- resolve/response.py | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 3d0ee31d..0e50be28 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -10,7 +10,7 @@ import resolve as rve def main(): - rve.set_nthreads(1) + rve.set_nthreads(2) rve.set_wgridding(False) diffusefluxlevel = 20 @@ -24,14 +24,6 @@ def main(): } rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) - # Plot individual dirty images - for kk, vv in interobs.items(): - npix = 500 - fov = 1 * rve.ARCMIN2RAD - domain = ift.RGSpace([npix, npix], [fov / npix, fov / npix]) - R = rve.StokesIResponse(vv, domain) - ift.single_plot(R.adjoint(vv.vis * vv.weight), name=f"dirty{kk}.png") - xs, ys = [], [] for vv in interobs.values(): xs.append(vv.direction.phase_center[0]) @@ -57,6 +49,7 @@ def main(): freq = list(interobs.values())[0].freq for vv in interobs.values(): assert np.all(vv.freq == freq) + print("Frequencies:", freq) extended_blockage = np.array([10.7, 0.75]) # m extended_blockage *= np.mean(freq) / rve.SPEEDOFLIGHT beam_func = lambda x: rve.alma_beam_func(*extended_blockage, x) @@ -70,7 +63,7 @@ def main(): for kk, obs in interobs.items() } - npix = np.array([1000, 1000]) + npix = np.array([1500, 1500]) fov = np.array([xfov, yfov]) dom = ift.RGSpace(npix, fov / npix) logsky = ift.SimpleCorrelatedField( @@ -84,6 +77,11 @@ def main(): ) lh = rve.ImagingLikelihood(interobs, skyslicer @ logsky.exp()) + R = rve.StokesIResponse(interobs, skyslicer.target) @ skyslicer + vis = ift.MultiField.from_dict({kk: o.vis for kk, o in interobs.items()}) + weight = ift.MultiField.from_dict({kk: o.weight for kk, o in interobs.items()}) + ift.single_plot(R.adjoint(vis*weight), name="total_dirty.png") + plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) plotter.add("power spectrum logsky", logsky.power_spectrum) diff --git a/resolve/response.py b/resolve/response.py index f44931bc..43fade8b 100644 --- a/resolve/response.py +++ b/resolve/response.py @@ -10,6 +10,7 @@ from ducc0.wgridder.experimental import dirty2vis, vis2dirty import nifty7 as ift +from .constants import SPEEDOFLIGHT from .global_config import epsilon, nthreads, wgridding from .multi_frequency.irg_space import IRGSpace from .observation import Observation @@ -238,6 +239,7 @@ class SingleResponse(ift.LinearOperator): self._target_dtype = np.complex64 if single_precision else np.complex128 self._domain_dtype = np.float32 if single_precision else np.float64 self._verbt, self._verbadj = verbose, verbose + self._ofac = None def apply(self, x, mode): self._check_input(x, mode) @@ -250,6 +252,9 @@ class SingleResponse(ift.LinearOperator): args1 = {"dirty": x} if self._verbt: args1["verbosity"] = True + print( + f"\nINFO: Oversampling factors in response: {self.oversampling_factors()}\n" + ) self._verbt = False f = dirty2vis # FIXME Use vis_out keyword of wgridder @@ -263,6 +268,9 @@ class SingleResponse(ift.LinearOperator): } if self._verbadj: args1["verbosity"] = True + print( + f"\nINFO: Oversampling factors in response: {self.oversampling_factors()}\n" + ) self._verbadj = False f = vis2dirty res = ift.makeField(self._tgt(mode), f(**self._args, **args1) * self._vol) @@ -270,3 +278,16 @@ class SingleResponse(ift.LinearOperator): res.dtype, self._target_dtype if mode == self.TIMES else self._domain_dtype ) return res + + def oversampling_factors(self): + if self._ofac is not None: + return self._ofac + maxuv = ( + np.max(np.abs(self._args["uvw"][:, 0:2]), axis=0) + * np.max(self._args["freq"]) + / SPEEDOFLIGHT + ) + hspace = self._domain[0].get_default_codomain() + hvol = np.array(hspace.shape) * np.array(hspace.distances) / 2 + self._ofac = hvol / maxuv + return self._ofac -- GitLab From 3a6d869c8a29ddb41e71576a2afe0c22128abcc2 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 11:06:08 +0100 Subject: [PATCH 05/37] Improve data import --- misc/convert_alma_data.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index e24bc4b6..643c60e1 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -1,5 +1,5 @@ import sys -from os.path import join +from os.path import join, split, splitext import numpy as np from casacore.tables import table @@ -7,6 +7,7 @@ from casacore.tables import table import resolve as rve f = sys.argv[1] +outfbase = split(splitext(f)[0])[1] _CASACORE_TABLE_CFG = {"readonly": True, "ack": False} spectral_window = 0 @@ -68,4 +69,4 @@ for ifield in set(field): myfreq, rve.Direction(field_directions[ifield], 2000), ) - obs.save(f"extended_field{ifield}.npz", compress=False) + obs.save(f"{outfbase}_field{ifield}.npz", compress=False) -- GitLab From 72d135130b7cf6cd5c22eb270424e24827e0ebe8 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 14:05:31 +0100 Subject: [PATCH 06/37] Add crazy offset in data import --- misc/convert_alma_data.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 643c60e1..41649cda 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -11,10 +11,20 @@ outfbase = split(splitext(f)[0])[1] _CASACORE_TABLE_CFG = {"readonly": True, "ack": False} spectral_window = 0 -channel_slice = slice(1027, 1030) with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: freq = np.array(t.getcol("CHAN_FREQ"))[spectral_window] + +minfreq = 230172900000. +if outfbase == "compact": + minfreq -= 27146364.99822998 +maxfreq = minfreq * 1.000015 +a = freq[::-1] +assert np.all(np.diff(a) >= 0) +ind1 = freq.size - np.searchsorted(a, minfreq) + 1 +ind0 = freq.size - np.searchsorted(a, maxfreq) + 1 +channel_slice = slice(ind0, ind1) +print(channel_slice) freq = freq[channel_slice] with table(join(f, "FIELD"), **_CASACORE_TABLE_CFG) as t: -- GitLab From 770b86465cdcbeb4ad2275ac53c9bd6709316341 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 14:07:10 +0100 Subject: [PATCH 07/37] Fixup --- misc/convert_alma_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 41649cda..2002f089 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -68,7 +68,6 @@ for ifield in set(field): mywgt = mywgt[:, ind] myvis = myvis[:, ind] myfreq = freq[ind] - assert len(myfreq) == 3 print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) obs = rve.Observation( -- GitLab From 422e4adf99342a8d8c1c80a9bbaa77a52a13c66e Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 14:08:16 +0100 Subject: [PATCH 08/37] Implement mosaicing response 9/n --- demo/mosaicing.py | 35 +++++++++++++++------------------ resolve/mosaicing/sky_slicer.py | 2 +- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 0e50be28..9f9e801e 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -14,14 +14,18 @@ def main(): rve.set_wgridding(False) diffusefluxlevel = 20 - interobs = { - f"extended{ii}": rve.Observation.load( - f"/data/mosaicing/extended/extended_field{ii}.npz" - ) + interobs_extended = { + f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") for ii in range(5, 135) # for ii in [50, 58, 59, 68, 69, 70, 88] # for ii in [68, 69, 70] } + interobs_compact = { + f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") + for ii in range(5, 153) + } + interobs = {**interobs_compact, **interobs_extended} + rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) xs, ys = [], [] @@ -45,25 +49,18 @@ def main(): plt.savefig("pointings.png") plt.close() - # Assert all frequencies are the same - freq = list(interobs.values())[0].freq - for vv in interobs.values(): - assert np.all(vv.freq == freq) - print("Frequencies:", freq) - extended_blockage = np.array([10.7, 0.75]) # m - extended_blockage *= np.mean(freq) / rve.SPEEDOFLIGHT - beam_func = lambda x: rve.alma_beam_func(*extended_blockage, x) - beam_directions = { - kk: rve.BeamDirection( + beam_directions = {} + for kk, obs in interobs.items(): + extended_blockage = np.array([10.7, 0.75]) # m + extended_blockage *= np.mean(obs.freq) / rve.SPEEDOFLIGHT + beam_directions[kk] = rve.BeamDirection( obs.direction.phase_center[0] - global_phase_center[0], obs.direction.phase_center[1] - global_phase_center[1], - beam_func, + lambda x: rve.alma_beam_func(*extended_blockage, x), 0.05, ) - for kk, obs in interobs.items() - } - npix = np.array([1500, 1500]) + npix = np.array([1800, 1800]) fov = np.array([xfov, yfov]) dom = ift.RGSpace(npix, fov / npix) logsky = ift.SimpleCorrelatedField( @@ -80,7 +77,7 @@ def main(): R = rve.StokesIResponse(interobs, skyslicer.target) @ skyslicer vis = ift.MultiField.from_dict({kk: o.vis for kk, o in interobs.items()}) weight = ift.MultiField.from_dict({kk: o.weight for kk, o in interobs.items()}) - ift.single_plot(R.adjoint(vis*weight), name="total_dirty.png") + plt.imsave("total_dirty.png", R.adjoint(vis * weight).val.T, origin="lower") plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) diff --git a/resolve/mosaicing/sky_slicer.py b/resolve/mosaicing/sky_slicer.py index 5fcf4253..7eb75e4d 100644 --- a/resolve/mosaicing/sky_slicer.py +++ b/resolve/mosaicing/sky_slicer.py @@ -39,7 +39,7 @@ class SkySlicer(ift.LinearOperator): if mode == self.TIMES: res = {} for kk in self._target.keys(): - res[kk] = self._beams[kk].val * x[self._slices[kk]] + res[kk] = x[self._slices[kk]] * self._beams[kk].val else: res = np.zeros(self._domain.shape) for kk, xx in x.items(): -- GitLab From febfe0bebea66a04371908d96a4b7d94a7d816c7 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 14:08:40 +0100 Subject: [PATCH 09/37] Crucial fix in pointing conventions --- resolve/mosaicing/sky_slicer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resolve/mosaicing/sky_slicer.py b/resolve/mosaicing/sky_slicer.py index 7eb75e4d..3f377890 100644 --- a/resolve/mosaicing/sky_slicer.py +++ b/resolve/mosaicing/sky_slicer.py @@ -95,7 +95,7 @@ class BeamDirection: patch_npix = fov/dst patch_npix = (np.ceil(patch_npix/2)*2).astype(int) - shift = np.array([self._dx, self._dy]) + shift = np.array([-self._dx, self._dy]) patch_center_unrounded = np.array(npix) / 2 + shift / dst ipix_patch_center = np.round(patch_center_unrounded).astype(int) # Or maybe use ceil? -- GitLab From 1e0f47033bb4972a45d84aa656c64bd811dca405 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 14:55:50 +0100 Subject: [PATCH 10/37] Remove dirty hack --- misc/convert_alma_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 2002f089..7a0e0a6b 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -16,8 +16,8 @@ with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: freq = np.array(t.getcol("CHAN_FREQ"))[spectral_window] minfreq = 230172900000. -if outfbase == "compact": - minfreq -= 27146364.99822998 +#if outfbase == "compact": + #minfreq -= 27146364.99822998 maxfreq = minfreq * 1.000015 a = freq[::-1] assert np.all(np.diff(a) >= 0) -- GitLab From 8c4586c334f85d77f9029c375e15bb94342c9003 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 5 Feb 2021 16:16:09 +0100 Subject: [PATCH 11/37] Set up first working MGVI reconstruction --- demo/mosaicing.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 9f9e801e..1cdba5e8 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -9,6 +9,12 @@ import nifty7 as ift import resolve as rve +@rve.onlymaster +def imsave(fname, field): + plt.imsave(fname, field.val.T, origin="lower") + plt.close() + + def main(): rve.set_nthreads(2) rve.set_wgridding(False) @@ -36,7 +42,7 @@ def main(): # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 - margin = 2 * rve.ARCMIN2RAD + margin = 1 * rve.ARCMIN2RAD plt.scatter(xs, ys, label="Extended") plt.scatter(*global_phase_center_casa, label="Phase center CASA") plt.scatter(*global_phase_center, label="Phase center resolve") @@ -45,8 +51,11 @@ def main(): print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) + plt.gca().invert_xaxis() + plt.gca().set_aspect("equal") plt.legend() - plt.savefig("pointings.png") + if rve.mpi.master: + plt.savefig("pointings.png") plt.close() beam_directions = {} @@ -57,41 +66,42 @@ def main(): obs.direction.phase_center[0] - global_phase_center[0], obs.direction.phase_center[1] - global_phase_center[1], lambda x: rve.alma_beam_func(*extended_blockage, x), - 0.05, + 0.1, ) npix = np.array([1800, 1800]) fov = np.array([xfov, yfov]) dom = ift.RGSpace(npix, fov / npix) logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 0.1), (5, 1), (1.2, 0.4), (0.2, 0.2), (-2, 0.5) + dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) ) skyslicer = rve.SkySlicer(logsky.target, beam_directions) ift.extra.check_linear_operator(skyslicer) - ift.single_plot( - skyslicer.adjoint(ift.full(skyslicer.target, 1.0)), - name="debug_skyslicer_adjoint.png", - ) lh = rve.ImagingLikelihood(interobs, skyslicer @ logsky.exp()) R = rve.StokesIResponse(interobs, skyslicer.target) @ skyslicer vis = ift.MultiField.from_dict({kk: o.vis for kk, o in interobs.items()}) weight = ift.MultiField.from_dict({kk: o.weight for kk, o in interobs.items()}) - plt.imsave("total_dirty.png", R.adjoint(vis * weight).val.T, origin="lower") + + imsave("total_dirty.png", R.adjoint(vis * weight)) + imsave("skyslicer_adjoint.png", skyslicer.adjoint(ift.full(skyslicer.target, 1.0))) plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) plotter.add("power spectrum logsky", logsky.power_spectrum) - plotter.add_histogram( - "normalized residuals (original weights)", lh.normalized_residual - ) - ham = ift.StandardHamiltonian(lh) + # plotter.add_histogram( + # "normalized residuals (original weights)", lh.normalized_residual + # ) + ham = ift.StandardHamiltonian(lh, ift.AbsDeltaEnergyController(0.5, 3, 300)) fld = 0.1 * ift.from_random(lh.domain) state = rve.MinimizationState(fld, []) - mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=10)) - state = rve.simple_minimize(ham, state.mean, 0, mini) - plotter.plot("stage1", state) - state.save("stage1") + mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) + # TODO Add minisanity to resolve + # TODO Fix histogram plots + for ii in range(20): + state = rve.simple_minimize(ham, state.mean, 3, mini) + plotter.plot(f"iter{ii}", state) + # state.save("stage1") # state = rve.MinimizationState.load("stage1") -- GitLab From ae87a91d3e9cb056aadc35bf7e17ff0ce0f941f5 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Sun, 7 Feb 2021 12:47:37 +0100 Subject: [PATCH 12/37] Minor --- demo/mosaicing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 1cdba5e8..1cd74980 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -88,11 +88,12 @@ def main(): plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) + plotter.add("sky", logsky.exp(), cmap="inferno_r") plotter.add("power spectrum logsky", logsky.power_spectrum) # plotter.add_histogram( # "normalized residuals (original weights)", lh.normalized_residual # ) - ham = ift.StandardHamiltonian(lh, ift.AbsDeltaEnergyController(0.5, 3, 300)) + ham = ift.StandardHamiltonian(lh, ift.AbsDeltaEnergyController(0.5, 3, 300, name="Sampling")) fld = 0.1 * ift.from_random(lh.domain) state = rve.MinimizationState(fld, []) mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) @@ -101,8 +102,7 @@ def main(): for ii in range(20): state = rve.simple_minimize(ham, state.mean, 3, mini) plotter.plot(f"iter{ii}", state) - # state.save("stage1") - # state = rve.MinimizationState.load("stage1") + state.save(f"iter{ii}") if __name__ == "__main__": -- GitLab From 564d8e7046ff320265eedb5e94ba4324b90569d2 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Sun, 7 Feb 2021 17:55:39 +0100 Subject: [PATCH 13/37] Start integrating single dish data --- misc/convert_alma_data.py | 175 ++++++++++++++++++++++-------------- resolve/observation.py | 181 +++++++++++++++++++++++++++----------- 2 files changed, 237 insertions(+), 119 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 7a0e0a6b..65d7e1c5 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -1,81 +1,124 @@ -import sys +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2021 Max-Planck-Society +# Author: Philipp Arras + from os.path import join, split, splitext import numpy as np -from casacore.tables import table +from casacore.tables import table, taql import resolve as rve -f = sys.argv[1] -outfbase = split(splitext(f)[0])[1] -_CASACORE_TABLE_CFG = {"readonly": True, "ack": False} - -spectral_window = 0 - -with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: - freq = np.array(t.getcol("CHAN_FREQ"))[spectral_window] - -minfreq = 230172900000. -#if outfbase == "compact": - #minfreq -= 27146364.99822998 -maxfreq = minfreq * 1.000015 -a = freq[::-1] -assert np.all(np.diff(a) >= 0) -ind1 = freq.size - np.searchsorted(a, minfreq) + 1 -ind0 = freq.size - np.searchsorted(a, maxfreq) + 1 -channel_slice = slice(ind0, ind1) -print(channel_slice) -freq = freq[channel_slice] - -with table(join(f, "FIELD"), **_CASACORE_TABLE_CFG) as t: - field_directions = np.squeeze(t.getcol("DELAY_DIR")) -assert field_directions.ndim == 2 -assert field_directions.shape[1] == 2 - -with table(f, **_CASACORE_TABLE_CFG) as t: - wgt = np.array(t.getcol("WEIGHT"))[:, None] - wgt = np.repeat(wgt, len(freq), axis=1) - flag = np.array(t.getcol("FLAG"))[:, channel_slice] - wgt[flag] = 0.0 - datadescid = np.array(t.getcol("DATA_DESC_ID")) - wgt[datadescid != spectral_window] = 0.0 - del (flag, spectral_window) - - vis = np.array(t.getcol("DATA"))[:, channel_slice] - uvw = np.array(t.getcol("UVW")) - field = np.array(t.getcol("FIELD_ID")) - - # Average polarization - assert vis.shape[2] == 2 - assert wgt.shape[2] == 2 - vis = np.sum(wgt * vis, axis=-1) - wgt = np.sum(wgt, axis=2) - vis /= wgt - -for ifield in set(field): - ww = wgt.copy() - ww[field != ifield] = 0.0 +def cleanup(weight, vis, freq, uvw=None): + assert weight.ndim == 2 + assert vis.shape == weight.shape + assert freq.ndim==1 + assert freq.size == vis.shape[1] + assert uvw.shape[0] == vis.shape[0] # Clean up rows - ind = np.any(ww != 0.0, axis=1) - myuvw = uvw[ind] - mywgt = wgt[ind] + ind = np.any(weight != 0.0, axis=1) + mywgt = weight[ind] myvis = vis[ind] + if uvw is not None: + myuvw = uvw[ind] # Clean up freqs - assert ww.ndim == 2 ind = np.any(mywgt != 0.0, axis=0) mywgt = mywgt[:, ind] myvis = myvis[:, ind] myfreq = freq[ind] - print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) - - obs = rve.Observation( - rve.AntennaPositions(np.ascontiguousarray(myuvw)), - np.ascontiguousarray(myvis[None]), - np.ascontiguousarray(mywgt[None]), - rve.Polarization.trivial(), - myfreq, - rve.Direction(field_directions[ifield], 2000), - ) - obs.save(f"{outfbase}_field{ifield}.npz", compress=False) + if uvw is None: + return mywgt, myvis, myfreq + return mywgt, myvis, myfreq, myuvw + + +_CASACORE_TABLE_CFG = {"readonly": True, "ack": False} +minfreq = 230172900000.0 +# if outfbase == "compact": +# minfreq -= 27146364.99822998 +maxfreq = minfreq * 1.000015 + +for f, spectral_window, single_dish, fieldid in [ + # ("extended.ms", 0, False, None), + # ("compact.ms", 0, False, None), + ("totalpower.ms", 17, True, 1) +]: + outfbase = split(splitext(f)[0])[1] + + with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: + freq = taql("select CHAN_FREQ from $t where ROWID()=$spectral_window").getcol( + "CHAN_FREQ" + ) + freq = np.squeeze(freq) + + if not np.all(np.diff(freq) >= 0): + a = freq[::-1] + assert np.all(np.diff(a) >= 0) + ind1 = freq.size - np.searchsorted(a, minfreq) + 1 + ind0 = freq.size - np.searchsorted(a, maxfreq) + 1 + else: + assert np.all(np.diff(freq) >= 0) + ind0 = np.searchsorted(freq, minfreq) + ind1 = np.searchsorted(freq, maxfreq) + channel_slice = slice(ind0, ind1) + freq = freq[channel_slice] + + if not single_dish: + with table(join(f, "FIELD"), **_CASACORE_TABLE_CFG) as t: + field_directions = np.squeeze(t.getcol("DELAY_DIR")) + assert field_directions.ndim == 2 + assert field_directions.shape[1] == 2 + + with table(f, **_CASACORE_TABLE_CFG) as t: + sel = lambda x: np.array( + taql(f"select {x} from $t where DATA_DESC_ID=$spectral_window").getcol(x) + ) + wgt = sel("WEIGHT")[:, None] + wgt = np.repeat(wgt, len(freq), axis=1) + flag = sel("FLAG")[:, channel_slice] + wgt[flag] = 0.0 + del flag + + vis = sel("FLOAT_DATA" if single_dish else "DATA")[:, channel_slice] + # Average polarization + assert vis.shape[2] == 2 + assert wgt.shape[2] == 2 + vis = np.sum(wgt * vis, axis=-1) + wgt = np.sum(wgt, axis=2) + vis /= wgt + field = sel("FIELD_ID") + if single_dish: + wgt[field != fieldid] = 0.0 + del field + else: + uvw = sel("UVW") + + if single_dish: + with table(f, readonly=True) as t: + pointings = taql("select DIRECTION from $t::POINTING where ROWID() in [select from $t where DATA_DESC_ID=$spectral_window giving [ROWID()]]").getcol("DIRECTION") + mywgt, myvis, myfreq, mypointings = cleanup(wgt, vis, freq, pointings) + obs = rve.SingleDishObservation( + np.ascontiguousarray(mypointings), + np.ascontiguousarray(myvis[None]), + np.ascontiguousarray(mywgt[None]), + rve.Polarization.trivial(), + myfreq, + ) + obs.save(f"{outfbase}.npz", compress=False) + else: + for ifield in set(field): + ww = wgt.copy() + ww[field != ifield] = 0.0 + mywgt, myvis, myfreq, myuvw = cleanup(ww, vis, freq, uvw) + print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) + + obs = rve.Observation( + rve.AntennaPositions(np.ascontiguousarray(myuvw)), + np.ascontiguousarray(myvis[None]), + np.ascontiguousarray(mywgt[None]), + rve.Polarization.trivial(), + myfreq, + rve.Direction(field_directions[ifield], 2000), + ) + obs.save(f"{outfbase}_field{ifield}.npz", compress=False) diff --git a/resolve/observation.py b/resolve/observation.py index 6285292d..c89c1253 100644 --- a/resolve/observation.py +++ b/resolve/observation.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2020 Max-Planck-Society +# Copyright(C) 2019-2021 Max-Planck-Society # Author: Philipp Arras import numpy as np @@ -14,7 +14,133 @@ from .polarization import Polarization from .util import compare_attributes, my_assert, my_assert_isinstance, my_asserteq -class Observation: +class _Observation: + @property + def vis(self): + dom = [ift.UnstructuredDomain(ss) for ss in self._vis.shape] + return ift.makeField(dom, self._vis) + + @property + def weight(self): + dom = [ift.UnstructuredDomain(ss) for ss in self._weight.shape] + return ift.makeField(dom, self._weight) + + @property + def freq(self): + return self._freq + + @property + def polarization(self): + return self._polarization + + @property + def direction(self): + return self._direction + + @property + def npol(self): + return self._vis.shape[0] + + @property + def nrow(self): + return self._vis.shape[1] + + @property + def nfreq(self): + return self._vis.shape[2] + + def apply_flags(self, arr): + return arr[self._weight != 0.0] + + @property + def flags(self): + return self._weight == 0.0 + + @property + def mask(self): + return self._weight > 0.0 + + def max_snr(self): + return np.max(np.abs(self.apply_flags(self._vis * np.sqrt(self._weight)))) + + def fraction_useful(self): + return self.apply_flags(self._weight).size / self._weight.size + + +class SingleDishObservation(_Observation): + def __init__(self): + self._weight + self._vis + self._direction + self._polarization + self._freq + + @onlymaster + def save(self, file_name, compress): + raise NotImplementedError + # dct = dict( + # vis=self._vis, + # weight=self._weight, + # freq=self._freq, + # polarization=self._polarization.to_list(), + # direction=self._direction.to_list(), + # ) + # for ii, vv in enumerate(self._antpos.to_list()): + # if vv is None: + # vv = np.array([]) + # dct[f"antpos{ii}"] = vv + # f = np.savez_compressed if compress else np.savez + # f(file_name, **dct) + + @staticmethod + def load(file_name): + raise NotImplementedError + # dct = dict(np.load(file_name)) + # antpos = [] + # for ii in range(4): + # val = dct[f"antpos{ii}"] + # if val.size == 0: + # val = None + # antpos.append(val) + # pol = Polarization.from_list(dct["polarization"]) + # direction = Direction.from_list(dct["direction"]) + # return Observation( + # AntennaPositions.from_list(antpos), + # dct["vis"], + # dct["weight"], + # pol, + # dct["freq"], + # direction, + # ) + + def __eq__(self, other): + raise NotImplementedError + # if not isinstance(other, Observation): + # return False + # if ( + # self._vis.dtype != other._vis.dtype + # or self._weight.dtype != other._weight.dtype + # ): + # return False + # return compare_attributes( + # self, + # other, + # ("_direction", "_polarization", "_freq", "_antpos", "_vis", "_weight"), + # ) + + def __getitem__(self, slc): + raise NotImplementedError + # return Observation( + # self._antpos[slc], + # self._vis[:, slc], + # self._weight[:, slc], + # self._polarization, + # self._freq, + # self._direction, + # ) + + +class Observation(_Observation): """Observation data This class contains all the data and information about an observation. @@ -61,23 +187,6 @@ class Observation: self._freq = freq self._direction = direction - def apply_flags(self, arr): - return arr[self._weight != 0.0] - - @property - def flags(self): - return self._weight == 0.0 - - @property - def mask(self): - return self._weight > 0.0 - - def max_snr(self): - return np.max(np.abs(self.apply_flags(self._vis * np.sqrt(self._weight)))) - - def fraction_useful(self): - return self.apply_flags(self._weight).size / self._weight.size - @onlymaster def save(self, file_name, compress): dct = dict( @@ -172,40 +281,6 @@ class Observation: uvlen = np.linalg.norm(self.uvw, axis=1) return np.outer(uvlen, self._freq / SPEEDOFLIGHT) - @property - def vis(self): - dom = [ift.UnstructuredDomain(ss) for ss in self._vis.shape] - return ift.makeField(dom, self._vis) - - @property - def weight(self): - dom = [ift.UnstructuredDomain(ss) for ss in self._weight.shape] - return ift.makeField(dom, self._weight) - - @property - def freq(self): - return self._freq - - @property - def polarization(self): - return self._polarization - - @property - def direction(self): - return self._direction - - @property - def npol(self): - return self._vis.shape[0] - - @property - def nrow(self): - return self._vis.shape[1] - - @property - def nfreq(self): - return self._vis.shape[2] - def tmin_tmax(*args): """ -- GitLab From beea25d0300b2aedaba0f3c50a5af405aeb16845 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Sun, 7 Feb 2021 21:50:41 +0100 Subject: [PATCH 14/37] Add SingleDishObservation --- misc/convert_alma_data.py | 13 ++++- resolve/__init__.py | 2 +- resolve/direction.py | 39 ++++++++++++- resolve/observation.py | 117 ++++++++++++++++++-------------------- 4 files changed, 105 insertions(+), 66 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 65d7e1c5..0962f982 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -13,7 +13,7 @@ import resolve as rve def cleanup(weight, vis, freq, uvw=None): assert weight.ndim == 2 assert vis.shape == weight.shape - assert freq.ndim==1 + assert freq.ndim == 1 assert freq.size == vis.shape[1] assert uvw.shape[0] == vis.shape[0] # Clean up rows @@ -96,10 +96,15 @@ for f, spectral_window, single_dish, fieldid in [ if single_dish: with table(f, readonly=True) as t: - pointings = taql("select DIRECTION from $t::POINTING where ROWID() in [select from $t where DATA_DESC_ID=$spectral_window giving [ROWID()]]").getcol("DIRECTION") + pointings = taql( + "select DIRECTION from $t::POINTING where ROWID() in [select from $t where DATA_DESC_ID=$spectral_window giving [ROWID()]]" + ).getcol("DIRECTION") mywgt, myvis, myfreq, mypointings = cleanup(wgt, vis, freq, pointings) + assert mypointings.shape[1] == 1 + mypointings = mypointings[:, 0] + mypointings = rve.Directions(mypointings, 2000) obs = rve.SingleDishObservation( - np.ascontiguousarray(mypointings), + mypointings, np.ascontiguousarray(myvis[None]), np.ascontiguousarray(mywgt[None]), rve.Polarization.trivial(), @@ -122,3 +127,5 @@ for f, spectral_window, single_dish, fieldid in [ rve.Direction(field_directions[ifield], 2000), ) obs.save(f"{outfbase}_field{ifield}.npz", compress=False) + check = rve.Observation.load(f"{outfbase}_field{ifield}.npz") + assert check == obs diff --git a/resolve/__init__.py b/resolve/__init__.py index 5b1b670e..b74b7fda 100644 --- a/resolve/__init__.py +++ b/resolve/__init__.py @@ -16,7 +16,7 @@ from .multi_frequency.operators import ( MfWeightingInterpolation, WienerIntegrations, ) -from .observation import Observation, tmin_tmax, unique_antennas, unique_times +from .observation import * from .plotter import MfPlotter, Plotter from .points import PointInserter from .polarization import Polarization, polarization_matrix_exponential diff --git a/resolve/direction.py b/resolve/direction.py index cc148054..a6b7b2fc 100644 --- a/resolve/direction.py +++ b/resolve/direction.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2020 Max-Planck-Society +# Copyright(C) 2020-2021 Max-Planck-Society # Author: Philipp Arras from .util import compare_attributes, my_asserteq @@ -42,3 +42,40 @@ class Direction: if not isinstance(other, Direction): return False return compare_attributes(self, other, ("_pc", "_e")) + + +class Directions: + def __init__(self, phase_centers, equinox): + assert phase_centers.ndim == 2 + assert phase_centers.shape[1] == 2 + self._pc = phase_centers + self._e = float(equinox) + + @property + def phase_centers(self): + return self._pc + + @property + def equinox(self): + return self._e + + def __repr__(self): + return f"Directions({self._pc}, equinox={self._e})" + + def to_list(self): + return [self._pc, self._e] + + def __len__(self): + return self._pc.shape[0] + + @staticmethod + def from_list(lst): + return Direction(lst[0], lst[1]) + + def __eq__(self, other): + if not isinstance(other, Direction): + return False + return compare_attributes(self, other, ("_pc", "_e")) + + def __getitem__(self, slc): + return Directions(self._pc[slc], self._e) diff --git a/resolve/observation.py b/resolve/observation.py index c89c1253..44355333 100644 --- a/resolve/observation.py +++ b/resolve/observation.py @@ -8,7 +8,7 @@ import nifty7 as ift from .antenna_positions import AntennaPositions from .constants import SPEEDOFLIGHT -from .direction import Direction +from .direction import Direction, Directions from .mpi import onlymaster from .polarization import Polarization from .util import compare_attributes, my_assert, my_assert_isinstance, my_asserteq @@ -68,76 +68,71 @@ class _Observation: class SingleDishObservation(_Observation): - def __init__(self): - self._weight - self._vis - self._direction - self._polarization - self._freq + def __init__(self, pointings, data, weight, polarization, freq): + my_assert_isinstance(pointings, Directions) + my_assert_isinstance(polarization, Polarization) + my_assert(data.dtype in [np.float32, np.float64]) + nrows = len(pointings) + my_asserteq(weight.shape, data.shape) + my_asserteq(data.shape, (len(polarization), nrows, len(freq))) + my_asserteq(nrows, data.shape[1]) + + data.flags.writeable = False + weight.flags.writeable = False + + my_assert(np.all(weight >= 0.0)) + my_assert(np.all(np.isfinite(data))) + my_assert(np.all(np.isfinite(weight))) + + self._pointings = pointings + self._vis = data + self._weight = weight + self._polarization = polarization + self._freq = freq @onlymaster def save(self, file_name, compress): - raise NotImplementedError - # dct = dict( - # vis=self._vis, - # weight=self._weight, - # freq=self._freq, - # polarization=self._polarization.to_list(), - # direction=self._direction.to_list(), - # ) - # for ii, vv in enumerate(self._antpos.to_list()): - # if vv is None: - # vv = np.array([]) - # dct[f"antpos{ii}"] = vv - # f = np.savez_compressed if compress else np.savez - # f(file_name, **dct) + p = self._pointings.to_list() + dct = dict( + vis=self._vis, + weight=self._weight, + freq=self._freq, + polarization=self._polarization.to_list(), + pointings0=p[0], + pointings1=p[1], + ) + f = np.savez_compressed if compress else np.savez + f(file_name, **dct) @staticmethod def load(file_name): - raise NotImplementedError - # dct = dict(np.load(file_name)) - # antpos = [] - # for ii in range(4): - # val = dct[f"antpos{ii}"] - # if val.size == 0: - # val = None - # antpos.append(val) - # pol = Polarization.from_list(dct["polarization"]) - # direction = Direction.from_list(dct["direction"]) - # return Observation( - # AntennaPositions.from_list(antpos), - # dct["vis"], - # dct["weight"], - # pol, - # dct["freq"], - # direction, - # ) + dct = dict(np.load(file_name)) + pol = Polarization.from_list(dct["polarization"]) + pointings = Directions.from_list(dct["pointings0"], dct["pointings1"]) + return SingleDishObservation( + pointings, dct["vis"], dct["weight"], pol, dct["freq"] + ) def __eq__(self, other): - raise NotImplementedError - # if not isinstance(other, Observation): - # return False - # if ( - # self._vis.dtype != other._vis.dtype - # or self._weight.dtype != other._weight.dtype - # ): - # return False - # return compare_attributes( - # self, - # other, - # ("_direction", "_polarization", "_freq", "_antpos", "_vis", "_weight"), - # ) + if not isinstance(other, Observation): + return False + if ( + self._vis.dtype != other._vis.dtype + or self._weight.dtype != other._weight.dtype + ): + return False + return compare_attributes( + self, other, ("_polarization", "_freq", "_pointings", "_vis", "_weight") + ) def __getitem__(self, slc): - raise NotImplementedError - # return Observation( - # self._antpos[slc], - # self._vis[:, slc], - # self._weight[:, slc], - # self._polarization, - # self._freq, - # self._direction, - # ) + return SingleDishObservation( + self._pointings[slc], + self._vis[:, slc], + self._weight[:, slc], + self._polarization, + self._freq, + ) class Observation(_Observation): -- GitLab From f295d5bc6504710953c1271c04f7df89d2aeac16 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Sun, 7 Feb 2021 22:04:00 +0100 Subject: [PATCH 15/37] Load single dish data --- demo/mosaicing.py | 22 +++++++++++++++++++--- resolve/direction.py | 2 +- resolve/observation.py | 6 +++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 1cd74980..803472fe 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -31,6 +31,7 @@ def main(): for ii in range(5, 153) } interobs = {**interobs_compact, **interobs_extended} + sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) @@ -38,17 +39,32 @@ def main(): for vv in interobs.values(): xs.append(vv.direction.phase_center[0]) ys.append(vv.direction.phase_center[1]) + for vv in sdobs.values(): + foo, bar = vv.pointings.phase_centers.T + xs.extend(foo) + ys.extend(bar) xs, ys = np.array(xs), np.array(ys) + # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 margin = 1 * rve.ARCMIN2RAD - plt.scatter(xs, ys, label="Extended") - plt.scatter(*global_phase_center_casa, label="Phase center CASA") - plt.scatter(*global_phase_center, label="Phase center resolve") xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") + + xs, ys = [], [] + for vv in interobs.values(): + xs.append(vv.direction.phase_center[0]) + ys.append(vv.direction.phase_center[1]) + xs, ys = np.array(xs), np.array(ys) + plt.scatter(xs, ys, label="Interferometer") + for kk, vv in sdobs.items(): + plt.scatter( + *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 + ) + plt.scatter(*global_phase_center_casa, label="Phase center CASA") + plt.scatter(*global_phase_center, label="Phase center resolve") plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) plt.gca().invert_xaxis() diff --git a/resolve/direction.py b/resolve/direction.py index a6b7b2fc..5d2acdad 100644 --- a/resolve/direction.py +++ b/resolve/direction.py @@ -70,7 +70,7 @@ class Directions: @staticmethod def from_list(lst): - return Direction(lst[0], lst[1]) + return Directions(lst[0], lst[1]) def __eq__(self, other): if not isinstance(other, Direction): diff --git a/resolve/observation.py b/resolve/observation.py index 44355333..3d2a0a28 100644 --- a/resolve/observation.py +++ b/resolve/observation.py @@ -108,7 +108,7 @@ class SingleDishObservation(_Observation): def load(file_name): dct = dict(np.load(file_name)) pol = Polarization.from_list(dct["polarization"]) - pointings = Directions.from_list(dct["pointings0"], dct["pointings1"]) + pointings = Directions.from_list([dct["pointings0"], dct["pointings1"]]) return SingleDishObservation( pointings, dct["vis"], dct["weight"], pol, dct["freq"] ) @@ -134,6 +134,10 @@ class SingleDishObservation(_Observation): self._freq, ) + @property + def pointings(self): + return self._pointings + class Observation(_Observation): """Observation data -- GitLab From 607171f44da67f3b2abfeecb50eecb93c3e825f7 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 8 Feb 2021 10:13:47 +0100 Subject: [PATCH 16/37] Tweak beam --- demo/mosaicing.py | 5 ++--- resolve/primary_beam.py | 12 +++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 803472fe..adffda54 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -76,12 +76,11 @@ def main(): beam_directions = {} for kk, obs in interobs.items(): - extended_blockage = np.array([10.7, 0.75]) # m - extended_blockage *= np.mean(obs.freq) / rve.SPEEDOFLIGHT + assert np.min(obs.freq)/np.max(obs.freq) > 0.99 beam_directions[kk] = rve.BeamDirection( obs.direction.phase_center[0] - global_phase_center[0], obs.direction.phase_center[1] - global_phase_center[1], - lambda x: rve.alma_beam_func(*extended_blockage, x), + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(obs.freq), x), 0.1, ) diff --git a/resolve/primary_beam.py b/resolve/primary_beam.py index b4a186dd..f627f5e2 100644 --- a/resolve/primary_beam.py +++ b/resolve/primary_beam.py @@ -7,7 +7,7 @@ import scipy.special as sc import nifty7 as ift -from .constants import ARCMIN2RAD +from .constants import ARCMIN2RAD, SPEEDOFLIGHT from .util import my_assert @@ -179,14 +179,16 @@ def vla_beam(domain, freq): return ift.makeOp(ift.makeField(dom, beam)) -def alma_beam_func(D, d, x): +def alma_beam_func(D, d, freq, x): assert isinstance(x, np.ndarray) assert x.ndim < 3 - assert np.max(np.abs(x)) < np.pi/np.sqrt(2) + assert np.max(np.abs(x)) < np.pi / np.sqrt(2) + + a = freq / SPEEDOFLIGHT b = d / D - x = np.pi * D * x + x = np.pi * a * D * x mask = x == 0.0 x[mask] = 1 sol = 2 / (x * (1 - b ** 2)) * (sc.jn(1, x) - b * sc.jn(1, x * b)) sol[mask] = 1 - return sol**2 + return sol ** 2 -- GitLab From d9a9411b0867e883c36bc479c0df9ce6127a268e Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 8 Feb 2021 11:54:45 +0100 Subject: [PATCH 17/37] Implement single dish likelihood --- demo/mosaicing.py | 63 ++++++++++++++++++++++++++------- resolve/likelihood.py | 30 ++++++++++++++++ resolve/mosaicing/__init__.py | 1 + resolve/mosaicing/sky_slicer.py | 1 + 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index adffda54..85327700 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -1,8 +1,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2020 Max-Planck-Society +# Copyright(C) 2019-2021 Max-Planck-Society # Author: Philipp Arras import numpy as np +from functools import reduce +from operator import add import matplotlib.pyplot as plt import nifty7 as ift @@ -32,9 +34,12 @@ def main(): } interobs = {**interobs_compact, **interobs_extended} sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} + assert len(set(sdobs.keys()) & set(interobs.keys())) == 0 rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) + # Compute phase center and define domain + # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel xs, ys = [], [] for vv in interobs.values(): xs.append(vv.direction.phase_center[0]) @@ -44,15 +49,18 @@ def main(): xs.extend(foo) ys.extend(bar) xs, ys = np.array(xs), np.array(ys) - - # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 margin = 1 * rve.ARCMIN2RAD xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") + npix = np.array([1800, 1800]) + fov = np.array([xfov, yfov]) + dom = ift.RGSpace(npix, fov / npix) + # End compute phase center and define domain + # Plot pointings xs, ys = [], [] for vv in interobs.values(): xs.append(vv.direction.phase_center[0]) @@ -73,33 +81,62 @@ def main(): if rve.mpi.master: plt.savefig("pointings.png") plt.close() + # End plot pointings + + # Single dish likelihood + single_dish_response = [] + for kk, oo in sdobs.items(): + assert np.min(oo.freq) / np.max(oo.freq) > 0.99 + R = rve.SingleDishResponse( + oo, + dom, + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + global_phase_center, + ) + single_dish_response.append(R.ducktape_left(kk)) + single_dish_response = reduce(add, single_dish_response) + dsd = ift.MultiField.from_dict({kk: o.vis for kk, o in sdobs.items()}) + invcovsd = ift.makeOp( + ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) + ) + lhsd = ift.GaussianEnergy(dsd, invcovsd) @ single_dish_response + imsave("sd_dirty.png", single_dish_response.adjoint(invcovsd(dsd))) + # End single dish likelihood + logsky = ift.SimpleCorrelatedField( + dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) + ) + sky = logsky.exp() + # p = ift.Plot() + # for _ in range(9): + # p.add(logsky(ift.from_random(logsky.domain))) + # p.output(name="prior.png") + + # Interferometer likelihood beam_directions = {} for kk, obs in interobs.items(): - assert np.min(obs.freq)/np.max(obs.freq) > 0.99 + assert np.min(obs.freq) / np.max(obs.freq) > 0.99 beam_directions[kk] = rve.BeamDirection( obs.direction.phase_center[0] - global_phase_center[0], obs.direction.phase_center[1] - global_phase_center[1], lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(obs.freq), x), 0.1, ) - - npix = np.array([1800, 1800]) - fov = np.array([xfov, yfov]) - dom = ift.RGSpace(npix, fov / npix) - logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) - ) skyslicer = rve.SkySlicer(logsky.target, beam_directions) + print(skyslicer.target) ift.extra.check_linear_operator(skyslicer) - lh = rve.ImagingLikelihood(interobs, skyslicer @ logsky.exp()) + lhinter = rve.ImagingLikelihood(interobs, skyslicer) + # End interferometer likelihood + lh = (lhinter + lhsd) @ sky + + # Plots R = rve.StokesIResponse(interobs, skyslicer.target) @ skyslicer vis = ift.MultiField.from_dict({kk: o.vis for kk, o in interobs.items()}) weight = ift.MultiField.from_dict({kk: o.weight for kk, o in interobs.items()}) - imsave("total_dirty.png", R.adjoint(vis * weight)) imsave("skyslicer_adjoint.png", skyslicer.adjoint(ift.full(skyslicer.target, 1.0))) + # End plots plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) diff --git a/resolve/likelihood.py b/resolve/likelihood.py index e588b5e9..c714f35c 100644 --- a/resolve/likelihood.py +++ b/resolve/likelihood.py @@ -63,6 +63,36 @@ def _varcov(observation, Rs, inverse_covariance_operator): return _build_varcov_gauss_lh_nres(residual, inverse_covariance_operator, dtype) +def SingleDishLikelihood( + observation, + sky_operator, + inverse_covariance_operator=None, + calibration_operator=None, +): + if inverse_covariance_operator is not None or calibration_operator is not None: + raise NotImplementedError + mosaicing = isinstance(observation, dict) + if mosaicing: + vis = ift.MultiField.from_dict({kk: o.vis for kk, o in observation.items()}) + weight = ift.MultiField.from_dict( + {kk: o.weight for kk, o in observation.items()} + ) + single_dish_response = [] + for kk, oo in observation.items(): + assert np.min(oo.freq) / np.max(oo.freq) > 0.99 + R = rve.SingleDishResponse( + oo, + sky_operator.target, + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + global_phase_center, + ) + single_dish_response.append(R.ducktape_left(kk)) + from functools import reduce + from operator import add + + single_dish_response = reduce(add, single_dish_response) + + def ImagingLikelihood( observation, sky_operator, diff --git a/resolve/mosaicing/__init__.py b/resolve/mosaicing/__init__.py index 6802616c..a346d91e 100644 --- a/resolve/mosaicing/__init__.py +++ b/resolve/mosaicing/__init__.py @@ -1 +1,2 @@ from .sky_slicer import * +from .single_dish_response import * diff --git a/resolve/mosaicing/sky_slicer.py b/resolve/mosaicing/sky_slicer.py index 3f377890..8ae42236 100644 --- a/resolve/mosaicing/sky_slicer.py +++ b/resolve/mosaicing/sky_slicer.py @@ -95,6 +95,7 @@ class BeamDirection: patch_npix = fov/dst patch_npix = (np.ceil(patch_npix/2)*2).astype(int) + # Convention: pointing convention (see also SingleDishResponse) shift = np.array([-self._dx, self._dy]) patch_center_unrounded = np.array(npix) / 2 + shift / dst ipix_patch_center = np.round(patch_center_unrounded).astype(int) -- GitLab From 421b4a0364a632b0a95508c792d737948a7b22e8 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 8 Feb 2021 12:08:48 +0100 Subject: [PATCH 18/37] Cosmetics --- demo/mosaicing.py | 14 +++++--------- resolve/mosaicing/sky_slicer.py | 2 ++ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 85327700..1ddceabf 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -25,8 +25,6 @@ def main(): interobs_extended = { f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") for ii in range(5, 135) - # for ii in [50, 58, 59, 68, 69, 70, 88] - # for ii in [68, 69, 70] } interobs_compact = { f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") @@ -54,7 +52,8 @@ def main(): margin = 1 * rve.ARCMIN2RAD xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin - print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") + if rve.mpi.master: + print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") npix = np.array([1800, 1800]) fov = np.array([xfov, yfov]) dom = ift.RGSpace(npix, fov / npix) @@ -107,10 +106,6 @@ def main(): dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) ) sky = logsky.exp() - # p = ift.Plot() - # for _ in range(9): - # p.add(logsky(ift.from_random(logsky.domain))) - # p.output(name="prior.png") # Interferometer likelihood beam_directions = {} @@ -123,7 +118,6 @@ def main(): 0.1, ) skyslicer = rve.SkySlicer(logsky.target, beam_directions) - print(skyslicer.target) ift.extra.check_linear_operator(skyslicer) lhinter = rve.ImagingLikelihood(interobs, skyslicer) # End interferometer likelihood @@ -145,7 +139,9 @@ def main(): # plotter.add_histogram( # "normalized residuals (original weights)", lh.normalized_residual # ) - ham = ift.StandardHamiltonian(lh, ift.AbsDeltaEnergyController(0.5, 3, 300, name="Sampling")) + ham = ift.StandardHamiltonian( + lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") + ) fld = 0.1 * ift.from_random(lh.domain) state = rve.MinimizationState(fld, []) mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) diff --git a/resolve/mosaicing/sky_slicer.py b/resolve/mosaicing/sky_slicer.py index 8ae42236..fb951280 100644 --- a/resolve/mosaicing/sky_slicer.py +++ b/resolve/mosaicing/sky_slicer.py @@ -27,7 +27,9 @@ class SkySlicer(ift.LinearOperator): self._domain = ift.makeDomain(domain) t, b, s = {}, {}, {} for kk, vv in self._bd.items(): + print("\r" + kk, end="") t[kk], s[kk], b[kk] = vv.slice_target(self._domain) + print() self._beams = b self._slices = s self._target = ift.makeDomain(t) -- GitLab From 6b3978a28f7fbb049f2b2bd78956d699ea2bab74 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 8 Feb 2021 12:08:56 +0100 Subject: [PATCH 19/37] Add file --- resolve/mosaicing/single_dish_response.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 resolve/mosaicing/single_dish_response.py diff --git a/resolve/mosaicing/single_dish_response.py b/resolve/mosaicing/single_dish_response.py new file mode 100644 index 00000000..d4aa2b87 --- /dev/null +++ b/resolve/mosaicing/single_dish_response.py @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2021 Max-Planck-Society +# Author: Philipp Arras + +import numpy as np + +import nifty7 as ift + +from ..observation import SingleDishObservation + + +def SingleDishResponse(observation, domain, beam_function, global_phase_center): + assert isinstance(observation, SingleDishObservation) + domain = ift.makeDomain(domain) + assert len(domain) == 1 + codomain = domain[0].get_default_codomain() + kernel = codomain.get_conv_kernel_from_func(beam_function) + HT = ift.HartleyOperator(domain, codomain) + conv = HT.inverse @ ift.makeOp(kernel) @ HT.scale(domain.total_volume()) + # FIXME Move into tests + fld = ift.from_random(conv.domain) + ift.extra.assert_allclose(conv(fld).integrate(), fld.integrate()) + pc = observation.pointings.phase_centers.T - np.array(global_phase_center)[:, None] + pc = pc + (np.array(domain.shape)*np.array(domain[0].distances)/2)[:, None] + # Convention: pointing convention (see also BeamDirection) + pc[0] *= -1 + interp = ift.LinearInterpolator(domain, pc) + bc = ift.ContractionOperator(observation.vis.domain, (0, 2)).adjoint + # NOTE The volume factor above `domain.total_volume()` and the volume factor + # below `domain[0].scalar_dvol` cancel each other. They are left in the + # code such that the convolution leaves the integral invariant. + return bc @ interp @ conv.scale(domain[0].scalar_dvol) -- GitLab From 71ce09c2dbf910a9ec05c95dfe6a524637a5fdec Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 8 Feb 2021 20:15:06 +0100 Subject: [PATCH 20/37] Restructure and add plots --- demo/cfg_mosaicing.py | 99 ++++++++++++++++++++++++++++++++++ demo/mosaicing.py | 117 +---------------------------------------- demo/post_mosaicing.py | 112 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 115 deletions(-) create mode 100644 demo/cfg_mosaicing.py create mode 100644 demo/post_mosaicing.py diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py new file mode 100644 index 00000000..d34490d4 --- /dev/null +++ b/demo/cfg_mosaicing.py @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2019-2021 Max-Planck-Society +# Author: Philipp Arras + +import numpy as np +from functools import reduce +from operator import add + +import nifty7 as ift +import resolve as rve + + +diffusefluxlevel = 20 +npix = np.array([1800, 1800]) + +rve.set_nthreads(2) +rve.set_wgridding(False) +interobs_extended = { + f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") + for ii in range(5, 135) +} +interobs_compact = { + f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") + for ii in range(5, 153) +} +interobs = {**interobs_compact, **interobs_extended} +sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} +assert len(set(sdobs.keys()) & set(interobs.keys())) == 0 +rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) +# Compute phase center and define domain +# Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel +xs, ys = [], [] +for vv in interobs.values(): + xs.append(vv.direction.phase_center[0]) + ys.append(vv.direction.phase_center[1]) +for vv in sdobs.values(): + foo, bar = vv.pointings.phase_centers.T + xs.extend(foo) + ys.extend(bar) +xs, ys = np.array(xs), np.array(ys) +global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 +global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 +margin = 1 * rve.ARCMIN2RAD +xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin +yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin +fov = np.array([xfov, yfov]) +dom = ift.RGSpace(npix, fov / npix) +# End compute phase center and define domain + +# Single dish likelihood +responsesd = [] +for kk, oo in sdobs.items(): + # Single dish response does not support multifrequency + assert np.min(oo.freq) / np.max(oo.freq) > 0.99 + R = rve.SingleDishResponse( + oo, + dom, + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + global_phase_center, + ) + responsesd.append(R.ducktape_left(kk)) +responsesd = reduce(add, responsesd) +dsd = ift.MultiField.from_dict({kk: o.vis for kk, o in sdobs.items()}) +invcovsd = ift.makeOp( + ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) +) +lhsd = ift.GaussianEnergy(dsd, invcovsd) @ responsesd +# End single dish likelihood + +# Sky model +logsky = ift.SimpleCorrelatedField( + dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) +) +sky = logsky.exp() +# End sky model + + +# Interferometer likelihood +def get_beamdirections(obs): + beam_directions = {} + for kk, oo in obs.items(): + assert np.min(oo.freq) / np.max(oo.freq) > 0.99 + beam_directions[kk] = rve.BeamDirection( + oo.direction.phase_center[0] - global_phase_center[0], + oo.direction.phase_center[1] - global_phase_center[1], + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + 0.1, + ) + return beam_directions + + +beamsc = get_beamdirections(interobs_compact) +beamsext = get_beamdirections(interobs_extended) +skyslicer_compact = rve.SkySlicer(logsky.target, beamsc) +skyslicer_extended = rve.SkySlicer(logsky.target, beamsext) +skyslicer = rve.SkySlicer(logsky.target, {**beamsc, **beamsext}) +ift.extra.check_linear_operator(skyslicer) +lhinter = rve.ImagingLikelihood(interobs, skyslicer) +# End interferometer likelihood diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 1ddceabf..21eb5b72 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -2,13 +2,11 @@ # Copyright(C) 2019-2021 Max-Planck-Society # Author: Philipp Arras -import numpy as np -from functools import reduce -from operator import add import matplotlib.pyplot as plt import nifty7 as ift import resolve as rve +from cfg_mosaicing import lhinter, lhsd, logsky, sky, xfov, yfov @rve.onlymaster @@ -18,123 +16,12 @@ def imsave(fname, field): def main(): - rve.set_nthreads(2) - rve.set_wgridding(False) - diffusefluxlevel = 20 - - interobs_extended = { - f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") - for ii in range(5, 135) - } - interobs_compact = { - f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") - for ii in range(5, 153) - } - interobs = {**interobs_compact, **interobs_extended} - sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} - assert len(set(sdobs.keys()) & set(interobs.keys())) == 0 - - rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) - - # Compute phase center and define domain - # Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel - xs, ys = [], [] - for vv in interobs.values(): - xs.append(vv.direction.phase_center[0]) - ys.append(vv.direction.phase_center[1]) - for vv in sdobs.values(): - foo, bar = vv.pointings.phase_centers.T - xs.extend(foo) - ys.extend(bar) - xs, ys = np.array(xs), np.array(ys) - global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 - global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 - margin = 1 * rve.ARCMIN2RAD - xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin - yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin if rve.mpi.master: print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") - npix = np.array([1800, 1800]) - fov = np.array([xfov, yfov]) - dom = ift.RGSpace(npix, fov / npix) - # End compute phase center and define domain - - # Plot pointings - xs, ys = [], [] - for vv in interobs.values(): - xs.append(vv.direction.phase_center[0]) - ys.append(vv.direction.phase_center[1]) - xs, ys = np.array(xs), np.array(ys) - plt.scatter(xs, ys, label="Interferometer") - for kk, vv in sdobs.items(): - plt.scatter( - *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 - ) - plt.scatter(*global_phase_center_casa, label="Phase center CASA") - plt.scatter(*global_phase_center, label="Phase center resolve") - plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) - plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) - plt.gca().invert_xaxis() - plt.gca().set_aspect("equal") - plt.legend() - if rve.mpi.master: - plt.savefig("pointings.png") - plt.close() - # End plot pointings - - # Single dish likelihood - single_dish_response = [] - for kk, oo in sdobs.items(): - assert np.min(oo.freq) / np.max(oo.freq) > 0.99 - R = rve.SingleDishResponse( - oo, - dom, - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), - global_phase_center, - ) - single_dish_response.append(R.ducktape_left(kk)) - single_dish_response = reduce(add, single_dish_response) - dsd = ift.MultiField.from_dict({kk: o.vis for kk, o in sdobs.items()}) - invcovsd = ift.makeOp( - ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) - ) - lhsd = ift.GaussianEnergy(dsd, invcovsd) @ single_dish_response - imsave("sd_dirty.png", single_dish_response.adjoint(invcovsd(dsd))) - # End single dish likelihood - - logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) - ) - sky = logsky.exp() - - # Interferometer likelihood - beam_directions = {} - for kk, obs in interobs.items(): - assert np.min(obs.freq) / np.max(obs.freq) > 0.99 - beam_directions[kk] = rve.BeamDirection( - obs.direction.phase_center[0] - global_phase_center[0], - obs.direction.phase_center[1] - global_phase_center[1], - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(obs.freq), x), - 0.1, - ) - skyslicer = rve.SkySlicer(logsky.target, beam_directions) - ift.extra.check_linear_operator(skyslicer) - lhinter = rve.ImagingLikelihood(interobs, skyslicer) - # End interferometer likelihood - lh = (lhinter + lhsd) @ sky - - # Plots - R = rve.StokesIResponse(interobs, skyslicer.target) @ skyslicer - vis = ift.MultiField.from_dict({kk: o.vis for kk, o in interobs.items()}) - weight = ift.MultiField.from_dict({kk: o.weight for kk, o in interobs.items()}) - imsave("total_dirty.png", R.adjoint(vis * weight)) - imsave("skyslicer_adjoint.png", skyslicer.adjoint(ift.full(skyslicer.target, 1.0))) - # End plots - plotter = rve.Plotter("png", "plots") plotter.add("logsky", logsky) - plotter.add("sky", logsky.exp(), cmap="inferno_r") + plotter.add("sky", sky, cmap="inferno_r") plotter.add("power spectrum logsky", logsky.power_spectrum) # plotter.add_histogram( # "normalized residuals (original weights)", lh.normalized_residual diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py new file mode 100644 index 00000000..ca3d257a --- /dev/null +++ b/demo/post_mosaicing.py @@ -0,0 +1,112 @@ +from functools import reduce +from operator import add + +import matplotlib.pyplot as plt +import numpy as np + +import nifty7 as ift +import resolve as rve +from cfg_mosaicing import ( + global_phase_center, + global_phase_center_casa, + interobs, + interobs_compact, + interobs_extended, + responsesd, + sky, + sdobs, + skyslicer, + skyslicer_compact, + skyslicer_extended, + xfov, + yfov, +) + + +def plot_pointings(): + xs, ys = [], [] + for vv in interobs.values(): + xs.append(vv.direction.phase_center[0]) + ys.append(vv.direction.phase_center[1]) + xs, ys = np.array(xs), np.array(ys) + plt.scatter(xs, ys, label="Interferometer") + for kk, vv in sdobs.items(): + plt.scatter( + *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 + ) + plt.scatter(*global_phase_center_casa, label="Phase center CASA") + plt.scatter(*global_phase_center, label="Phase center resolve") + plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) + plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) + plt.gca().invert_xaxis() + plt.gca().set_aspect("equal") + plt.legend() + plt.savefig("pointings.png") + plt.close() + + +def imshow(ax, fld, **kwargs): + kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} + return ax.imshow(fld.val.T, **kwargs) + + +def imshow_symmetric(ax, fld): + lim = np.max(np.abs(fld.val)) + im = imshow(ax, fld, vmin=-lim, vmax=lim, cmap="seismic") + plt.colorbar(im, ax=ax) + + +def dirty_images(mean_sky): + responses = [responsesd] + responses.extend( + [ + rve.StokesIResponse(obs, skyslc.target) @ skyslc + for obs, skyslc in ( + (interobs_extended, skyslicer_extended), + (interobs_compact, skyslicer_compact), + (interobs, skyslicer), + ) + ] + ) + responses.append(reduce(add, responses)) + + data = [ + ift.MultiField.from_dict({kk: o.vis for kk, o in obs.items()}) + for obs in [sdobs, interobs_extended, interobs_compact, interobs] + ] + data.append(reduce(lambda a, b: a.unite(b), data)) + + weights = [ + ift.MultiField.from_dict({kk: o.weight for kk, o in obs.items()}) + for obs in [sdobs, interobs_extended, interobs_compact, interobs] + ] + weights.append(reduce(lambda a, b: a.unite(b), weights)) + + fig, axs = plt.subplots(5, 5, figsize=(20, 20), sharey=True, sharex=True) + + for ii, (R, icov, d) in enumerate(zip(responses, weights, data)): + jj = 0 + imshow_symmetric(axs[ii, jj], R.adjoint(ift.full(R.target, 1.0))) + jj += 1 + imshow_symmetric(axs[ii, jj], R.adjoint(icov * d)) + jj += 1 + imshow_symmetric(axs[ii, jj], R.adjoint(icov.sqrt() * d)) + jj += 1 + residual = d - R(mean_sky) + imshow_symmetric(axs[ii, jj], R.adjoint(icov * residual)) + jj += 1 + imshow_symmetric(axs[ii, jj], R.adjoint(icov.sqrt() * residual)) + plt.tight_layout() + plt.savefig("dirty_overview.png") + + +def main(): + state = rve.MinimizationState.load("iter8") + skysc = ift.StatCalculator() + for ss in state: + skysc.add(sky.force(ss)) + dirty_images(skysc.mean) + + +if __name__ == "__main__": + main() -- GitLab From ded45f07dee955669f5cf11153b706a6ca289790 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 10 Feb 2021 10:58:33 +0100 Subject: [PATCH 21/37] Work --- demo/cfg_mosaicing.py | 13 +++++++++++++ demo/mosaicing.py | 31 ++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index d34490d4..5ac84652 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -65,6 +65,19 @@ invcovsd = ift.makeOp( ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) ) lhsd = ift.GaussianEnergy(dsd, invcovsd) @ responsesd + + +def VariableCovarianceGaussianEnergy(data, signal_response, invcov): + resi = ift.Adder(dsd, True).ducktape_left("resi") @ responsesd + dtype = {kk: vv.dtype for kk, vv in data.items()} + return ift.VariableCovarianceGaussianEnergy(data.domain, "resi", "icov", dtype) @ ( + resi + invcov.ducktape_left("icov") + ) + + +assert list(dsd.domain.keys()) == ["sd0"] +invcovop = ift.FieldAdapter(dsd.domain["sd0"], "sd0invcov").ducktape_left("sd0").exp() +lhsd_varcov = VariableCovarianceGaussianEnergy(dsd, responsesd, invcovop) # End single dish likelihood # Sky model diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 21eb5b72..cdecee56 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -4,9 +4,9 @@ import matplotlib.pyplot as plt +import cfg_mosaicing as c import nifty7 as ift import resolve as rve -from cfg_mosaicing import lhinter, lhsd, logsky, sky, xfov, yfov @rve.onlymaster @@ -17,15 +17,20 @@ def imsave(fname, field): def main(): if rve.mpi.master: - print(f"Field of view: {xfov/rve.ARCMIN2RAD:.2f}' x {yfov/rve.ARCMIN2RAD:.2f}'") - lh = (lhinter + lhsd) @ sky - plotter = rve.Plotter("png", "plots") - plotter.add("logsky", logsky) - plotter.add("sky", sky, cmap="inferno_r") - plotter.add("power spectrum logsky", logsky.power_spectrum) - # plotter.add_histogram( - # "normalized residuals (original weights)", lh.normalized_residual - # ) + print( + f"Field of view: {c.xfov/rve.ARCMIN2RAD:.2f}' x {c.yfov/rve.ARCMIN2RAD:.2f}'" + ) + minimize(c.lhsd @ c.sky, "sd") + minimize(c.lh_compact @ c.sky, "compact") + minimize(c.lh_extended @ c.sky, "extended") + minimize((c.lh_compact + c.lh_extended + c.lhsd) @ c.sky, "all") + + +def minimize(lh, prefix): + plotter = rve.Plotter("png", f"plots_{prefix}") + plotter.add("logsky", c.logsky) + plotter.add("sky", c.sky, cmap="inferno_r") + plotter.add("power spectrum logsky", c.logsky.power_spectrum) ham = ift.StandardHamiltonian( lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") ) @@ -37,7 +42,11 @@ def main(): for ii in range(20): state = rve.simple_minimize(ham, state.mean, 3, mini) plotter.plot(f"iter{ii}", state) - state.save(f"iter{ii}") + state.save(f"{prefix}iter") + + +def minimize_with_varcov(lh, prefix): + pass if __name__ == "__main__": -- GitLab From b851a9455a3c55cbaeb83ebe1220b2d1dc7e2e89 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 10 Feb 2021 13:16:57 +0100 Subject: [PATCH 22/37] Add KeyPrefixer --- resolve/simple_operators.py | 21 +++++++++++++++++++++ test/test_general.py | 8 ++++++++ 2 files changed, 29 insertions(+) diff --git a/resolve/simple_operators.py b/resolve/simple_operators.py index 7a900e9f..a2c294bd 100644 --- a/resolve/simple_operators.py +++ b/resolve/simple_operators.py @@ -64,3 +64,24 @@ class AddEmptyDimensionAtEnd(ift.LinearOperator): else: x = x.val[..., 0] return ift.makeField(self._tgt(mode), x) + + +class KeyPrefixer(ift.LinearOperator): + def __init__(self, domain, prefix): + self._domain = ift.MultiDomain.make(domain) + self._target = ift.MultiDomain.make( + {prefix + kk: vv for kk, vv in self._domain.items()} + ) + self._capability = self.TIMES | self.ADJOINT_TIMES + self._prefix = prefix + + def apply(self, x, mode): + self._check_input(x, mode) + if mode == self.TIMES: + res = {self._prefix + kk: vv for kk, vv in x.items()} + else: + res = {kk[len(self._prefix) :]: vv for kk, vv in x.items()} + return ift.MultiField.from_dict(res) + + def __repr__(self): + return f"{self.domain.keys()} -> {self.target.keys()}" diff --git a/test/test_general.py b/test/test_general.py index 3511ce12..d09c9f2e 100644 --- a/test/test_general.py +++ b/test/test_general.py @@ -310,3 +310,11 @@ def test_intop(): dom = ift.RGSpace((12, 12)) op = rve.WienerIntegrations(freqdomain, dom) ift.extra.check_linear_operator(op) + + +def test_prefixer(): + op = rve.KeyPrefixer( + ift.MultiDomain.make({"a": ift.UnstructuredDomain(10), "b": ift.RGSpace(190)}), + "invcov_inp", + ).adjoint + ift.extra.check_linear_operator(op) -- GitLab From c3ea91888406f3db7a25a65cc485e88457eefb8e Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 10 Feb 2021 13:24:55 +0100 Subject: [PATCH 23/37] Reorder --- demo/cfg_mosaicing.py | 14 +++++++------- demo/mosaicing.py | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index 5ac84652..5a90c856 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -47,6 +47,13 @@ fov = np.array([xfov, yfov]) dom = ift.RGSpace(npix, fov / npix) # End compute phase center and define domain +# Sky model +logsky = ift.SimpleCorrelatedField( + dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) +) +sky = logsky.exp() +# End sky model + # Single dish likelihood responsesd = [] for kk, oo in sdobs.items(): @@ -80,13 +87,6 @@ invcovop = ift.FieldAdapter(dsd.domain["sd0"], "sd0invcov").ducktape_left("sd0") lhsd_varcov = VariableCovarianceGaussianEnergy(dsd, responsesd, invcovop) # End single dish likelihood -# Sky model -logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) -) -sky = logsky.exp() -# End sky model - # Interferometer likelihood def get_beamdirections(obs): diff --git a/demo/mosaicing.py b/demo/mosaicing.py index cdecee56..d398c66f 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -37,8 +37,6 @@ def minimize(lh, prefix): fld = 0.1 * ift.from_random(lh.domain) state = rve.MinimizationState(fld, []) mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) - # TODO Add minisanity to resolve - # TODO Fix histogram plots for ii in range(20): state = rve.simple_minimize(ham, state.mean, 3, mini) plotter.plot(f"iter{ii}", state) -- GitLab From f6e37b526fa395baa714c7537ea7f3bda2871ef5 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 10 Feb 2021 13:55:42 +0100 Subject: [PATCH 24/37] Implement variable covariance Gaussian energy --- demo/cfg_mosaicing.py | 41 +++++++++++++++++++------------------ demo/mosaicing.py | 3 +++ demo/post_mosaicing.py | 4 ++++ demo/pre_mosaicing.py | 27 ++++++++++++++++++++++++ resolve/simple_operators.py | 24 ++++++++++++++++++++-- test/test_general.py | 3 ++- 6 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 demo/pre_mosaicing.py diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index 5a90c856..84acc7c7 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -72,21 +72,24 @@ invcovsd = ift.makeOp( ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) ) lhsd = ift.GaussianEnergy(dsd, invcovsd) @ responsesd - - -def VariableCovarianceGaussianEnergy(data, signal_response, invcov): - resi = ift.Adder(dsd, True).ducktape_left("resi") @ responsesd - dtype = {kk: vv.dtype for kk, vv in data.items()} - return ift.VariableCovarianceGaussianEnergy(data.domain, "resi", "icov", dtype) @ ( - resi + invcov.ducktape_left("icov") - ) - - -assert list(dsd.domain.keys()) == ["sd0"] -invcovop = ift.FieldAdapter(dsd.domain["sd0"], "sd0invcov").ducktape_left("sd0").exp() -lhsd_varcov = VariableCovarianceGaussianEnergy(dsd, responsesd, invcovop) +invcovsdop = invcovsd @ rve.KeyPrefixer(dsd.domain, "invcov_inp").adjoint.exp() +lhsd_varcov = rve.MultiDomainVariableCovarianceGaussianEnergy( + dsd, responsesd.ducktape("sky"), invcovsdop +) # End single dish likelihood +# Consistency checks +ift.extra.assert_allclose( + invcovsdop(ift.full(invcovsdop.domain, 0.0)), + invcovsd(ift.full(invcovsd.domain, 1.0)), +) +_, lhsd1 = lhsd_varcov.simplify_for_constant_input(ift.full(invcovsdop.domain, 0.0)) +foo = ift.from_random(lhsd1.domain) +# Inaccuracy probably due to tr-log term in VariableCovGaussianEnergy +ift.extra.assert_allclose(lhsd(foo["sky"]), lhsd1(foo), rtol=5e-3) +del lhsd1, foo +# End consistency check + # Interferometer likelihood def get_beamdirections(obs): @@ -102,11 +105,9 @@ def get_beamdirections(obs): return beam_directions -beamsc = get_beamdirections(interobs_compact) -beamsext = get_beamdirections(interobs_extended) -skyslicer_compact = rve.SkySlicer(logsky.target, beamsc) -skyslicer_extended = rve.SkySlicer(logsky.target, beamsext) -skyslicer = rve.SkySlicer(logsky.target, {**beamsc, **beamsext}) -ift.extra.check_linear_operator(skyslicer) -lhinter = rve.ImagingLikelihood(interobs, skyslicer) +skyslicer_compact = rve.SkySlicer(logsky.target, get_beamdirections(interobs_compact)) +skyslicer_extended = rve.SkySlicer(logsky.target, get_beamdirections(interobs_extended)) +ift.extra.check_linear_operator(skyslicer_compact) +lh_compact = rve.ImagingLikelihood(interobs_compact, skyslicer_compact) +lh_extended = rve.ImagingLikelihood(interobs_extended, skyslicer_extended) # End interferometer likelihood diff --git a/demo/mosaicing.py b/demo/mosaicing.py index d398c66f..4776f948 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -20,6 +20,9 @@ def main(): print( f"Field of view: {c.xfov/rve.ARCMIN2RAD:.2f}' x {c.yfov/rve.ARCMIN2RAD:.2f}'" ) + + minimize_with_varcov(c.lhsd_varcov @ c.sky.ducktape_left("sky"), [], "sd") + minimize(c.lhsd @ c.sky, "sd") minimize(c.lh_compact @ c.sky, "compact") minimize(c.lh_extended @ c.sky, "extended") diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py index ca3d257a..046486fc 100644 --- a/demo/post_mosaicing.py +++ b/demo/post_mosaicing.py @@ -1,3 +1,7 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2021 Max-Planck-Society +# Author: Philipp Arras + from functools import reduce from operator import add diff --git a/demo/pre_mosaicing.py b/demo/pre_mosaicing.py new file mode 100644 index 00000000..d9ca1d0b --- /dev/null +++ b/demo/pre_mosaicing.py @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright(C) 2021 Max-Planck-Society +# Author: Philipp Arras + +import matplotlib.pyplot as plt +import numpy as np + +import cfg_mosaicing as c +import nifty7 as ift +import resolve as rve + +def plot_sd_data(): + for kk, vv in c.dsd.items(): + lim = np.max(np.abs(vv.val)) + plt.hist(vv.val.ravel(), bins=np.arange(-lim, lim+1, 1)) + plt.xlabel("Flux [Jy]") + plt.ylabel("Count") + plt.title("Single dish data") + plt.savefig(f"single_dish_data_{kk}.png") + plt.close() + + +def main(): + plot_sd_data() + +if __name__=="__main__": + main() diff --git a/resolve/simple_operators.py b/resolve/simple_operators.py index a2c294bd..8e3b7b76 100644 --- a/resolve/simple_operators.py +++ b/resolve/simple_operators.py @@ -1,12 +1,15 @@ # SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2020 Max-Planck-Society +# Copyright(C) 2019-2021 Max-Planck-Society # Author: Philipp Arras +from functools import reduce +from operator import add + import numpy as np import nifty7 as ift -from .util import my_assert_isinstance, my_asserteq +from .util import my_assert, my_assert_isinstance, my_asserteq class AddEmptyDimension(ift.LinearOperator): @@ -85,3 +88,20 @@ class KeyPrefixer(ift.LinearOperator): def __repr__(self): return f"{self.domain.keys()} -> {self.target.keys()}" + + +def MultiDomainVariableCovarianceGaussianEnergy(data, signal_response, invcov): + my_asserteq(data.domain, signal_response.target, invcov.target) + my_assert_isinstance(data.domain, ift.MultiDomain) + my_assert_isinstance(signal_response.domain, ift.MultiDomain) + my_assert(ift.is_operator(invcov)) + my_assert(ift.is_operator(signal_response)) + resi = KeyPrefixer(data.domain, "resi") @ ift.Adder(data, True) @ signal_response + invcov = KeyPrefixer(data.domain, "icov") @ invcov + res = [ + ift.VariableCovarianceGaussianEnergy( + data.domain[kk], "resi" + kk, "icov" + kk, data[kk].dtype + ) + for kk in data.keys() + ] + return reduce(add, res) @ (resi + invcov) diff --git a/test/test_general.py b/test/test_general.py index d09c9f2e..61a17224 100644 --- a/test/test_general.py +++ b/test/test_general.py @@ -2,8 +2,9 @@ # Copyright(C) 2020 Max-Planck-Society # Author: Philipp Arras -import numpy as np from os.path import join + +import numpy as np import pytest import nifty7 as ift -- GitLab From d8ea77305096ea46e5088ab6520de5e90127b5be Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Wed, 10 Feb 2021 18:57:07 +0100 Subject: [PATCH 25/37] Manage implementing a somewhat working version for the single dish data set --- demo/cfg_mosaicing.py | 30 +++++++++++++---- demo/mosaicing.py | 28 ++++++++++++++-- demo/post_mosaicing.py | 26 +-------------- demo/pre_mosaicing.py | 65 +++++++++++++++++++++++++++++++++---- misc/convert_alma_data.py | 10 ++++++ resolve/likelihood.py | 26 +++++++++++++-- resolve/simple_operators.py | 20 ++++++++---- 7 files changed, 156 insertions(+), 49 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index 84acc7c7..aea45566 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -10,18 +10,20 @@ import nifty7 as ift import resolve as rve -diffusefluxlevel = 20 +diffusefluxlevel = 29 npix = np.array([1800, 1800]) rve.set_nthreads(2) rve.set_wgridding(False) interobs_extended = { f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") - for ii in range(5, 135) + # for ii in range(5, 135) + for ii in range(5, 7) } interobs_compact = { f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") - for ii in range(5, 153) + # for ii in range(5, 153) + for ii in range(5, 7) } interobs = {**interobs_compact, **interobs_extended} sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} @@ -49,7 +51,7 @@ dom = ift.RGSpace(npix, fov / npix) # Sky model logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 0.1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) + dom, diffusefluxlevel, (1, 1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) ) sky = logsky.exp() # End sky model @@ -72,7 +74,21 @@ invcovsd = ift.makeOp( ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) ) lhsd = ift.GaussianEnergy(dsd, invcovsd) @ responsesd -invcovsdop = invcovsd @ rve.KeyPrefixer(dsd.domain, "invcov_inp").adjoint.exp() + +if True: + # Learn one number per observation + foo = [] + for kk in dsd.keys(): + foo.append( + ift.ContractionOperator(invcovsd.domain[kk], (0, 1, 2)) + .adjoint.ducktape(kk + "invcovinp") + .ducktape_left(kk) + ) + invcovsdop = invcovsd @ reduce(add, foo).exp() +else: + # Learn one number per data point + invcovsdop = invcovsd @ rve.KeyPrefixer(dsd.domain, "invcov_inp").adjoint.exp() + lhsd_varcov = rve.MultiDomainVariableCovarianceGaussianEnergy( dsd, responsesd.ducktape("sky"), invcovsdop ) @@ -85,8 +101,8 @@ ift.extra.assert_allclose( ) _, lhsd1 = lhsd_varcov.simplify_for_constant_input(ift.full(invcovsdop.domain, 0.0)) foo = ift.from_random(lhsd1.domain) -# Inaccuracy probably due to tr-log term in VariableCovGaussianEnergy -ift.extra.assert_allclose(lhsd(foo["sky"]), lhsd1(foo), rtol=5e-3) +# FIXME Inaccuracy probably due to tr-log term in VariableCovGaussianEnergy +ift.extra.assert_allclose(lhsd(foo["sky"]), lhsd1(foo), rtol=1e-2) del lhsd1, foo # End consistency check diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 4776f948..0285bbbf 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -21,7 +21,13 @@ def main(): f"Field of view: {c.xfov/rve.ARCMIN2RAD:.2f}' x {c.yfov/rve.ARCMIN2RAD:.2f}'" ) - minimize_with_varcov(c.lhsd_varcov @ c.sky.ducktape_left("sky"), [], "sd") + # minimize(c.lhsd @ c.sky, "sd") + minimize_with_varcov( + c.lhsd_varcov.partial_insert(c.sky.ducktape_left("sky")), + c.invcovsdop.domain.keys(), + "sd", + ) + exit() minimize(c.lhsd @ c.sky, "sd") minimize(c.lh_compact @ c.sky, "compact") @@ -46,8 +52,24 @@ def minimize(lh, prefix): state.save(f"{prefix}iter") -def minimize_with_varcov(lh, prefix): - pass +def minimize_with_varcov(lh, varcov_keys, prefix): + assert set(varcov_keys) < set(lh.domain.keys()) + plotter = rve.Plotter("png", f"plots_{prefix}") + plotter.add("logsky", c.logsky) + plotter.add("sky", c.sky, cmap="inferno_r") + plotter.add("power spectrum logsky", c.logsky.power_spectrum) + ham = ift.StandardHamiltonian( + lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") + ) + fld = 0.1 * ift.from_random(lh.domain) + state = rve.MinimizationState(fld, []) + mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) + for ii in range(20): + state = rve.simple_minimize( + ham, state.mean, 3, mini, constants=varcov_keys if ii < 3 else [] + ) + plotter.plot(f"{ii}both", state) + state.save(f"{prefix}both") if __name__ == "__main__": diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py index 046486fc..250ca80e 100644 --- a/demo/post_mosaicing.py +++ b/demo/post_mosaicing.py @@ -17,9 +17,8 @@ from cfg_mosaicing import ( interobs_compact, interobs_extended, responsesd, - sky, sdobs, - skyslicer, + sky, skyslicer_compact, skyslicer_extended, xfov, @@ -27,28 +26,6 @@ from cfg_mosaicing import ( ) -def plot_pointings(): - xs, ys = [], [] - for vv in interobs.values(): - xs.append(vv.direction.phase_center[0]) - ys.append(vv.direction.phase_center[1]) - xs, ys = np.array(xs), np.array(ys) - plt.scatter(xs, ys, label="Interferometer") - for kk, vv in sdobs.items(): - plt.scatter( - *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 - ) - plt.scatter(*global_phase_center_casa, label="Phase center CASA") - plt.scatter(*global_phase_center, label="Phase center resolve") - plt.xlim([global_phase_center[0] - xfov / 2, global_phase_center[0] + xfov / 2]) - plt.ylim([global_phase_center[1] - yfov / 2, global_phase_center[1] + yfov / 2]) - plt.gca().invert_xaxis() - plt.gca().set_aspect("equal") - plt.legend() - plt.savefig("pointings.png") - plt.close() - - def imshow(ax, fld, **kwargs): kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} return ax.imshow(fld.val.T, **kwargs) @@ -68,7 +45,6 @@ def dirty_images(mean_sky): for obs, skyslc in ( (interobs_extended, skyslicer_extended), (interobs_compact, skyslicer_compact), - (interobs, skyslicer), ) ] ) diff --git a/demo/pre_mosaicing.py b/demo/pre_mosaicing.py index d9ca1d0b..efae1cb3 100644 --- a/demo/pre_mosaicing.py +++ b/demo/pre_mosaicing.py @@ -4,24 +4,77 @@ import matplotlib.pyplot as plt import numpy as np +import nifty7 as ift import cfg_mosaicing as c -import nifty7 as ift -import resolve as rve + def plot_sd_data(): + # FIXME QUESTION: In which unit is the single dish data? Jy/beam? Maybe learn relative factor between single dish and interferometer or figure out the correct factor. for kk, vv in c.dsd.items(): - lim = np.max(np.abs(vv.val)) - plt.hist(vv.val.ravel(), bins=np.arange(-lim, lim+1, 1)) - plt.xlabel("Flux [Jy]") + obs = c.sdobs[kk] + directions = obs.pointings.phase_centers + ind = np.any(obs.vis.val <= 0.0, axis=(0, 2)) + plt.scatter(*directions[ind].T, label="Non-positive", alpha=0.4, s=2) + plt.scatter(*directions[~ind].T, label="Positive", alpha=0.4, s=2) + plt.gca().invert_xaxis() + plt.gca().set_aspect("equal") + plt.legend() + plt.savefig(f"single_dish_negative_{kk}.png") + plt.close() + + vv = obs.apply_flags(vv.val) + lim = np.max(np.abs(vv)) + plt.hist(vv.ravel(), bins=np.arange(-lim, lim + 1, 1)) + plt.xlabel("Flux [???]") plt.ylabel("Count") plt.title("Single dish data") plt.savefig(f"single_dish_data_{kk}.png") plt.close() +def plot_pointings(): + xs, ys = [], [] + for vv in c.interobs.values(): + xs.append(vv.direction.phase_center[0]) + ys.append(vv.direction.phase_center[1]) + xs, ys = np.array(xs), np.array(ys) + plt.scatter(xs, ys, label="Interferometer") + for kk, vv in c.sdobs.items(): + plt.scatter( + *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 + ) + plt.scatter(*c.global_phase_center_casa, label="Phase center CASA") + plt.scatter(*c.global_phase_center, label="Phase center resolve") + plt.xlim( + [c.global_phase_center[0] - c.xfov / 2, c.global_phase_center[0] + c.xfov / 2] + ) + plt.ylim( + [c.global_phase_center[1] - c.yfov / 2, c.global_phase_center[1] + c.yfov / 2] + ) + plt.gca().invert_xaxis() + plt.gca().set_aspect("equal") + plt.legend() + plt.savefig("pointings.png") + plt.close() + + +def plot_sky_prior(): + p = ift.Plot() + pspecs = [] + for _ in range(8): + pos = ift.from_random(c.sky.domain) + p.add(c.logsky(pos)) + pspecs.append(c.logsky.power_spectrum.force(pos)) + p.add(pspecs) + p.output(name="sky_prior.png") + + def main(): + plot_sky_prior() + plot_pointings() plot_sd_data() -if __name__=="__main__": + +if __name__ == "__main__": main() diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 0962f982..4a638632 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -28,6 +28,7 @@ def cleanup(weight, vis, freq, uvw=None): mywgt = mywgt[:, ind] myvis = myvis[:, ind] myfreq = freq[ind] + myvis[mywgt == 0.0] = 0.0 if uvw is None: return mywgt, myvis, myfreq return mywgt, myvis, myfreq, myuvw @@ -81,6 +82,15 @@ for f, spectral_window, single_dish, fieldid in [ del flag vis = sel("FLOAT_DATA" if single_dish else "DATA")[:, channel_slice] + # FIXME QUESTION Why is there negative data? + if single_dish: + assert not np.iscomplexobj(vis) + ind = vis * np.sqrt(wgt) <= 2.0 + print( + f"{np.sum(ind)} data points are not 2-sigma positive ({np.sum(ind)/ind.size*100:.1f} %)" + ) + wgt[ind] = 0.0 + # Average polarization assert vis.shape[2] == 2 assert wgt.shape[2] == 2 diff --git a/resolve/likelihood.py b/resolve/likelihood.py index c714f35c..a659bbbe 100644 --- a/resolve/likelihood.py +++ b/resolve/likelihood.py @@ -2,6 +2,9 @@ # Copyright(C) 2020 Max-Planck-Society # Author: Philipp Arras +from functools import reduce +from operator import add + import numpy as np import nifty7 as ift @@ -14,7 +17,6 @@ from .util import my_assert, my_assert_isinstance, my_asserteq def _get_mask(observation): # Only needed for variable covariance gaussian energy my_assert_isinstance(observation, Observation) - vis = observation.vis flags = observation.flags if not np.any(flags): @@ -23,6 +25,24 @@ def _get_mask(observation): return mask, mask(vis), mask(observation.weight) +def get_mask_multi_field(weight): + assert isinstance(weight, ift.MultiField) + op = [] + for kk, ww in weight.items(): + flags = ww.val == 0.0 + if np.any(flags): + op.append( + ift.MaskOperator(ift.makeField(ww.domain, flags)) + .ducktape(kk) + .ducktape_left(kk) + ) + else: + op.append(ift.ScalingOperator(ww.domain, 1.0)) + op = reduce(add, op) + assert op.domain == weight.domain + return op + + def _Likelihood(operator, normalized_residual_operator): my_assert_isinstance(operator, ift.Operator) my_asserteq(operator.target, ift.DomainTuple.scalar_domain()) @@ -160,7 +180,9 @@ def ImagingLikelihood( if inverse_covariance_operator is None: if mosaicing: vis = ift.MultiField.from_dict({kk: o.vis for kk, o in observation.items()}) - weight = ift.MultiField.from_dict({kk: o.weight for kk, o in observation.items()}) + weight = ift.MultiField.from_dict( + {kk: o.weight for kk, o in observation.items()} + ) else: vis, weight = observation.vis, observation.weight return _build_gauss_lh_nres(model_data, vis, weight) diff --git a/resolve/simple_operators.py b/resolve/simple_operators.py index 8e3b7b76..ad834f4c 100644 --- a/resolve/simple_operators.py +++ b/resolve/simple_operators.py @@ -91,17 +91,25 @@ class KeyPrefixer(ift.LinearOperator): def MultiDomainVariableCovarianceGaussianEnergy(data, signal_response, invcov): + from .likelihood import get_mask_multi_field + my_asserteq(data.domain, signal_response.target, invcov.target) my_assert_isinstance(data.domain, ift.MultiDomain) my_assert_isinstance(signal_response.domain, ift.MultiDomain) my_assert(ift.is_operator(invcov)) my_assert(ift.is_operator(signal_response)) + res = [] + invcovfld = invcov(ift.full(invcov.domain, 1.0)) + mask = get_mask_multi_field(invcovfld) + data = mask(data) + signal_response = mask @ signal_response + invcov = mask @ invcov + for kk in data.keys(): + res.append( + ift.VariableCovarianceGaussianEnergy( + data.domain[kk], "resi" + kk, "icov" + kk, data[kk].dtype + ) + ) resi = KeyPrefixer(data.domain, "resi") @ ift.Adder(data, True) @ signal_response invcov = KeyPrefixer(data.domain, "icov") @ invcov - res = [ - ift.VariableCovarianceGaussianEnergy( - data.domain[kk], "resi" + kk, "icov" + kk, data[kk].dtype - ) - for kk in data.keys() - ] return reduce(add, res) @ (resi + invcov) -- GitLab From 60f4c40a84d837b6bf25550925bbbc564393e85a Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 11:07:12 +0100 Subject: [PATCH 26/37] Add debugging script --- demo/debug_mosaicing.py | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 demo/debug_mosaicing.py diff --git a/demo/debug_mosaicing.py b/demo/debug_mosaicing.py new file mode 100644 index 00000000..eec4303a --- /dev/null +++ b/demo/debug_mosaicing.py @@ -0,0 +1,51 @@ +import resolve as rve +import numpy as np +import matplotlib.pyplot as plt +import nifty7 as ift +import cfg_mosaicing as c + + +if False: + state = rve.MinimizationState.load("sditer") + bins = np.linspace(-20, 20, 40) + for kk in c.invcovsd.target.keys(): + for ss in state: + arr = ( + (c.invcovsd.get_sqrt() @ ift.Adder(c.dsd, True) @ c.responsesd @ c.sky) + .force(ss) + .val + ) + arr = c.sdobs[kk].apply_flags(arr[kk]).ravel() + plt.hist(arr.clip(-20, 20), bins=bins, alpha=0.5) + print("Reduced chi**2:", np.sum(arr ** 2) / arr.size) + plt.title(f"Noise weighted residual {kk}") + plt.savefig(f"debug_nwresi_{kk}.png") + plt.close() +else: + state = rve.MinimizationState.load("sdboth") + + upperlim = 10 + bins = np.linspace(0, upperlim, upperlim * 10) + for kk in c.invcovsdop.target.keys(): + for ss in state: + learnedsd = 1 / c.invcovsdop.force(ss)[kk].sqrt() + datasd = 1 / c.invcovsd(ift.full(c.invcovsd.domain, 1.0))[kk].sqrt() + plt.hist( + (learnedsd / datasd).sqrt().val.ravel().clip(0, upperlim), bins=bins + ) + plt.axvline(1.0) + plt.xlabel("Learned sigma / sigma in measurement set") + plt.savefig(f"debug{kk}.png") + plt.close() + + bins = np.linspace(-20, 20, 40) + for kk in c.invcovsd.target.keys(): + for ss in state: + arr = (ift.Adder(c.dsd, True) @ c.responsesd @ c.sky).force(ss) + arr = arr * c.invcovsdop.force(ss).sqrt() + arr = c.sdobs[kk].apply_flags(arr.val[kk]).ravel() + plt.hist(arr.clip(-20, 20), bins=bins, alpha=0.5) + print("Reduced chi**2:", np.sum(arr ** 2) / arr.size) + plt.title(f"Noise weighted residual {kk}") + plt.savefig(f"debug_nwresi_{kk}.png") + plt.close() -- GitLab From 5a85042f90f6722557031e36e30b0ee8d5910ae5 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 13:47:22 +0100 Subject: [PATCH 27/37] Cleanup --- resolve/likelihood.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/resolve/likelihood.py b/resolve/likelihood.py index a659bbbe..859e4477 100644 --- a/resolve/likelihood.py +++ b/resolve/likelihood.py @@ -83,36 +83,6 @@ def _varcov(observation, Rs, inverse_covariance_operator): return _build_varcov_gauss_lh_nres(residual, inverse_covariance_operator, dtype) -def SingleDishLikelihood( - observation, - sky_operator, - inverse_covariance_operator=None, - calibration_operator=None, -): - if inverse_covariance_operator is not None or calibration_operator is not None: - raise NotImplementedError - mosaicing = isinstance(observation, dict) - if mosaicing: - vis = ift.MultiField.from_dict({kk: o.vis for kk, o in observation.items()}) - weight = ift.MultiField.from_dict( - {kk: o.weight for kk, o in observation.items()} - ) - single_dish_response = [] - for kk, oo in observation.items(): - assert np.min(oo.freq) / np.max(oo.freq) > 0.99 - R = rve.SingleDishResponse( - oo, - sky_operator.target, - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), - global_phase_center, - ) - single_dish_response.append(R.ducktape_left(kk)) - from functools import reduce - from operator import add - - single_dish_response = reduce(add, single_dish_response) - - def ImagingLikelihood( observation, sky_operator, -- GitLab From 14e299d70b70070b1d6d677f98696334e2463a87 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 15:17:01 +0100 Subject: [PATCH 28/37] Sky target always MultiDomain --- demo/cfg_mosaicing.py | 12 +++++++----- resolve/likelihood.py | 9 +++------ resolve/mosaicing/single_dish_response.py | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index aea45566..fc1713e9 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -53,7 +53,7 @@ dom = ift.RGSpace(npix, fov / npix) logsky = ift.SimpleCorrelatedField( dom, diffusefluxlevel, (1, 1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) ) -sky = logsky.exp() +sky = logsky.exp().ducktape_left("sky") # End sky model # Single dish likelihood @@ -90,7 +90,7 @@ else: invcovsdop = invcovsd @ rve.KeyPrefixer(dsd.domain, "invcov_inp").adjoint.exp() lhsd_varcov = rve.MultiDomainVariableCovarianceGaussianEnergy( - dsd, responsesd.ducktape("sky"), invcovsdop + dsd, responsesd, invcovsdop ) # End single dish likelihood @@ -102,7 +102,7 @@ ift.extra.assert_allclose( _, lhsd1 = lhsd_varcov.simplify_for_constant_input(ift.full(invcovsdop.domain, 0.0)) foo = ift.from_random(lhsd1.domain) # FIXME Inaccuracy probably due to tr-log term in VariableCovGaussianEnergy -ift.extra.assert_allclose(lhsd(foo["sky"]), lhsd1(foo), rtol=1e-2) +ift.extra.assert_allclose(lhsd(foo), lhsd1(foo), rtol=1e-2) del lhsd1, foo # End consistency check @@ -124,6 +124,8 @@ def get_beamdirections(obs): skyslicer_compact = rve.SkySlicer(logsky.target, get_beamdirections(interobs_compact)) skyslicer_extended = rve.SkySlicer(logsky.target, get_beamdirections(interobs_extended)) ift.extra.check_linear_operator(skyslicer_compact) -lh_compact = rve.ImagingLikelihood(interobs_compact, skyslicer_compact) -lh_extended = rve.ImagingLikelihood(interobs_extended, skyslicer_extended) +lh_compact = rve.ImagingLikelihood(interobs_compact, skyslicer_compact).ducktape("sky") +lh_extended = rve.ImagingLikelihood(interobs_extended, skyslicer_extended).ducktape( + "sky" +) # End interferometer likelihood diff --git a/resolve/likelihood.py b/resolve/likelihood.py index 859e4477..49d1b4b1 100644 --- a/resolve/likelihood.py +++ b/resolve/likelihood.py @@ -31,13 +31,10 @@ def get_mask_multi_field(weight): for kk, ww in weight.items(): flags = ww.val == 0.0 if np.any(flags): - op.append( - ift.MaskOperator(ift.makeField(ww.domain, flags)) - .ducktape(kk) - .ducktape_left(kk) - ) + myop = ift.MaskOperator(ift.makeField(ww.domain, flags)) else: - op.append(ift.ScalingOperator(ww.domain, 1.0)) + myop = ift.ScalingOperator(ww.domain, 1.0) + op.append(myop.ducktape(kk).ducktape_left(kk)) op = reduce(add, op) assert op.domain == weight.domain return op diff --git a/resolve/mosaicing/single_dish_response.py b/resolve/mosaicing/single_dish_response.py index d4aa2b87..e54f0660 100644 --- a/resolve/mosaicing/single_dish_response.py +++ b/resolve/mosaicing/single_dish_response.py @@ -21,7 +21,7 @@ def SingleDishResponse(observation, domain, beam_function, global_phase_center): fld = ift.from_random(conv.domain) ift.extra.assert_allclose(conv(fld).integrate(), fld.integrate()) pc = observation.pointings.phase_centers.T - np.array(global_phase_center)[:, None] - pc = pc + (np.array(domain.shape)*np.array(domain[0].distances)/2)[:, None] + pc = pc + (np.array(domain.shape) * np.array(domain[0].distances) / 2)[:, None] # Convention: pointing convention (see also BeamDirection) pc[0] *= -1 interp = ift.LinearInterpolator(domain, pc) -- GitLab From 374d3d36d1bcca5211645dbbe128338b009ac92e Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 15:17:32 +0100 Subject: [PATCH 29/37] Add additive term for dealing with negative data --- demo/cfg_mosaicing.py | 4 ++++ demo/mosaicing.py | 3 +++ resolve/mosaicing/single_dish_response.py | 11 +++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index fc1713e9..a9ae0af9 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -58,6 +58,9 @@ sky = logsky.exp().ducktape_left("sky") # Single dish likelihood responsesd = [] +additive_term = ift.SimpleCorrelatedField( + dom, 0, (1, 1), (1, 1), (1, 1), None, (-2, 0.5), "negativity_madness" +) for kk, oo in sdobs.items(): # Single dish response does not support multifrequency assert np.min(oo.freq) / np.max(oo.freq) > 0.99 @@ -66,6 +69,7 @@ for kk, oo in sdobs.items(): dom, lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), global_phase_center, + additive_term=additive_term, ) responsesd.append(R.ducktape_left(kk)) responsesd = reduce(add, responsesd) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 0285bbbf..8e6484ac 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -40,6 +40,9 @@ def minimize(lh, prefix): plotter.add("logsky", c.logsky) plotter.add("sky", c.sky, cmap="inferno_r") plotter.add("power spectrum logsky", c.logsky.power_spectrum) + plotter.add("additive term", c.additive_term) + plotter.add("additive term power spectrum", c.additive_term.power_spectrum) + ham = ift.StandardHamiltonian( lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") ) diff --git a/resolve/mosaicing/single_dish_response.py b/resolve/mosaicing/single_dish_response.py index e54f0660..1d584882 100644 --- a/resolve/mosaicing/single_dish_response.py +++ b/resolve/mosaicing/single_dish_response.py @@ -9,7 +9,9 @@ import nifty7 as ift from ..observation import SingleDishObservation -def SingleDishResponse(observation, domain, beam_function, global_phase_center): +def SingleDishResponse( + observation, domain, beam_function, global_phase_center, additive_term=None +): assert isinstance(observation, SingleDishObservation) domain = ift.makeDomain(domain) assert len(domain) == 1 @@ -20,6 +22,7 @@ def SingleDishResponse(observation, domain, beam_function, global_phase_center): # FIXME Move into tests fld = ift.from_random(conv.domain) ift.extra.assert_allclose(conv(fld).integrate(), fld.integrate()) + pc = observation.pointings.phase_centers.T - np.array(global_phase_center)[:, None] pc = pc + (np.array(domain.shape) * np.array(domain[0].distances) / 2)[:, None] # Convention: pointing convention (see also BeamDirection) @@ -29,4 +32,8 @@ def SingleDishResponse(observation, domain, beam_function, global_phase_center): # NOTE The volume factor above `domain.total_volume()` and the volume factor # below `domain[0].scalar_dvol` cancel each other. They are left in the # code such that the convolution leaves the integral invariant. - return bc @ interp @ conv.scale(domain[0].scalar_dvol) + + convsky = conv.scale(domain[0].scalar_dvol).ducktape("sky") + if additive_term is not None: + convsky = convsky + additive_term + return bc @ interp @ convsky -- GitLab From bebdc5cb2d7bd2dcd37bdd610810ca2f2c286856 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 17:24:23 +0100 Subject: [PATCH 30/37] Use all single dish data again --- misc/convert_alma_data.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py index 4a638632..fd578ae2 100644 --- a/misc/convert_alma_data.py +++ b/misc/convert_alma_data.py @@ -82,14 +82,14 @@ for f, spectral_window, single_dish, fieldid in [ del flag vis = sel("FLOAT_DATA" if single_dish else "DATA")[:, channel_slice] - # FIXME QUESTION Why is there negative data? - if single_dish: - assert not np.iscomplexobj(vis) - ind = vis * np.sqrt(wgt) <= 2.0 - print( - f"{np.sum(ind)} data points are not 2-sigma positive ({np.sum(ind)/ind.size*100:.1f} %)" - ) - wgt[ind] = 0.0 + # # FIXME QUESTION Why is there negative data? + # if single_dish: + # assert not np.iscomplexobj(vis) + # ind = vis * np.sqrt(wgt) <= 2.0 + # print( + # f"{np.sum(ind)} data points are not 2-sigma positive ({np.sum(ind)/ind.size*100:.1f} %)" + # ) + # wgt[ind] = 0.0 # Average polarization assert vis.shape[2] == 2 -- GitLab From 03fe871b84195aeebc39f16f64274b996b94ebc9 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Fri, 12 Feb 2021 17:24:31 +0100 Subject: [PATCH 31/37] Kind of working version for single dish --- demo/debug_mosaicing.py | 45 +++++++++++++++++++++++++++-------------- demo/mosaicing.py | 17 +++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/demo/debug_mosaicing.py b/demo/debug_mosaicing.py index eec4303a..1c561bc8 100644 --- a/demo/debug_mosaicing.py +++ b/demo/debug_mosaicing.py @@ -22,27 +22,42 @@ if False: plt.savefig(f"debug_nwresi_{kk}.png") plt.close() else: + state = rve.MinimizationState.load("sditer") state = rve.MinimizationState.load("sdboth") - upperlim = 10 - bins = np.linspace(0, upperlim, upperlim * 10) - for kk in c.invcovsdop.target.keys(): - for ss in state: - learnedsd = 1 / c.invcovsdop.force(ss)[kk].sqrt() - datasd = 1 / c.invcovsd(ift.full(c.invcovsd.domain, 1.0))[kk].sqrt() - plt.hist( - (learnedsd / datasd).sqrt().val.ravel().clip(0, upperlim), bins=bins - ) - plt.axvline(1.0) - plt.xlabel("Learned sigma / sigma in measurement set") - plt.savefig(f"debug{kk}.png") - plt.close() + plotter = rve.Plotter("png", "debug") + plotter.add("additive term", c.additive_term) + plotter.add("sky", ift.FieldAdapter(c.sky.target["sky"], "sky")@ c.sky, cmap="inferno_r") + plotter.add("additive term power spectrum", c.additive_term.power_spectrum) + plotter.plot("debug", state) + + learnnoise = True + if learnnoise: + upperlim = 10 + bins = np.linspace(0, upperlim, upperlim * 10) + for kk in c.invcovsdop.target.keys(): + for ss in state: + learnedsd = 1 / c.invcovsdop.force(ss)[kk].sqrt() + datasd = 1 / c.invcovsd(ift.full(c.invcovsd.domain, 1.0))[kk].sqrt() + plt.hist( + (learnedsd / datasd).sqrt().val.ravel().clip(0, upperlim), bins=bins + ) + plt.axvline(1.0) + plt.xlabel("Learned sigma / sigma in measurement set") + plt.savefig(f"debug{kk}.png") + plt.close() + bins = np.linspace(-20, 20, 40) for kk in c.invcovsd.target.keys(): for ss in state: - arr = (ift.Adder(c.dsd, True) @ c.responsesd @ c.sky).force(ss) - arr = arr * c.invcovsdop.force(ss).sqrt() + arr = (ift.Adder(c.dsd, True) @ c.responsesd.partial_insert(c.sky)).force( + ss + ) + if learnnoise: + arr = arr * c.invcovsdop.force(ss).sqrt() + else: + arr = c.invcovsd.get_sqrt()(arr) arr = c.sdobs[kk].apply_flags(arr.val[kk]).ravel() plt.hist(arr.clip(-20, 20), bins=bins, alpha=0.5) print("Reduced chi**2:", np.sum(arr ** 2) / arr.size) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 8e6484ac..61151979 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -21,15 +21,12 @@ def main(): f"Field of view: {c.xfov/rve.ARCMIN2RAD:.2f}' x {c.yfov/rve.ARCMIN2RAD:.2f}'" ) - # minimize(c.lhsd @ c.sky, "sd") + # minimize(c.lhsd.partial_insert(c.sky), "sd") minimize_with_varcov( - c.lhsd_varcov.partial_insert(c.sky.ducktape_left("sky")), - c.invcovsdop.domain.keys(), - "sd", + c.lhsd_varcov.partial_insert(c.sky), c.invcovsdop.domain.keys(), "sd" ) exit() - minimize(c.lhsd @ c.sky, "sd") minimize(c.lh_compact @ c.sky, "compact") minimize(c.lh_extended @ c.sky, "extended") minimize((c.lh_compact + c.lh_extended + c.lhsd) @ c.sky, "all") @@ -38,7 +35,9 @@ def main(): def minimize(lh, prefix): plotter = rve.Plotter("png", f"plots_{prefix}") plotter.add("logsky", c.logsky) - plotter.add("sky", c.sky, cmap="inferno_r") + plotter.add( + "sky", ift.FieldAdapter(c.sky.target["sky"], "sky") @ c.sky, cmap="inferno_r" + ) plotter.add("power spectrum logsky", c.logsky.power_spectrum) plotter.add("additive term", c.additive_term) plotter.add("additive term power spectrum", c.additive_term.power_spectrum) @@ -59,8 +58,12 @@ def minimize_with_varcov(lh, varcov_keys, prefix): assert set(varcov_keys) < set(lh.domain.keys()) plotter = rve.Plotter("png", f"plots_{prefix}") plotter.add("logsky", c.logsky) - plotter.add("sky", c.sky, cmap="inferno_r") + plotter.add( + "sky", ift.FieldAdapter(c.sky.target["sky"], "sky") @ c.sky, cmap="inferno_r" + ) plotter.add("power spectrum logsky", c.logsky.power_spectrum) + plotter.add("additive term", c.additive_term) + plotter.add("additive term power spectrum", c.additive_term.power_spectrum) ham = ift.StandardHamiltonian( lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") ) -- GitLab From 2d65c643af85ea4bb0a7dc3d5bf47a4f86777287 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 15 Feb 2021 14:35:08 +0100 Subject: [PATCH 32/37] Add comparison to ALMA reconstruction --- demo/cfg_mosaicing.py | 3 +- demo/post_mosaicing.py | 64 +++++++++++++++++--------- resolve/__init__.py | 2 +- resolve/fits.py | 100 ++++++++++++++++++++++++++++------------- 4 files changed, 113 insertions(+), 56 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index a9ae0af9..ec512ef8 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -53,7 +53,8 @@ dom = ift.RGSpace(npix, fov / npix) logsky = ift.SimpleCorrelatedField( dom, diffusefluxlevel, (1, 1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) ) -sky = logsky.exp().ducktape_left("sky") +sky_domaintuple = logsky.exp() +sky = sky_domaintuple.ducktape_left("sky") # End sky model # Single dish likelihood diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py index 250ca80e..67a2c01a 100644 --- a/demo/post_mosaicing.py +++ b/demo/post_mosaicing.py @@ -10,20 +10,8 @@ import numpy as np import nifty7 as ift import resolve as rve -from cfg_mosaicing import ( - global_phase_center, - global_phase_center_casa, - interobs, - interobs_compact, - interobs_extended, - responsesd, - sdobs, - sky, - skyslicer_compact, - skyslicer_extended, - xfov, - yfov, -) + +import cfg_mosaicing as c def imshow(ax, fld, **kwargs): @@ -31,6 +19,12 @@ def imshow(ax, fld, **kwargs): return ax.imshow(fld.val.T, **kwargs) +def imshow_with_colorbar(ax, fld, **kwargs): + kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} + im = ax.imshow(fld.val.T, **kwargs) + plt.colorbar(im, ax=ax) + + def imshow_symmetric(ax, fld): lim = np.max(np.abs(fld.val)) im = imshow(ax, fld, vmin=-lim, vmax=lim, cmap="seismic") @@ -38,13 +32,13 @@ def imshow_symmetric(ax, fld): def dirty_images(mean_sky): - responses = [responsesd] + responses = [c.responsesd] responses.extend( [ - rve.StokesIResponse(obs, skyslc.target) @ skyslc + rve.StokesIResponse(obs, skyslc.target) @ skyslc.ducktape("sky") for obs, skyslc in ( - (interobs_extended, skyslicer_extended), - (interobs_compact, skyslicer_compact), + (c.interobs_extended, c.skyslicer_extended), + (c.interobs_compact, c.skyslicer_compact), ) ] ) @@ -52,13 +46,13 @@ def dirty_images(mean_sky): data = [ ift.MultiField.from_dict({kk: o.vis for kk, o in obs.items()}) - for obs in [sdobs, interobs_extended, interobs_compact, interobs] + for obs in [c.sdobs, c.interobs_extended, c.interobs_compact, c.interobs] ] data.append(reduce(lambda a, b: a.unite(b), data)) weights = [ ift.MultiField.from_dict({kk: o.weight for kk, o in obs.items()}) - for obs in [sdobs, interobs_extended, interobs_compact, interobs] + for obs in [c.sdobs, c.interobs_extended, c.interobs_compact, c.interobs] ] weights.append(reduce(lambda a, b: a.unite(b), weights)) @@ -81,12 +75,38 @@ def dirty_images(mean_sky): def main(): - state = rve.MinimizationState.load("iter8") + state = rve.MinimizationState.load("sdboth") skysc = ift.StatCalculator() for ss in state: - skysc.add(sky.force(ss)) + skysc.add(c.sky_domaintuple.force(ss)) + + sd_comparison(skysc.mean) dirty_images(skysc.mean) +def sd_comparison(mean_sky): + fld, refval = rve.fits2field("../resources_multipointing/M83.spw17.I.sd.fits") + # FIXME Check with convert_alma_data.py script + minfreq = 230172900000.0 + ind = int(np.round((minfreq - refval[0]) / fld.domain[0].distances[0])) + extractor = ift.DomainTupleFieldInserter(fld.domain, 0, (ind,)).adjoint + casasky = extractor(fld) + + fig, axs = plt.subplots(1, 2) + axs = list(axs) + + axx = axs.pop(0) + imshow_with_colorbar(axx, casasky) + axx.set_title("CASA single dish") + + axx = axs.pop(0) + imshow_with_colorbar(axx, mean_sky) + axx.set_title("Resolve single dish posterior mean") + + plt.tight_layout() + fig.savefig("sd_reconstructions.png") + plt.close() + + if __name__ == "__main__": main() diff --git a/resolve/__init__.py b/resolve/__init__.py index b74b7fda..d69bc4b0 100644 --- a/resolve/__init__.py +++ b/resolve/__init__.py @@ -2,7 +2,7 @@ from .antenna_positions import AntennaPositions from .calibration import calibration_distribution from .constants import * from .direction import * -from .fits import field2fits +from .fits import field2fits, fits2field from .global_config import * from .likelihood import * from .minimization import Minimization, MinimizationState, simple_minimize diff --git a/resolve/fits.py b/resolve/fits.py index 4f2ecc6d..93700159 100644 --- a/resolve/fits.py +++ b/resolve/fits.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2020 Max-Planck-Society +# Copyright(C) 2019-2021 Max-Planck-Society # Author: Philipp Arras import time @@ -7,6 +7,9 @@ from os.path import splitext import numpy as np +import nifty7 as ift + +from .constants import DEG2RAD from .mpi import onlymaster @@ -41,35 +44,68 @@ def field2fits(field, file_name, overwrite, direction=None): base, ext = splitext(file_name) hdulist.writeto(base + ext, overwrite=overwrite) - # @staticmethod - # def make_from_file(file_name): - # with pyfits.open(file_name) as hdu_list: - # lst = hdu_list[0] - # pcx = lst.header['CRVAL1']/180*np.pi - # pcy = lst.header['CRVAL2']/180*np.pi - # equ = lst.header['EQUINOX'] - # return FitsWriter([pcx, pcy], equ) - # @staticmethod - # def fits2field(file_name, ignore_units=False, from_wsclean=False): - # with pyfits.open(file_name) as hdu_list: - # image_data = np.squeeze(hdu_list[0].data).astype(np.float64) - # head = hdu_list[0].header - # dstx = abs(head['CDELT1']*np.pi/180) - # dsty = abs(head['CDELT2']*np.pi/180) - # if not ignore_units: - # if head['BUNIT'] == 'JY/BEAM': - # fac = np.pi/4/np.log(2) - # scale = fac*head['BMAJ']*head['BMIN']*(np.pi/180)**2 - # elif head['BUNIT'] == 'JY/PIXEL': - # scale = dstx*dsty - # else: - # scale = 1 - # image_data /= scale - # if from_wsclean: - # image_data = image_data[::-1].T[:, :-1] - # image_data = np.pad(image_data, ((0, 0), (1, 0)), mode='constant') - # else: - # image_data = image_data.T[:, ::-1] - # dom = ift.RGSpace(image_data.shape, (dstx, dsty)) - # return ift.makeField(dom, image_data) +def fits2field(file_name, ignore_units=False, from_wsclean=False): + import astropy.io.fits as pyfits + + with pyfits.open(file_name) as hdu_list: + image_data = hdu_list[0].data.astype(np.float64) + assert image_data.shape[0] == 1 # Only one Stokes component + image_data = image_data[0] + head = hdu_list[0].header + assert head["CUNIT1"].strip() == "deg" + assert head["CUNIT2"].strip() == "deg" + assert head["CUNIT3"].strip() == "Hz" + refs = [] + refs.append([float(head["CRVAL3"]), int(head["CRPIX3"]), head["CDELT3"]]) + refs.append( + [ + float(head["CRVAL2"]) * DEG2RAD, + int(head["CRPIX2"]), + head["CDELT2"] * DEG2RAD, + ] + ) + refs.append( + [ + float(head["CRVAL1"]) * DEG2RAD, + int(head["CRPIX1"]), + head["CDELT1"] * DEG2RAD, + ] + ) + + if not ignore_units: + if head["BUNIT"].upper() == "JY/BEAM": + fac = np.pi / 4 / np.log(2) + scale = fac * head["BMAJ"] * head["BMIN"] * (np.pi / 180) ** 2 + elif head["BUNIT"].upper() == "JY/PIXEL": + scale = abs(refs[0][2] * refs[1][2]) + else: + scale = 1 + image_data /= scale + + # Convert CASA conventions to resolve conventions + inds = 0, 2, 1 + image_data = np.transpose(image_data, inds) + refs = [refs[ii] for ii in inds] + refs[1][2] *= -1 + + for ii, (_, mypx, mydst) in enumerate(refs): + if mydst == 0.0: + raise RuntimeError + if mydst > 0: + continue + image_data = np.flip(image_data, ii) + refs[ii][2] *= -1 + # FIXME Assume pixel counting start at 0. Maybe also 1? + refs[ii][1] = image_data.shape[ii] - mypx + + refval = tuple(refs[ii][0] for ii in range(3)) + refpx = tuple(refs[ii][1] for ii in range(3)) + dsts = tuple(refs[ii][2] for ii in range(3)) + dom = ( + ift.RGSpace(image_data.shape[0], dsts[0]), + ift.RGSpace(image_data.shape[1:], dsts[1:]), + ) + refval = tuple(refval[ii] - refpx[ii] * dsts[ii] for ii in range(3)) + del refpx + return ift.makeField(dom, image_data), refval -- GitLab From e4b6373f757173a211e4021fbbf61ad1171ea0af Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 15 Feb 2021 14:35:28 +0100 Subject: [PATCH 33/37] Add caching for primary beam --- demo/cfg_mosaicing.py | 6 ++++-- resolve/primary_beam.py | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index ec512ef8..11b2013f 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -68,7 +68,7 @@ for kk, oo in sdobs.items(): R = rve.SingleDishResponse( oo, dom, - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x, use_cache=True), global_phase_center, additive_term=additive_term, ) @@ -120,7 +120,9 @@ def get_beamdirections(obs): beam_directions[kk] = rve.BeamDirection( oo.direction.phase_center[0] - global_phase_center[0], oo.direction.phase_center[1] - global_phase_center[1], - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x), + lambda x: rve.alma_beam_func( + 10.7, 0.75, np.mean(oo.freq), x, use_cache=True + ), 0.1, ) return beam_directions diff --git a/resolve/primary_beam.py b/resolve/primary_beam.py index f627f5e2..e13e7738 100644 --- a/resolve/primary_beam.py +++ b/resolve/primary_beam.py @@ -179,11 +179,24 @@ def vla_beam(domain, freq): return ift.makeOp(ift.makeField(dom, beam)) -def alma_beam_func(D, d, freq, x): +def alma_beam_func(D, d, freq, x, use_cache=False): assert isinstance(x, np.ndarray) assert x.ndim < 3 assert np.max(np.abs(x)) < np.pi / np.sqrt(2) + if not use_cache: + return _compute_alma_beam(D, d, freq, x) + + iden = "_".join([str(ll) for ll in [D, d, freq]] + [str(ll) for ll in x.shape]) + fname = f".beamcache{iden}.npy" + try: + return np.load(fname) + except FileNotFoundError: + arr = _compute_alma_beam(D, d, freq, x) + np.save(fname, arr) + + +def _compute_alma_beam(D, d, freq, x): a = freq / SPEEDOFLIGHT b = d / D x = np.pi * a * D * x @@ -191,4 +204,4 @@ def alma_beam_func(D, d, freq, x): x[mask] = 1 sol = 2 / (x * (1 - b ** 2)) * (sc.jn(1, x) - b * sc.jn(1, x * b)) sol[mask] = 1 - return sol ** 2 + return sol * sol -- GitLab From 68aaabba29318d6a88eb549a40dd7f224b971873 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Mon, 15 Feb 2021 14:35:41 +0100 Subject: [PATCH 34/37] Speed up --- demo/cfg_mosaicing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py index 11b2013f..78e7c073 100644 --- a/demo/cfg_mosaicing.py +++ b/demo/cfg_mosaicing.py @@ -18,12 +18,12 @@ rve.set_wgridding(False) interobs_extended = { f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") # for ii in range(5, 135) - for ii in range(5, 7) + for ii in range(5, 6) } interobs_compact = { f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") # for ii in range(5, 153) - for ii in range(5, 7) + for ii in range(5, 6) } interobs = {**interobs_compact, **interobs_extended} sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} -- GitLab From 63ce5d0c986f2e13943151cc53547e199a063e1b Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Tue, 16 Feb 2021 22:22:50 +0100 Subject: [PATCH 35/37] Work on plots --- demo/post_mosaicing.py | 78 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py index 67a2c01a..ead57523 100644 --- a/demo/post_mosaicing.py +++ b/demo/post_mosaicing.py @@ -80,28 +80,90 @@ def main(): for ss in state: skysc.add(c.sky_domaintuple.force(ss)) - sd_comparison(skysc.mean) + logskysc = ift.StatCalculator() + for ss in state: + logskysc.add(c.logsky.force(ss)) + + sc_sd_simdata = ift.StatCalculator() + op = c.responsesd.partial_insert(c.sky) + for ss in state: + sc_sd_simdata.add(op.force(ss)) + sd_comparison(skysc.mean, logskysc.mean, sc_sd_simdata) dirty_images(skysc.mean) -def sd_comparison(mean_sky): +def sd_comparison(mean_sky, mean_logsky, sc_sd_simdata): fld, refval = rve.fits2field("../resources_multipointing/M83.spw17.I.sd.fits") - # FIXME Check with convert_alma_data.py script minfreq = 230172900000.0 ind = int(np.round((minfreq - refval[0]) / fld.domain[0].distances[0])) extractor = ift.DomainTupleFieldInserter(fld.domain, 0, (ind,)).adjoint casasky = extractor(fld) - fig, axs = plt.subplots(1, 2) - axs = list(axs) + obs = c.sdobs["sd0"] + r_casa = ( + rve.SingleDishResponse( + obs, + casasky.domain, + lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(obs.freq), x), + c.global_phase_center, + ) + @ ift.FieldAdapter(casasky.domain, "sky").adjoint + ) + + tmp = casasky.val_rw() + tmp[np.isnan(tmp)] = 0.0 + tmp = ift.makeField(casasky.domain, tmp) + + # FIXME Question: How can I compute data residuals for single dish data? + # FIXME Question: How do I arrive at the correct units? + d0 = obs.vis.val.ravel() + d1 = r_casa(tmp).val.ravel() + scaling_factor = np.vdot(d0, d1) / np.vdot(d1, d1) + print( + "Crazy scaling factor to make CASA reconstruction fit the data better", + scaling_factor, + ) + + tmp = tmp * scaling_factor + d1 = r_casa(tmp).val.ravel() + + plt.hist(d0) + plt.savefig("data.png") + plt.close() + + plt.hist(d1) + plt.savefig("data_sim.png") + plt.close() + + fig, axs = plt.subplots(2, 2) + axs = list(axs.ravel()) + + axx = axs.pop(0) + imshow_symmetric(axx, r_casa.adjoint(obs.vis)) + axx.set_title("Data") axx = axs.pop(0) - imshow_with_colorbar(axx, casasky) + imshow_symmetric(axx, r_casa.adjoint(obs.vis - r_casa(tmp))) + axx.set_title("CASA Residual") + + plt.tight_layout() + plt.savefig("casa_sd_reconstruction.png") + plt.close() + + fig, axs = plt.subplots(2, 2) + axs = list(axs.ravel()) + + axx = axs.pop(0) + imshow_with_colorbar(axx, casasky * scaling_factor, cmap="inferno") axx.set_title("CASA single dish") axx = axs.pop(0) - imshow_with_colorbar(axx, mean_sky) - axx.set_title("Resolve single dish posterior mean") + imshow_with_colorbar(axx, mean_sky, cmap="inferno") + axx.set_title("Resolve single dish") + + axx = axs.pop(0) + imshow_with_colorbar(axx, mean_logsky.exp(), cmap="inferno") + axx.set_title("Resolve log. single dish") plt.tight_layout() fig.savefig("sd_reconstructions.png") -- GitLab From 991a592aa8f0b8524695d8b574b5aa436cb77b42 Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Tue, 16 Feb 2021 22:23:04 +0100 Subject: [PATCH 36/37] Various tweaks --- demo/mosaicing.py | 3 ++- demo/post_mosaicing.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/demo/mosaicing.py b/demo/mosaicing.py index 61151979..9ffb7387 100644 --- a/demo/mosaicing.py +++ b/demo/mosaicing.py @@ -68,11 +68,12 @@ def minimize_with_varcov(lh, varcov_keys, prefix): lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") ) fld = 0.1 * ift.from_random(lh.domain) + fld = ift.MultiField.union([fld, fld.extract_by_keys(varcov_keys) * 0.0]) state = rve.MinimizationState(fld, []) mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) for ii in range(20): state = rve.simple_minimize( - ham, state.mean, 3, mini, constants=varcov_keys if ii < 3 else [] + ham, state.mean, 5, mini, constants=varcov_keys if ii < 2 else [] ) plotter.plot(f"{ii}both", state) state.save(f"{prefix}both") diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py index ead57523..1567860c 100644 --- a/demo/post_mosaicing.py +++ b/demo/post_mosaicing.py @@ -15,17 +15,23 @@ import cfg_mosaicing as c def imshow(ax, fld, **kwargs): + if isinstance(fld.domain, ift.MultiDomain): + fld = ift.FieldAdapter(fld.domain, "sky")(fld) kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} return ax.imshow(fld.val.T, **kwargs) def imshow_with_colorbar(ax, fld, **kwargs): + if isinstance(fld.domain, ift.MultiDomain): + fld = ift.FieldAdapter(fld.domain, "sky")(fld) kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} - im = ax.imshow(fld.val.T, **kwargs) + im = ax.imshow(fld.T, **kwargs) plt.colorbar(im, ax=ax) def imshow_symmetric(ax, fld): + if isinstance(fld.domain, ift.MultiDomain): + fld = ift.FieldAdapter(fld.domain, "sky")(fld) lim = np.max(np.abs(fld.val)) im = imshow(ax, fld, vmin=-lim, vmax=lim, cmap="seismic") plt.colorbar(im, ax=ax) @@ -56,6 +62,11 @@ def dirty_images(mean_sky): ] weights.append(reduce(lambda a, b: a.unite(b), weights)) + # Remove single dish response since it is not linear if additive_term is taken into account + responses = responses[1:] + data = data[1:] + weights = weights[1:] + fig, axs = plt.subplots(5, 5, figsize=(20, 20), sharey=True, sharex=True) for ii, (R, icov, d) in enumerate(zip(responses, weights, data)): -- GitLab From e9d8c0a702d63fa4fcd646396eb9410b8b5c298e Mon Sep 17 00:00:00 2001 From: Philipp Arras <parras@mpa-garching.mpg.de> Date: Sat, 12 Jun 2021 13:10:27 +0200 Subject: [PATCH 37/37] Cleanup --- demo/cfg_mosaicing.py | 138 ---------------------------- demo/debug_mosaicing.py | 66 -------------- demo/mosaicing.py | 83 ----------------- demo/post_mosaicing.py | 185 -------------------------------------- demo/pre_mosaicing.py | 80 ----------------- misc/convert_alma_data.py | 141 ----------------------------- 6 files changed, 693 deletions(-) delete mode 100644 demo/cfg_mosaicing.py delete mode 100644 demo/debug_mosaicing.py delete mode 100644 demo/mosaicing.py delete mode 100644 demo/post_mosaicing.py delete mode 100644 demo/pre_mosaicing.py delete mode 100644 misc/convert_alma_data.py diff --git a/demo/cfg_mosaicing.py b/demo/cfg_mosaicing.py deleted file mode 100644 index 78e7c073..00000000 --- a/demo/cfg_mosaicing.py +++ /dev/null @@ -1,138 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2021 Max-Planck-Society -# Author: Philipp Arras - -import numpy as np -from functools import reduce -from operator import add - -import nifty7 as ift -import resolve as rve - - -diffusefluxlevel = 29 -npix = np.array([1800, 1800]) - -rve.set_nthreads(2) -rve.set_wgridding(False) -interobs_extended = { - f"extended{ii}": rve.Observation.load(f"/data/mosaicing/extended_field{ii}.npz") - # for ii in range(5, 135) - for ii in range(5, 6) -} -interobs_compact = { - f"compact{ii}": rve.Observation.load(f"/data/mosaicing/compact_field{ii}.npz") - # for ii in range(5, 153) - for ii in range(5, 6) -} -interobs = {**interobs_compact, **interobs_extended} -sdobs = {"sd0": rve.SingleDishObservation.load("/data/mosaicing/totalpower.npz")} -assert len(set(sdobs.keys()) & set(interobs.keys())) == 0 -rve.set_epsilon(1 / 10 / max(o.max_snr() for o in interobs.values())) -# Compute phase center and define domain -# Convention: The phase center is the coordinate of the [nx/2, ny/2] pixel -xs, ys = [], [] -for vv in interobs.values(): - xs.append(vv.direction.phase_center[0]) - ys.append(vv.direction.phase_center[1]) -for vv in sdobs.values(): - foo, bar = vv.pointings.phase_centers.T - xs.extend(foo) - ys.extend(bar) -xs, ys = np.array(xs), np.array(ys) -global_phase_center_casa = -2.7180339078174175, -0.5210930907116116 -global_phase_center = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2 -margin = 1 * rve.ARCMIN2RAD -xfov = 2 * max(abs(xs - global_phase_center[0])) + 2 * margin -yfov = 2 * max(abs(ys - global_phase_center[1])) + 2 * margin -fov = np.array([xfov, yfov]) -dom = ift.RGSpace(npix, fov / npix) -# End compute phase center and define domain - -# Sky model -logsky = ift.SimpleCorrelatedField( - dom, diffusefluxlevel, (1, 1), (1.5, 1), (1.2, 0.4), (0.2, 0.2), (-1.5, 0.5) -) -sky_domaintuple = logsky.exp() -sky = sky_domaintuple.ducktape_left("sky") -# End sky model - -# Single dish likelihood -responsesd = [] -additive_term = ift.SimpleCorrelatedField( - dom, 0, (1, 1), (1, 1), (1, 1), None, (-2, 0.5), "negativity_madness" -) -for kk, oo in sdobs.items(): - # Single dish response does not support multifrequency - assert np.min(oo.freq) / np.max(oo.freq) > 0.99 - R = rve.SingleDishResponse( - oo, - dom, - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(oo.freq), x, use_cache=True), - global_phase_center, - additive_term=additive_term, - ) - responsesd.append(R.ducktape_left(kk)) -responsesd = reduce(add, responsesd) -dsd = ift.MultiField.from_dict({kk: o.vis for kk, o in sdobs.items()}) -invcovsd = ift.makeOp( - ift.MultiField.from_dict({kk: o.weight for kk, o in sdobs.items()}) -) -lhsd = ift.GaussianEnergy(dsd, invcovsd) @ responsesd - -if True: - # Learn one number per observation - foo = [] - for kk in dsd.keys(): - foo.append( - ift.ContractionOperator(invcovsd.domain[kk], (0, 1, 2)) - .adjoint.ducktape(kk + "invcovinp") - .ducktape_left(kk) - ) - invcovsdop = invcovsd @ reduce(add, foo).exp() -else: - # Learn one number per data point - invcovsdop = invcovsd @ rve.KeyPrefixer(dsd.domain, "invcov_inp").adjoint.exp() - -lhsd_varcov = rve.MultiDomainVariableCovarianceGaussianEnergy( - dsd, responsesd, invcovsdop -) -# End single dish likelihood - -# Consistency checks -ift.extra.assert_allclose( - invcovsdop(ift.full(invcovsdop.domain, 0.0)), - invcovsd(ift.full(invcovsd.domain, 1.0)), -) -_, lhsd1 = lhsd_varcov.simplify_for_constant_input(ift.full(invcovsdop.domain, 0.0)) -foo = ift.from_random(lhsd1.domain) -# FIXME Inaccuracy probably due to tr-log term in VariableCovGaussianEnergy -ift.extra.assert_allclose(lhsd(foo), lhsd1(foo), rtol=1e-2) -del lhsd1, foo -# End consistency check - - -# Interferometer likelihood -def get_beamdirections(obs): - beam_directions = {} - for kk, oo in obs.items(): - assert np.min(oo.freq) / np.max(oo.freq) > 0.99 - beam_directions[kk] = rve.BeamDirection( - oo.direction.phase_center[0] - global_phase_center[0], - oo.direction.phase_center[1] - global_phase_center[1], - lambda x: rve.alma_beam_func( - 10.7, 0.75, np.mean(oo.freq), x, use_cache=True - ), - 0.1, - ) - return beam_directions - - -skyslicer_compact = rve.SkySlicer(logsky.target, get_beamdirections(interobs_compact)) -skyslicer_extended = rve.SkySlicer(logsky.target, get_beamdirections(interobs_extended)) -ift.extra.check_linear_operator(skyslicer_compact) -lh_compact = rve.ImagingLikelihood(interobs_compact, skyslicer_compact).ducktape("sky") -lh_extended = rve.ImagingLikelihood(interobs_extended, skyslicer_extended).ducktape( - "sky" -) -# End interferometer likelihood diff --git a/demo/debug_mosaicing.py b/demo/debug_mosaicing.py deleted file mode 100644 index 1c561bc8..00000000 --- a/demo/debug_mosaicing.py +++ /dev/null @@ -1,66 +0,0 @@ -import resolve as rve -import numpy as np -import matplotlib.pyplot as plt -import nifty7 as ift -import cfg_mosaicing as c - - -if False: - state = rve.MinimizationState.load("sditer") - bins = np.linspace(-20, 20, 40) - for kk in c.invcovsd.target.keys(): - for ss in state: - arr = ( - (c.invcovsd.get_sqrt() @ ift.Adder(c.dsd, True) @ c.responsesd @ c.sky) - .force(ss) - .val - ) - arr = c.sdobs[kk].apply_flags(arr[kk]).ravel() - plt.hist(arr.clip(-20, 20), bins=bins, alpha=0.5) - print("Reduced chi**2:", np.sum(arr ** 2) / arr.size) - plt.title(f"Noise weighted residual {kk}") - plt.savefig(f"debug_nwresi_{kk}.png") - plt.close() -else: - state = rve.MinimizationState.load("sditer") - state = rve.MinimizationState.load("sdboth") - - plotter = rve.Plotter("png", "debug") - plotter.add("additive term", c.additive_term) - plotter.add("sky", ift.FieldAdapter(c.sky.target["sky"], "sky")@ c.sky, cmap="inferno_r") - plotter.add("additive term power spectrum", c.additive_term.power_spectrum) - plotter.plot("debug", state) - - learnnoise = True - if learnnoise: - upperlim = 10 - bins = np.linspace(0, upperlim, upperlim * 10) - for kk in c.invcovsdop.target.keys(): - for ss in state: - learnedsd = 1 / c.invcovsdop.force(ss)[kk].sqrt() - datasd = 1 / c.invcovsd(ift.full(c.invcovsd.domain, 1.0))[kk].sqrt() - plt.hist( - (learnedsd / datasd).sqrt().val.ravel().clip(0, upperlim), bins=bins - ) - plt.axvline(1.0) - plt.xlabel("Learned sigma / sigma in measurement set") - plt.savefig(f"debug{kk}.png") - plt.close() - - - bins = np.linspace(-20, 20, 40) - for kk in c.invcovsd.target.keys(): - for ss in state: - arr = (ift.Adder(c.dsd, True) @ c.responsesd.partial_insert(c.sky)).force( - ss - ) - if learnnoise: - arr = arr * c.invcovsdop.force(ss).sqrt() - else: - arr = c.invcovsd.get_sqrt()(arr) - arr = c.sdobs[kk].apply_flags(arr.val[kk]).ravel() - plt.hist(arr.clip(-20, 20), bins=bins, alpha=0.5) - print("Reduced chi**2:", np.sum(arr ** 2) / arr.size) - plt.title(f"Noise weighted residual {kk}") - plt.savefig(f"debug_nwresi_{kk}.png") - plt.close() diff --git a/demo/mosaicing.py b/demo/mosaicing.py deleted file mode 100644 index 9ffb7387..00000000 --- a/demo/mosaicing.py +++ /dev/null @@ -1,83 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2019-2021 Max-Planck-Society -# Author: Philipp Arras - -import matplotlib.pyplot as plt - -import cfg_mosaicing as c -import nifty7 as ift -import resolve as rve - - -@rve.onlymaster -def imsave(fname, field): - plt.imsave(fname, field.val.T, origin="lower") - plt.close() - - -def main(): - if rve.mpi.master: - print( - f"Field of view: {c.xfov/rve.ARCMIN2RAD:.2f}' x {c.yfov/rve.ARCMIN2RAD:.2f}'" - ) - - # minimize(c.lhsd.partial_insert(c.sky), "sd") - minimize_with_varcov( - c.lhsd_varcov.partial_insert(c.sky), c.invcovsdop.domain.keys(), "sd" - ) - exit() - - minimize(c.lh_compact @ c.sky, "compact") - minimize(c.lh_extended @ c.sky, "extended") - minimize((c.lh_compact + c.lh_extended + c.lhsd) @ c.sky, "all") - - -def minimize(lh, prefix): - plotter = rve.Plotter("png", f"plots_{prefix}") - plotter.add("logsky", c.logsky) - plotter.add( - "sky", ift.FieldAdapter(c.sky.target["sky"], "sky") @ c.sky, cmap="inferno_r" - ) - plotter.add("power spectrum logsky", c.logsky.power_spectrum) - plotter.add("additive term", c.additive_term) - plotter.add("additive term power spectrum", c.additive_term.power_spectrum) - - ham = ift.StandardHamiltonian( - lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") - ) - fld = 0.1 * ift.from_random(lh.domain) - state = rve.MinimizationState(fld, []) - mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) - for ii in range(20): - state = rve.simple_minimize(ham, state.mean, 3, mini) - plotter.plot(f"iter{ii}", state) - state.save(f"{prefix}iter") - - -def minimize_with_varcov(lh, varcov_keys, prefix): - assert set(varcov_keys) < set(lh.domain.keys()) - plotter = rve.Plotter("png", f"plots_{prefix}") - plotter.add("logsky", c.logsky) - plotter.add( - "sky", ift.FieldAdapter(c.sky.target["sky"], "sky") @ c.sky, cmap="inferno_r" - ) - plotter.add("power spectrum logsky", c.logsky.power_spectrum) - plotter.add("additive term", c.additive_term) - plotter.add("additive term power spectrum", c.additive_term.power_spectrum) - ham = ift.StandardHamiltonian( - lh, ift.AbsDeltaEnergyController(0.5, 5, 300, name="Sampling") - ) - fld = 0.1 * ift.from_random(lh.domain) - fld = ift.MultiField.union([fld, fld.extract_by_keys(varcov_keys) * 0.0]) - state = rve.MinimizationState(fld, []) - mini = ift.NewtonCG(ift.GradientNormController(name="newton", iteration_limit=5)) - for ii in range(20): - state = rve.simple_minimize( - ham, state.mean, 5, mini, constants=varcov_keys if ii < 2 else [] - ) - plotter.plot(f"{ii}both", state) - state.save(f"{prefix}both") - - -if __name__ == "__main__": - main() diff --git a/demo/post_mosaicing.py b/demo/post_mosaicing.py deleted file mode 100644 index 1567860c..00000000 --- a/demo/post_mosaicing.py +++ /dev/null @@ -1,185 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2021 Max-Planck-Society -# Author: Philipp Arras - -from functools import reduce -from operator import add - -import matplotlib.pyplot as plt -import numpy as np - -import nifty7 as ift -import resolve as rve - -import cfg_mosaicing as c - - -def imshow(ax, fld, **kwargs): - if isinstance(fld.domain, ift.MultiDomain): - fld = ift.FieldAdapter(fld.domain, "sky")(fld) - kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} - return ax.imshow(fld.val.T, **kwargs) - - -def imshow_with_colorbar(ax, fld, **kwargs): - if isinstance(fld.domain, ift.MultiDomain): - fld = ift.FieldAdapter(fld.domain, "sky")(fld) - kwargs = {**dict(origin="lower", cmap="inferno_r"), **kwargs} - im = ax.imshow(fld.T, **kwargs) - plt.colorbar(im, ax=ax) - - -def imshow_symmetric(ax, fld): - if isinstance(fld.domain, ift.MultiDomain): - fld = ift.FieldAdapter(fld.domain, "sky")(fld) - lim = np.max(np.abs(fld.val)) - im = imshow(ax, fld, vmin=-lim, vmax=lim, cmap="seismic") - plt.colorbar(im, ax=ax) - - -def dirty_images(mean_sky): - responses = [c.responsesd] - responses.extend( - [ - rve.StokesIResponse(obs, skyslc.target) @ skyslc.ducktape("sky") - for obs, skyslc in ( - (c.interobs_extended, c.skyslicer_extended), - (c.interobs_compact, c.skyslicer_compact), - ) - ] - ) - responses.append(reduce(add, responses)) - - data = [ - ift.MultiField.from_dict({kk: o.vis for kk, o in obs.items()}) - for obs in [c.sdobs, c.interobs_extended, c.interobs_compact, c.interobs] - ] - data.append(reduce(lambda a, b: a.unite(b), data)) - - weights = [ - ift.MultiField.from_dict({kk: o.weight for kk, o in obs.items()}) - for obs in [c.sdobs, c.interobs_extended, c.interobs_compact, c.interobs] - ] - weights.append(reduce(lambda a, b: a.unite(b), weights)) - - # Remove single dish response since it is not linear if additive_term is taken into account - responses = responses[1:] - data = data[1:] - weights = weights[1:] - - fig, axs = plt.subplots(5, 5, figsize=(20, 20), sharey=True, sharex=True) - - for ii, (R, icov, d) in enumerate(zip(responses, weights, data)): - jj = 0 - imshow_symmetric(axs[ii, jj], R.adjoint(ift.full(R.target, 1.0))) - jj += 1 - imshow_symmetric(axs[ii, jj], R.adjoint(icov * d)) - jj += 1 - imshow_symmetric(axs[ii, jj], R.adjoint(icov.sqrt() * d)) - jj += 1 - residual = d - R(mean_sky) - imshow_symmetric(axs[ii, jj], R.adjoint(icov * residual)) - jj += 1 - imshow_symmetric(axs[ii, jj], R.adjoint(icov.sqrt() * residual)) - plt.tight_layout() - plt.savefig("dirty_overview.png") - - -def main(): - state = rve.MinimizationState.load("sdboth") - skysc = ift.StatCalculator() - for ss in state: - skysc.add(c.sky_domaintuple.force(ss)) - - logskysc = ift.StatCalculator() - for ss in state: - logskysc.add(c.logsky.force(ss)) - - sc_sd_simdata = ift.StatCalculator() - op = c.responsesd.partial_insert(c.sky) - for ss in state: - sc_sd_simdata.add(op.force(ss)) - sd_comparison(skysc.mean, logskysc.mean, sc_sd_simdata) - dirty_images(skysc.mean) - - -def sd_comparison(mean_sky, mean_logsky, sc_sd_simdata): - fld, refval = rve.fits2field("../resources_multipointing/M83.spw17.I.sd.fits") - minfreq = 230172900000.0 - ind = int(np.round((minfreq - refval[0]) / fld.domain[0].distances[0])) - extractor = ift.DomainTupleFieldInserter(fld.domain, 0, (ind,)).adjoint - casasky = extractor(fld) - - obs = c.sdobs["sd0"] - r_casa = ( - rve.SingleDishResponse( - obs, - casasky.domain, - lambda x: rve.alma_beam_func(10.7, 0.75, np.mean(obs.freq), x), - c.global_phase_center, - ) - @ ift.FieldAdapter(casasky.domain, "sky").adjoint - ) - - tmp = casasky.val_rw() - tmp[np.isnan(tmp)] = 0.0 - tmp = ift.makeField(casasky.domain, tmp) - - # FIXME Question: How can I compute data residuals for single dish data? - # FIXME Question: How do I arrive at the correct units? - d0 = obs.vis.val.ravel() - d1 = r_casa(tmp).val.ravel() - scaling_factor = np.vdot(d0, d1) / np.vdot(d1, d1) - print( - "Crazy scaling factor to make CASA reconstruction fit the data better", - scaling_factor, - ) - - tmp = tmp * scaling_factor - d1 = r_casa(tmp).val.ravel() - - plt.hist(d0) - plt.savefig("data.png") - plt.close() - - plt.hist(d1) - plt.savefig("data_sim.png") - plt.close() - - fig, axs = plt.subplots(2, 2) - axs = list(axs.ravel()) - - axx = axs.pop(0) - imshow_symmetric(axx, r_casa.adjoint(obs.vis)) - axx.set_title("Data") - - axx = axs.pop(0) - imshow_symmetric(axx, r_casa.adjoint(obs.vis - r_casa(tmp))) - axx.set_title("CASA Residual") - - plt.tight_layout() - plt.savefig("casa_sd_reconstruction.png") - plt.close() - - fig, axs = plt.subplots(2, 2) - axs = list(axs.ravel()) - - axx = axs.pop(0) - imshow_with_colorbar(axx, casasky * scaling_factor, cmap="inferno") - axx.set_title("CASA single dish") - - axx = axs.pop(0) - imshow_with_colorbar(axx, mean_sky, cmap="inferno") - axx.set_title("Resolve single dish") - - axx = axs.pop(0) - imshow_with_colorbar(axx, mean_logsky.exp(), cmap="inferno") - axx.set_title("Resolve log. single dish") - - plt.tight_layout() - fig.savefig("sd_reconstructions.png") - plt.close() - - -if __name__ == "__main__": - main() diff --git a/demo/pre_mosaicing.py b/demo/pre_mosaicing.py deleted file mode 100644 index efae1cb3..00000000 --- a/demo/pre_mosaicing.py +++ /dev/null @@ -1,80 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2021 Max-Planck-Society -# Author: Philipp Arras - -import matplotlib.pyplot as plt -import numpy as np -import nifty7 as ift - -import cfg_mosaicing as c - - -def plot_sd_data(): - # FIXME QUESTION: In which unit is the single dish data? Jy/beam? Maybe learn relative factor between single dish and interferometer or figure out the correct factor. - for kk, vv in c.dsd.items(): - obs = c.sdobs[kk] - directions = obs.pointings.phase_centers - ind = np.any(obs.vis.val <= 0.0, axis=(0, 2)) - plt.scatter(*directions[ind].T, label="Non-positive", alpha=0.4, s=2) - plt.scatter(*directions[~ind].T, label="Positive", alpha=0.4, s=2) - plt.gca().invert_xaxis() - plt.gca().set_aspect("equal") - plt.legend() - plt.savefig(f"single_dish_negative_{kk}.png") - plt.close() - - vv = obs.apply_flags(vv.val) - lim = np.max(np.abs(vv)) - plt.hist(vv.ravel(), bins=np.arange(-lim, lim + 1, 1)) - plt.xlabel("Flux [???]") - plt.ylabel("Count") - plt.title("Single dish data") - plt.savefig(f"single_dish_data_{kk}.png") - plt.close() - - -def plot_pointings(): - xs, ys = [], [] - for vv in c.interobs.values(): - xs.append(vv.direction.phase_center[0]) - ys.append(vv.direction.phase_center[1]) - xs, ys = np.array(xs), np.array(ys) - plt.scatter(xs, ys, label="Interferometer") - for kk, vv in c.sdobs.items(): - plt.scatter( - *vv.pointings.phase_centers.T, label="Single dish " + kk, alpha=0.2, s=1 - ) - plt.scatter(*c.global_phase_center_casa, label="Phase center CASA") - plt.scatter(*c.global_phase_center, label="Phase center resolve") - plt.xlim( - [c.global_phase_center[0] - c.xfov / 2, c.global_phase_center[0] + c.xfov / 2] - ) - plt.ylim( - [c.global_phase_center[1] - c.yfov / 2, c.global_phase_center[1] + c.yfov / 2] - ) - plt.gca().invert_xaxis() - plt.gca().set_aspect("equal") - plt.legend() - plt.savefig("pointings.png") - plt.close() - - -def plot_sky_prior(): - p = ift.Plot() - pspecs = [] - for _ in range(8): - pos = ift.from_random(c.sky.domain) - p.add(c.logsky(pos)) - pspecs.append(c.logsky.power_spectrum.force(pos)) - p.add(pspecs) - p.output(name="sky_prior.png") - - -def main(): - plot_sky_prior() - plot_pointings() - plot_sd_data() - - -if __name__ == "__main__": - main() diff --git a/misc/convert_alma_data.py b/misc/convert_alma_data.py deleted file mode 100644 index fd578ae2..00000000 --- a/misc/convert_alma_data.py +++ /dev/null @@ -1,141 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# Copyright(C) 2021 Max-Planck-Society -# Author: Philipp Arras - -from os.path import join, split, splitext - -import numpy as np -from casacore.tables import table, taql - -import resolve as rve - - -def cleanup(weight, vis, freq, uvw=None): - assert weight.ndim == 2 - assert vis.shape == weight.shape - assert freq.ndim == 1 - assert freq.size == vis.shape[1] - assert uvw.shape[0] == vis.shape[0] - # Clean up rows - ind = np.any(weight != 0.0, axis=1) - mywgt = weight[ind] - myvis = vis[ind] - if uvw is not None: - myuvw = uvw[ind] - - # Clean up freqs - ind = np.any(mywgt != 0.0, axis=0) - mywgt = mywgt[:, ind] - myvis = myvis[:, ind] - myfreq = freq[ind] - myvis[mywgt == 0.0] = 0.0 - if uvw is None: - return mywgt, myvis, myfreq - return mywgt, myvis, myfreq, myuvw - - -_CASACORE_TABLE_CFG = {"readonly": True, "ack": False} -minfreq = 230172900000.0 -# if outfbase == "compact": -# minfreq -= 27146364.99822998 -maxfreq = minfreq * 1.000015 - -for f, spectral_window, single_dish, fieldid in [ - # ("extended.ms", 0, False, None), - # ("compact.ms", 0, False, None), - ("totalpower.ms", 17, True, 1) -]: - outfbase = split(splitext(f)[0])[1] - - with table(join(f, "SPECTRAL_WINDOW"), **_CASACORE_TABLE_CFG) as t: - freq = taql("select CHAN_FREQ from $t where ROWID()=$spectral_window").getcol( - "CHAN_FREQ" - ) - freq = np.squeeze(freq) - - if not np.all(np.diff(freq) >= 0): - a = freq[::-1] - assert np.all(np.diff(a) >= 0) - ind1 = freq.size - np.searchsorted(a, minfreq) + 1 - ind0 = freq.size - np.searchsorted(a, maxfreq) + 1 - else: - assert np.all(np.diff(freq) >= 0) - ind0 = np.searchsorted(freq, minfreq) - ind1 = np.searchsorted(freq, maxfreq) - channel_slice = slice(ind0, ind1) - freq = freq[channel_slice] - - if not single_dish: - with table(join(f, "FIELD"), **_CASACORE_TABLE_CFG) as t: - field_directions = np.squeeze(t.getcol("DELAY_DIR")) - assert field_directions.ndim == 2 - assert field_directions.shape[1] == 2 - - with table(f, **_CASACORE_TABLE_CFG) as t: - sel = lambda x: np.array( - taql(f"select {x} from $t where DATA_DESC_ID=$spectral_window").getcol(x) - ) - wgt = sel("WEIGHT")[:, None] - wgt = np.repeat(wgt, len(freq), axis=1) - flag = sel("FLAG")[:, channel_slice] - wgt[flag] = 0.0 - del flag - - vis = sel("FLOAT_DATA" if single_dish else "DATA")[:, channel_slice] - # # FIXME QUESTION Why is there negative data? - # if single_dish: - # assert not np.iscomplexobj(vis) - # ind = vis * np.sqrt(wgt) <= 2.0 - # print( - # f"{np.sum(ind)} data points are not 2-sigma positive ({np.sum(ind)/ind.size*100:.1f} %)" - # ) - # wgt[ind] = 0.0 - - # Average polarization - assert vis.shape[2] == 2 - assert wgt.shape[2] == 2 - vis = np.sum(wgt * vis, axis=-1) - wgt = np.sum(wgt, axis=2) - vis /= wgt - field = sel("FIELD_ID") - if single_dish: - wgt[field != fieldid] = 0.0 - del field - else: - uvw = sel("UVW") - - if single_dish: - with table(f, readonly=True) as t: - pointings = taql( - "select DIRECTION from $t::POINTING where ROWID() in [select from $t where DATA_DESC_ID=$spectral_window giving [ROWID()]]" - ).getcol("DIRECTION") - mywgt, myvis, myfreq, mypointings = cleanup(wgt, vis, freq, pointings) - assert mypointings.shape[1] == 1 - mypointings = mypointings[:, 0] - mypointings = rve.Directions(mypointings, 2000) - obs = rve.SingleDishObservation( - mypointings, - np.ascontiguousarray(myvis[None]), - np.ascontiguousarray(mywgt[None]), - rve.Polarization.trivial(), - myfreq, - ) - obs.save(f"{outfbase}.npz", compress=False) - else: - for ifield in set(field): - ww = wgt.copy() - ww[field != ifield] = 0.0 - mywgt, myvis, myfreq, myuvw = cleanup(ww, vis, freq, uvw) - print(ifield, myuvw.shape, myvis.shape, mywgt.shape, myfreq.shape) - - obs = rve.Observation( - rve.AntennaPositions(np.ascontiguousarray(myuvw)), - np.ascontiguousarray(myvis[None]), - np.ascontiguousarray(mywgt[None]), - rve.Polarization.trivial(), - myfreq, - rve.Direction(field_directions[ifield], 2000), - ) - obs.save(f"{outfbase}_field{ifield}.npz", compress=False) - check = rve.Observation.load(f"{outfbase}_field{ifield}.npz") - assert check == obs -- GitLab