diff --git a/resolve/__init__.py b/resolve/__init__.py
index 4a1ade5b438f0cae57c7cee0a4e78a7ded42e6c3..68cdefef795f56efc4bf8836d86271b2eab1df6b 100644
--- a/resolve/__init__.py
+++ b/resolve/__init__.py
@@ -23,6 +23,7 @@ from .plotter import MfPlotter, Plotter
 from .points import PointInserter
 from .data.polarization import Polarization
 from .polarization_matrix_exponential import *
+from .polarization_space import *
 from .response import MfResponse, ResponseDistributor, StokesIResponse, SingleResponse
 from .simple_operators import *
 from .util import *
diff --git a/resolve/data/observation.py b/resolve/data/observation.py
index fd44cde7b90aac90940664e30da2689aa37f6fd4..b4c7934d80e8cd6791bda4fc348faeebe0a6310d 100644
--- a/resolve/data/observation.py
+++ b/resolve/data/observation.py
@@ -18,7 +18,8 @@ from .polarization import Polarization
 class BaseObservation:
     @property
     def _dom(self):
-        dom = [ift.UnstructuredDomain(ss) for ss in self._vis.shape]
+        pol_dom = self.polarization.space
+        dom = [pol_dom] + [ift.UnstructuredDomain(ss) for ss in self._vis.shape[1:]]
         return ift.makeDomain(dom)
 
     @property
@@ -294,6 +295,8 @@ class Observation(BaseObservation):
         my_assert(np.all(np.isfinite(vis[weight > 0.])))
         my_assert(np.all(np.isfinite(weight)))
 
+        my_assert(np.all(np.diff(freq)))
+
         vis.flags.writeable = False
         weight.flags.writeable = False
 
@@ -476,6 +479,24 @@ class Observation(BaseObservation):
             self._antpos, vis, wgt, Polarization.trivial(), self._freq, self._auxiliary_tables
         )
 
+    def restrict_by_time(self, tmin, tmax, with_index=False):
+        my_assert(all(np.diff(self.time) >= 0))
+        start, stop = np.searchsorted(self.time, [tmin, tmax])
+        ind = slice(start, stop)
+        res = self[ind]
+        if with_index:
+            return res, ind
+        return res
+
+    def restrict_by_freq(self, fmin, fmax, with_index=False):
+        my_assert(all(np.diff(self.freq) > 0))
+        start, stop = np.searchsorted(self.freq, [fmin, fmax])
+        ind = slice(start, stop)
+        res = self.get_freqs_by_slice(ind)
+        if with_index:
+            return res, ind
+        return res
+
     def restrict_to_stokesi(self):
         # FIXME Do I need to change something in self._auxiliary_tables?
         my_asserteq(self._vis.shape[0], 4)
@@ -501,6 +522,53 @@ class Observation(BaseObservation):
             self._auxiliary_tables
         )
 
+    def time_average(self, list_of_timebins):
+        # FIXME check that timebins do not overlap
+        # time, ant1, ant2
+        ts = self._antpos.time
+        row_to_bin_map = np.empty(ts.shape)
+        row_to_bin_map[:] = np.nan
+
+        for ii, (lo, hi) in enumerate(list_of_timebins):
+            ind = np.logical_and(ts >= lo, ts < hi)
+            assert np.all(np.isnan(row_to_bin_map[ind]))
+            row_to_bin_map[ind] = ii
+
+        assert np.all(~np.isnan(row_to_bin_map))
+        row_to_bin_map = row_to_bin_map.astype(int)
+
+        ant1 = self._antpos.ant1
+        ant2 = self._antpos.ant2
+        assert np.max(ant1) < 1000
+        assert np.max(ant2) < 1000
+        assert np.max(row_to_bin_map) < np.iinfo(np.dtype("int64")).max / 1000000
+        atset = set(zip(ant1, ant2, row_to_bin_map))
+        dct = {aa: ii for ii, aa in enumerate(atset)}
+        dct_inv = {yy: xx for xx, yy in dct.items()}
+        masterindex = np.array([dct[(a1, a2, tt)] for a1, a2, tt in zip(ant1, ant2, row_to_bin_map)])
+
+        vis, wgt = self.vis.val, self.weight.val
+        new_vis = np.empty((self.npol, len(atset), self.nfreq), dtype=self.vis.dtype)
+        new_wgt = np.empty((self.npol, len(atset), self.nfreq), dtype=self.weight.dtype)
+        for pol in range(self.npol):
+            for freq in range(self.nfreq):
+                enum = np.bincount(masterindex, weights=vis[pol, :, freq].real*wgt[pol, :, freq])
+                enum = enum + 1j*np.bincount(masterindex, weights=vis[pol, :, freq].imag*wgt[pol, :, freq])
+                denom = np.bincount(masterindex, weights=wgt[pol, :, freq])
+                new_vis[pol, :, freq] = enum/denom
+                new_wgt[pol, :, freq] = denom
+
+        new_times = np.array([dct_inv[ii][2] for ii in range(len(atset))])
+        new_times = np.mean(np.array(list_of_timebins), axis=1)[new_times]
+
+        new_ant1 = np.array([dct_inv[ii][0] for ii in range(len(atset))])
+        new_ant2 = np.array([dct_inv[ii][1] for ii in range(len(atset))])
+        new_uvw = ap.uvw
+        # FIXME Find correct uvw
+        ap = self._antpos
+        ap = AntennaPositions(new_uvw, new_ant1, new_ant2, new_times)
+        return Observation(ap, new_vis, new_wgt, self._polarization, self._freq, self._auxiliary_tables)
+
     @property
     def uvw(self):
         return self._antpos.uvw
@@ -509,6 +577,18 @@ class Observation(BaseObservation):
     def antenna_positions(self):
         return self._antpos
 
+    @property
+    def ant1(self):
+        return self._antpos.ant1
+
+    @property
+    def ant2(self):
+        return self._antpos.ant2
+
+    @property
+    def time(self):
+        return self._antpos.time
+
     @property
     def direction(self):
         if self._auxiliary_tables is not None and "FIELD" in self._auxiliary_tables:
diff --git a/resolve/data/polarization.py b/resolve/data/polarization.py
index 3bbbd199cef3a65a20fa97e74e49fdfaf9cba295..48b2c79c645c055242cca75795836cee1305b938 100644
--- a/resolve/data/polarization.py
+++ b/resolve/data/polarization.py
@@ -68,6 +68,15 @@ class Polarization:
     def to_str_list(self):
         return [TABLE[ii] for ii in self._ind]
 
+    @property
+    def space(self):
+        from ..polarization_space import PolarizationSpace
+        if self == Polarization.trivial():
+            x = "I"
+        else:
+            x = [TABLE[ii] for ii in self._ind]
+        return PolarizationSpace(x)
+
     @staticmethod
     def from_list(lst):
         return Polarization(lst)
diff --git a/resolve/likelihood.py b/resolve/likelihood.py
index 7e75481a576326cbf3bfcfb1dd21dfd9348d389c..54c9ed462895fd652a9118a32abd05cbb5cd7967 100644
--- a/resolve/likelihood.py
+++ b/resolve/likelihood.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 functools import reduce
diff --git a/resolve/multi_frequency/irg_space.py b/resolve/multi_frequency/irg_space.py
index 70a07f7126b0e22df548d08dac98ee6c96bd7da0..a2a23bc76611820836e7c0562ebb1d8fe2b9d942 100644
--- a/resolve/multi_frequency/irg_space.py
+++ b/resolve/multi_frequency/irg_space.py
@@ -12,8 +12,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 # Copyright(C) 2013-2020 Max-Planck-Society
-#
-# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
 
 import numpy as np
 
diff --git a/resolve/multi_frequency/operators.py b/resolve/multi_frequency/operators.py
index 11aae3dbb3e0e2ca7fc867643daed1c265bb87ad..b77f98b2ecd8e21dacd600472e5297b722d63c76 100644
--- a/resolve/multi_frequency/operators.py
+++ b/resolve/multi_frequency/operators.py
@@ -2,10 +2,10 @@
 # Copyright(C) 2013-2021 Max-Planck-Society
 # Authors: Philipp Frank, Philipp Arras, Philipp Haim
 
-import numpy as np
-
 import nifty8 as ift
+import numpy as np
 
+from ..polarization_space import PolarizationSpace
 from ..util import my_assert, my_assert_isinstance, my_asserteq
 from .irg_space import IRGSpace
 
@@ -91,7 +91,7 @@ class MfWeightingInterpolation(ift.LinearOperator):
         my_asserteq(domain.shape[0], eff_uvw.shape[2])  # freqaxis
         self._domain = domain
         nrow, nfreq = eff_uvw.shape[1:]
-        tgt = [ift.UnstructuredDomain(aa) for aa in [1, nrow, nfreq]]
+        tgt = [PolarizationSpace("I")] + [ift.UnstructuredDomain(aa) for aa in [nrow, nfreq]]
         self._target = ift.DomainTuple.make(tgt)
         self._capability = self.TIMES | self.ADJOINT_TIMES
         # FIXME Try to unify all those operators which loop over freq dimension
diff --git a/resolve/polarization_space.py b/resolve/polarization_space.py
new file mode 100644
index 0000000000000000000000000000000000000000..dad3e786189775d14b398350c3d5e0c1402f902a
--- /dev/null
+++ b/resolve/polarization_space.py
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright(C) 2021 Max-Planck-Society
+# Author: Philipp Arras, Jakob Knollmüller
+
+import nifty8 as ift
+
+from .util import my_assert
+
+
+class PolarizationSpace(ift.UnstructuredDomain):
+    """
+
+    Parameters
+    ----------
+    coordinates : np.ndarray
+        Must be sorted and strictly ascending.
+    """
+
+    _needed_for_hash = ["_shape", "_lbl"]
+
+    def __init__(self, polarization_labels):
+        if isinstance(polarization_labels, str):
+            polarization_labels = (polarization_labels,)
+        self._lbl = tuple(polarization_labels)
+        for ll in self._lbl:
+            my_assert(ll in ["I", "Q", "U", "V", "LL", "LR", "RL", "RR", "XX", "XY", "YX", "YY"])
+        super(PolarizationSpace, self).__init__(len(self._lbl))
+
+    def __repr__(self):
+        return f"PolarizationSpace(polarization_labels={self._lbl})"
+
+    @property
+    def labels(self):
+        return self._lbl
diff --git a/resolve/response.py b/resolve/response.py
index c9d66ee3a5910ed8efb30b39266ddcef7b7052b8..fcb097b37d095eb5b611d7fe30a470a2e5084dfd 100644
--- a/resolve/response.py
+++ b/resolve/response.py
@@ -41,7 +41,7 @@ def StokesIResponse(observation, domain):
         return contr.adjoint @ sr0
     elif npol == 2:
         sr1 = SingleResponse(domain, observation.uvw, observation.freq, mask[1])
-        return ResponseDistributor(sr0, sr1)
+        return ResponseDistributor(observation.polarization.space, sr0, sr1)
     raise RuntimeError
 
 
@@ -186,7 +186,7 @@ class MfResponse(ift.LinearOperator):
 
 
 class ResponseDistributor(ift.LinearOperator):
-    def __init__(self, *ops):
+    def __init__(self, iterating_target_space, *ops):
         dom, tgt = ops[0].domain, ops[0].target
         cap = self.TIMES | self.ADJOINT_TIMES
         for op in ops:
@@ -194,8 +194,10 @@ class ResponseDistributor(ift.LinearOperator):
             my_assert(dom is op.domain)
             my_assert(tgt is op.target)
             my_assert(self.TIMES & op.capability, self.ADJOINT_TIMES & op.capability)
+        my_asserteq(len(iterating_target_space.shape), 1)
+        my_asserteq(len(ops), iterating_target_space.shape[0])
         self._domain = ift.makeDomain(dom)
-        self._target = ift.makeDomain((ift.UnstructuredDomain(len(ops)), *tgt))
+        self._target = ift.makeDomain((iterating_target_space, *tgt))
         self._capability = cap
         self._ops = ops
 
@@ -222,7 +224,7 @@ class FullResponse(ift.LinearOperator):
 
 
 class SingleResponse(ift.LinearOperator):
-    def __init__(self, domain, uvw, freq, mask, facets=(1, 1)):
+    def __init__(self, domain, uvw, freq, mask=None, facets=(1, 1)):
         my_assert_isinstance(facets, tuple)
         for ii in range(2):
             if domain.shape[ii] % facets[ii] != 0:
@@ -234,7 +236,6 @@ class SingleResponse(ift.LinearOperator):
         self._args = {
             "uvw": uvw,
             "freq": freq,
-            "mask": mask.astype(np.uint8),
             "pixsize_x": self._domain[0].distances[0],
             "pixsize_y": self._domain[0].distances[1],
             "epsilon": epsilon(),
@@ -242,6 +243,8 @@ class SingleResponse(ift.LinearOperator):
             "nthreads": nthreads(),
             "flip_v": True,
         }
+        if mask is not None:
+            self._args["mask"] = mask.astype(np.uint8)
         self._vol = self._domain[0].scalar_dvol
         self._target_dtype = np_dtype(True)
         self._domain_dtype = np_dtype(False)
diff --git a/test/test_general.py b/test/test_general.py
index 44e255b9da498142fa55c73bd4b7686d93227bb5..5ec89ebc45b01fdde333752a1f16128cd1dbb3f8 100644
--- a/test/test_general.py
+++ b/test/test_general.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 os.path import join
@@ -150,7 +150,7 @@ def test_calibration_likelihood(time_mode):
     npol, nfreq = obs[0].npol, obs[0].nfreq
     total_N = npol * nants * nfreq
     dom = [
-        ift.UnstructuredDomain(npol),
+        obs[0].polarization.space,
         ift.UnstructuredDomain(len(uants)),
         time_domain,
         ift.UnstructuredDomain(nfreq),
@@ -217,10 +217,8 @@ def test_calibration_distributor(obs):
     tgt = obs.vis.domain
     utimes = rve.unique_times(obs)
     uants = obs.antenna_positions.unique_antennas()
-    dom = [
-        ift.UnstructuredDomain(nn)
-        for nn in [obs.npol, len(uants), len(utimes), obs.nfreq]
-    ]
+    dom = [obs.polarization.space] + \
+        [ift.UnstructuredDomain(nn) for nn in [len(uants), len(utimes), obs.nfreq]]
     uants = rve.unique_antennas(obs)
     time_dct = {aa: ii for ii, aa in enumerate(utimes)}
     antenna_dct = {aa: ii for ii, aa in enumerate(uants)}
@@ -263,7 +261,7 @@ def test_response_distributor():
     dom = ift.UnstructuredDomain(2), ift.UnstructuredDomain(4)
     op0 = ift.makeOp(ift.makeField(dom, np.arange(8).reshape(2, -1)))
     op1 = ift.makeOp(ift.makeField(dom, 2 * np.arange(8).reshape(2, -1)))
-    op = rve.response.ResponseDistributor(op0, op1)
+    op = rve.response.ResponseDistributor(ift.UnstructuredDomain(2), op0, op1)
     ift.extra.check_linear_operator(op)