Repository: RadioAstronomySoftwareGroup/pyuvdata

Issue:
Fix uvflag version string
Currently the version string is added ad hoc. We need to model it off UVData, specifically using the util to check that the history doesn't already contain the string so we don't replicate it when reading multiple files.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=h1) Report
> Merging [#648](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=desc) into [master](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/commit/ed4515162a333706ddcd0803bb80fcaa09f90a03?src=pr&el=desc) will **decrease** coverage by `0.07%`.
> The diff coverage is `97.23%`.

[![Impacted file tree graph](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648/graphs/tree.svg?width=650&token=4QykcCeB1H&height=150&src=pr)](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##           master    #648      +/-   ##
=========================================
- Coverage   98.88%   98.8%   -0.08%     
=========================================
  Files          20      20              
  Lines        8154    8368     +214     
=========================================
+ Hits         8063    8268     +205     
- Misses         91     100       +9
```


| [Impacted Files](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pyuvdata/uvflag.py](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648/diff?src=pr&el=tree#diff-cHl1dmRhdGEvdXZmbGFnLnB5) | `98.72% <97.23%> (-1.28%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=footer). Last update [ed45151...2b46008](https://codecov.io/gh/RadioAstronomySoftwareGroup/pyuvdata/pull/648?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

mkolopanis: Should we use this PR to setup readthedocs to include UVFlag?
mkolopanis: I think this one is ready to have a look. Maybe @bhazelton  or @adampbeardsley are probably the most familiar with uvflag
bhazelton: I had RTD build this branch and there's an error with the imports:
https://readthedocs.org/projects/pyuvdata/builds/9510206/
bhazelton: Do you want to punt the tutorial additions to issue #651?
mkolopanis: oh tutorial. I knew there was something I was forgetting. I wouldn't mind punting on it for now.
bhazelton: Ok, I'll add it to the list in that issue and we can leave it for now.
bhazelton: Hmm.. the hera_qm failures look non-trivial to me (incorrect array shapes). Do you expect them?
mkolopanis: They are both non-trivial and expected. The QM test manually set some arrays with different shapes, then perform operations on them so things like check will fail. There is also one where times are selected out of the object without updating metadata (obviously because this was not possible before).
bhazelton: Ok, that's fine. The hera_qm check isn't required to merge the PR, so as long as you and @adampbeardsley are on board, that's not a show stopper. Looks like the only required test that's failing is the patch test coverage.
mkolopanis: oh changing the defaults in __eq__ changed coverage. Let me add some tests to compensate

Patch:
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index c42bc3a5..a6ecc535 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -64,7 +64,7 @@ http://makeapullrequest.com/ and http://www.firsttimersonly.com/. If you have mo
 2. Do the changes in your fork
 3. If you like the change and think the project could use it:
   - If you're fixing a bug, include a new test that breaks as a result of the bug (if possible)
-  - Ensure that all your new code is covered by tests and that the existing tests pass in both python 2 and 3. Tests can be run and coverage automatically created by running the script `test_coverage.sh` in the `scripts` directory. Coverage reports require the `pytest-cov` plug-in.
+  - Ensure that all your new code is covered by tests and that the existing tests pass in both python 2 and 3. Tests can be run and coverage automatically created by running the script `test_coverage.sh` in the `scripts` directory. Coverage reports require the `pytest-cov` plug-in. Testing of `UVFlag` module requires the `pytest-cases` plug-in (available from pip, may require `setuptools_scm` for python 2 developers).
   - Ensure that your code meets the PEP8 style guidelines. You can check that your code will pass our linting tests by running `pycodestyle . --ignore=E501,W503` in the top-level pyuvdata directory.
   - Ensure that you fully document any new features via docstrings and in the [tutorial](../docs/tutorial.rst)
   - You can see the full pull request checklist [here](PULL_REQUEST_TEMPLATE.md)
diff --git a/.gitignore b/.gitignore
index b696af03..2868ebc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,7 @@ docs/index.rst
 docs/uvdata_parameters.rst
 docs/uvcal_parameters.rst
 docs/uvbeam_parameters.rst
+docs/uvflag_parameters.rst
 
 # PyBuilder
 target/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb4e8568..ba71c363 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
 
 ## [Unreleased]
 
+### Added
+- `UVFlag` information on Read The Docs
+
+### Changed
+- `UVFlag` inherits from `UVBase` object.
+
 ## [1.4.1] - 2019-08-2
 
 ### Added
diff --git a/README.md b/README.md
index 385d6210..88067e14 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,16 @@ The three main goals are:
 3. Provide precise data definition via both human readable code and high quality online documentation
 
 # Package Details
-pyuvdata has three major user classes:
+pyuvdata has four major user classes:
 
 * UVData: supports interferometric data (visibilities) and associated metadata
 * UVCal: supports interferometric calibration solutions (antenna-based) and
 associated metadata (Note that this is a fairly new object, consider it to be a beta version)
 * UVBeam: supports primary beams (E-field or power) and associated metadata
 (Note that this is a new object and is very experimental, consider it to be an alpha version)
+* UVFlag: A class to handle the manipulation and combination of flags for data sets.
+Also can convert raw data quality metrics into flags using thresholding.
+(This object is very new and experimental. Consider it to be a beta version)
 
 ## UVData File standard notes
 * miriad has been throughly tested with aipy-style miriad files and minimally
@@ -60,6 +63,7 @@ metadata, details here: [cst settings file](docs/cst_settings_yaml.rst)
 compatible with other interferometric HDF5 files defined by other groups.
 * UVCal: object and calfits file format (beta version)
 * UVBeam: object and beamfits file format (alpha version)
+* UVFlag: object, initialization, and type changing. (beta version)
 
 ## Known Issues and Planned Improvements
 * UVData: phasing (and the accuracy on the uvw coordinates) is only known to be
@@ -71,6 +75,8 @@ see the [phasing memo](docs/references/phasing.pdf) for more details).
 * UVCal/UVData: method to apply calibration to data.
 * package version detection can cause issues with installation directly from the
 repo for some users (see [issue #590](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/issues/590))
+* UVFlag: cannot convert between "antenna" and "baseline" types. (see [issue #565](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/issues/565))
+* UVFlag: cannot initialize a blank object. (see [issue #652](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/issues/652))
 
 For details see the [issue log](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/issues).
 
@@ -143,7 +149,7 @@ Required:
 
 Optional:
 
-* h5py (for reading and writing uvh5 format)
+* h5py (for reading and writing uvh5 format and uvflag save files)
 * python-casacore (for working with CASA measurement sets)
 * healpy (for working with beams in HEALPix formats)
 * pyyaml (for working with settings files for CST beam files)
@@ -171,15 +177,16 @@ https://github.com/casacore/python-casacore
 Uses the `pytest` package to execute test suite.
 From the source pyuvdata directory run ```pytest``` or ```python -m pytest```.
 
+Testing of `UVFlag` module requires the `pytest-cases` plug-in (available from pip; may require `setuptools_scm` for python 2 developers).
 
 # API
 The primary interface to data from python is via the UVData object. It provides
 import functionality from all supported file formats (UVFITS, Miriad, UVH5, FHD,
 CASA measurement sets) and export to UVFITS, Miriad, and UVH5 formats and can
-be interacted with directly. Similarly, the primary calibration and beam
-interfaces are via the UVCal and UVBeam objects. The attributes of the UVData,
-UVCal and UVBeam objects are described in the uvdata_parameters, uvcal_parameters
-and uvbeam_parameters descriptions at https://pyuvdata.readthedocs.io or
+be interacted with directly. Similarly, the primary calibration, beam, and flag
+interfaces are via the UVCal, UVBeam, and UVflag objects. The attributes of the UVData,
+UVCal, UVBeam, and UVFlag objects are described in the uvdata_parameters, uvcal_parameters,
+ uvbeam_parameters and uvflag_parameters descriptions at https://pyuvdata.readthedocs.io or
 [here](https://github.com/RadioAstronomySoftwareGroup/pyuvdata/blob/master/docs).
 
 
diff --git a/ci/rtd_env.yml b/ci/rtd_env.yml
index 7112fce3..8bcb1738 100644
--- a/ci/rtd_env.yml
+++ b/ci/rtd_env.yml
@@ -5,4 +5,5 @@ dependencies:
   - numpy
   - scipy
   - astropy
+  - h5py
   - conda-forge::pypandoc
diff --git a/docs/conf.py b/docs/conf.py
index 6558843b..0c8c7a2f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -35,6 +35,7 @@ index_file = os.path.join(os.path.abspath('../docs'), 'index.rst')
 dataparams_file = os.path.join(os.path.abspath('../docs'), 'uvdata_parameters.rst')
 calparams_file = os.path.join(os.path.abspath('../docs'), 'uvcal_parameters.rst')
 beamparams_file = os.path.join(os.path.abspath('../docs'), 'uvbeam_parameters.rst')
+flagparams_file = os.path.join(os.path.abspath('../docs'), 'uvflag_parameters.rst')
 
 # -- General configuration ------------------------------------------------
 
@@ -333,10 +334,12 @@ def build_custom_docs(app):
     import make_parameters
     import make_cal_parameters
     import make_beam_parameters
+    import make_flag_parameters
     make_index.write_index_rst(readme_file=readme_file, write_file=index_file)
     make_parameters.write_dataparams_rst(write_file=dataparams_file)
     make_cal_parameters.write_calparams_rst(write_file=calparams_file)
     make_beam_parameters.write_beamparams_rst(write_file=beamparams_file)
+    make_flag_parameters.write_flagparams_rst(write_file=flagparams_file)
 
 
 # this is to enable running python in the rst files.
diff --git a/docs/make_flag_parameters.py b/docs/make_flag_parameters.py
new file mode 100644
index 00000000..d80c3d9f
--- /dev/null
+++ b/docs/make_flag_parameters.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+"""Format the UVData object parameters into a sphinx rst file."""
+from __future__ import absolute_import, division, print_function
+
+import os
+import inspect
+from pyuvdata import UVFlag
+from pyuvdata.data import DATA_PATH
+from astropy.time import Time
+
+
+def write_flagparams_rst(write_file=None):
+    test_file = os.path.join(DATA_PATH, 'zen.2457698.40355.xx.HH.uvcAA.testuvflag.h5')
+    UV = UVFlag(test_file)
+    out = 'UVFLag Parameters\n=================\n'
+    out += ("These are the standard attributes of UVFlag objects.\n\nUnder the hood "
+            "they are actually properties based on UVParameter objects.\n\n")
+    out += 'Required\n----------------\n'
+    out += ('These parameters are required to have a sensible UVFlag object and \n'
+            'are required for most kinds of uv data files.')
+    out += "\n\n"
+    for thing in UV.required():
+        obj = getattr(UV, thing)
+        out += '**{name}**\n'.format(name=obj.name)
+        out += '     {desc}\n'.format(desc=obj.description)
+        out += "\n"
+
+    out += 'Optional\n----------------\n'
+    out += ('These parameters are defined by one or more  type but are not '
+            'always required.\nSome of them are required depending on the '
+            'type (as noted below).')
+    out += "\n\n"
+    for thing in UV.extra():
+        obj = getattr(UV, thing)
+        out += '**{name}**\n'.format(name=obj.name)
+        out += '     {desc}\n'.format(desc=obj.description)
+        out += "\n"
+    t = Time.now()
+    t.out_subfmt = 'date'
+    out += "last updated: {date}".format(date=t.iso)
+    if write_file is None:
+        write_path = os.path.dirname(os.path.abspath(inspect.stack()[0][1]))
+        write_file = os.path.join(write_path, 'uvflag_parameters.rst')
+    F = open(write_file, 'w')
+    F.write(out)
+    print("wrote " + write_file)
diff --git a/docs/make_index.py b/docs/make_index.py
index 5f73aa15..950f045b 100644
--- a/docs/make_index.py
+++ b/docs/make_index.py
@@ -76,6 +76,8 @@ def write_index_rst(readme_file=None, write_file=None):
             '   uvcal\n'
             '   uvbeam_parameters\n'
             '   uvbeam\n'
+            '   uvflag_parameters\n'
+            '   uvflag\n'
             '   cst_settings_yaml\n'
             '   utility_functions\n'
             '   known_telescopes\n'
diff --git a/docs/uvflag.rst b/docs/uvflag.rst
new file mode 100644
index 00000000..1574312e
--- /dev/null
+++ b/docs/uvflag.rst
@@ -0,0 +1,12 @@
+UVFlag Class
+=============
+
+UVFlag is a *beta* level code being actively developed.
+
+UVFlag is the main user class for combining flags, and turning data quality metrics into flags.
+It provides import and export functionality to all supported file formats (hdf5)
+and can be interacted with directly.
+
+
+.. autoclass:: pyuvdata.UVFlag
+  :members:
diff --git a/pyuvdata/parameter.py b/pyuvdata/parameter.py
index a64c7a6f..61cfb008 100644
--- a/pyuvdata/parameter.py
+++ b/pyuvdata/parameter.py
@@ -106,7 +106,7 @@ class UVParameter(object):
                 if isinstance(self.value, six.string_types):
                     str_type = True
                 if isinstance(self.value, (list, np.ndarray)):
-                    if isinstance(self.value[0], str):
+                    if isinstance(self.value[0], six.string_types):
                         str_type = True
 
                 if not str_type:
diff --git a/pyuvdata/uvflag.py b/pyuvdata/uvflag.py
index 38b523d4..e183352b 100644
--- a/pyuvdata/uvflag.py
+++ b/pyuvdata/uvflag.py
@@ -5,58 +5,230 @@
 from __future__ import print_function, division, absolute_import
 import numpy as np
 import os
+import warnings
+import copy
+import six
+from six.moves import map
+
+from .uvbase import UVBase
+from . import parameter as uvp
 from . import UVData
 from . import UVCal
 from . import utils as uvutils
-from . import version as uvversion
 from . import telescopes as uvtel
-import warnings
-import copy
-from six.moves import map
 
 
-class UVFlag(object):
-    """ Object to handle flag arrays and waterfalls. Supports reading/writing,
-    and stores all relevant information to combine flags and apply to data.
+class UVFlag(UVBase):
+    """Object to handle flag arrays and waterfalls for interferometric datasets.
+
+    Supports reading/writing, and stores all relevant information to combine flags and apply to data.
+    Initialization of the UVFlag object requires some parameters. Metadata is copied from input object. If input
+    is subclass of UVData or UVCal, the weights_array will be set to all ones.
+    Input lists or tuples are iterated through, treating each entry with an
+    individual UVFlag init.
+
+    Parameters
+    ----------
+    input : UVData, UVCal, str, list of compatible combination of options
+        Input to initialize UVFlag object. If str, assumed to be path to previously
+        saved UVFlag object. UVData and UVCal objects cannot be directly combined,
+        unless waterfall is True.
+    mode : {"metric", "flag"}, optional
+        The mode determines whether the object has a floating point metric_array
+        or a boolean flag_array.
+    copy_flags : bool, optional
+        Whether to copy flags from input to new UVFlag object
+    waterfall : bool, optional
+        Whether to immediately initialize as a waterfall object, with flag/metric
+        axes: time, frequency, polarization.
+    history : str, optional
+        History string to attach to object.
+    label: str, optional
+        String used for labeling the object (e.g. 'FM').
+
+    Attributes
+    -----------
+     UVParameter objects :
+         For full list see the UVFlag Parameters Documentation. (https://pyuvdata.readthedocs.io/en/latest/uvflag_parameters.html)
+         Some are always required, some are required for certain phase_types
+         and others are always optional.
+
+
     """
 
     def __init__(self, input, mode='metric', copy_flags=False, waterfall=False, history='',
                  label=''):
-        """Initialize UVFlag object. Metadata is copied from input object. If input
-        is subclass of UVData or UVCal, the weights_array will be set to all ones.
-        Input lists or tuples are iterated through, treating each entry with an
-        individual UVFlag init.
-
-        Parameters
-        ----------
-        input : UVData or UVCal or str or list of compatible combination of options
-            Input to initialize UVFlag object. If str, assumed to be path to previously
-            saved UVFlag object. UVData and UVCal objects cannot be directly combined,
-            unless waterfall is True.
-        mode : {"metric", "flag"}, optional
-            The mode determines whether the object has a floating point metric_array
-            or a boolean flag_array. Default is "metric".
-        copy_flags : bool, optional
-            Whether to copy flags from input to new UVFlag object. Default is False.
-        waterfall : bool, optional
-            Whether to immediately initialize as a waterfall object, with flag/metric
-            axes: time, frequency, polarization. Default is False.
-        history : str, optional
-            History string to attach to object. Default is empty string.
-        label: str, optional
-            String used for labeling the object (e.g. 'FM'). Default is empty string.
-        """
+        """Initialize the object."""
+        # standard angle tolerance: 10 mas in radians.
+        # Should perhaps be decreased to 1 mas in the future
+        radian_tol = 10 * 2 * np.pi * 1e-3 / (60.0 * 60.0 * 360.0)
+
+        desc = ('The mode determines whether the object has a '
+                'floating point metric_array or a boolean flag_array. '
+                'Options: {"metric", "flag"}. Default is "metric".')
+        self._mode = uvp.UVParameter('mode',
+                                     description=desc,
+                                     form='str', expected_type=str,
+                                     acceptable_vals=["metric", "flag"])
+
+        desc = ("String used for labeling the object (e.g. 'FM'). "
+                "Default is empty string.")
+        self._label = uvp.UVParameter('label', description=desc,
+                                      form='str', expected_type=str)
+
+        desc = ('The type of object defines the form of some arrays '
+                ' and also how metrics/flags are combined. '
+                'Accepted types:"waterfall", "baseline", "antenna"')
+        self._type = uvp.UVParameter('type', description=desc,
+                                     form='str', expected_type=str,
+                                     acceptable_vals=["antenna",
+                                                      "baseline",
+                                                      "waterfall"]
+                                     )
+
+        self._Ntimes = uvp.UVParameter('Ntimes', description='Number of times',
+                                       expected_type=int)
+        desc = ('Number of baselines. '
+                'Only Required for "baseline" type objects.')
+        self._Nbls = uvp.UVParameter('Nbls', description=desc,
+                                     expected_type=int, required=False)
+        self._Nblts = uvp.UVParameter('Nblts', description='Number of baseline-times '
+                                      '(i.e. number of spectra). Not necessarily '
+                                      'equal to Nbls * Ntimes', expected_type=int)
+        self._Nspws = uvp.UVParameter('Nspws', description='Number of spectral windows '
+                                      '(ie non-contiguous spectral chunks). '
+                                      'More than one spectral window is not '
+                                      'currently supported.', expected_type=int,
+                                      required=False)
+        self._Nfreqs = uvp.UVParameter('Nfreqs', description='Number of frequency channels',
+                                       expected_type=int)
+        self._Npols = uvp.UVParameter('Npols', description='Number of polarizations',
+                                      expected_type=int)
+
+        desc = ('Floating point metric information, only availble in metric mode. '
+                'shape (Nblts, Nspws, Nfreq, Npols).')
+        self._metric_array = uvp.UVParameter('metric_array', description=desc,
+                                             form=('Nblts', 'Nspws',
+                                                   'Nfreqs', 'Npols'),
+                                             expected_type=np.float,
+                                             required=False)
+
+        desc = ('Boolean flag, True is flagged, only availble in flag mode. '
+                'shape (Nblts, Nspws, Nfreq, Npols).')
+        self._flag_array = uvp.UVParameter('flag_array', description=desc,
+                                           form=('Nblts', 'Nspws',
+                                                 'Nfreqs', 'Npols'),
+                                           expected_type=np.bool,
+                                           required=False)
+
+        desc = 'Floating point weight information, shape (Nblts, Nspws, Nfreq, Npols).'
+        self._weights_array = uvp.UVParameter('weights_array', description=desc,
+                                              form=('Nblts', 'Nspws',
+                                                    'Nfreqs', 'Npols'),
+                                              expected_type=np.float)
+
+        desc = ('Array of times, center of integration, shape (Nblts), '
+                'units Julian Date')
+        self._time_array = uvp.UVParameter('time_array', description=desc,
+                                           form=('Nblts',),
+                                           expected_type=np.float,
+                                           tols=1e-3 / (60.0 * 60.0 * 24.0))  # 1 ms in days
+
+        desc = ('Array of lsts, center of integration, shape (Nblts), '
+                'units radians')
+        self._lst_array = uvp.UVParameter('lst_array', description=desc,
+                                          form=('Nblts',),
+                                          expected_type=np.float,
+                                          tols=radian_tol)
+
+        desc = ('Array of first antenna indices, shape (Nblts). '
+                'Only available for "baseline" type objects. '
+                'type = int, 0 indexed')
+        self._ant_1_array = uvp.UVParameter('ant_1_array', description=desc,
+                                            expected_type=int, form=('Nblts',))
+        desc = ('Array of second antenna indices, shape (Nblts). '
+                'Only available for "baseline" type objects. '
+                'type = int, 0 indexed')
+        self._ant_2_array = uvp.UVParameter('ant_2_array', description=desc,
+                                            expected_type=int, form=('Nblts',))
+
+        desc = ('Array of antenna numbers, shape (Nants_data), '
+                'Only available for "antenna" type objects. '
+                'type = int, 0 indexed')
+        self._ant_array = uvp.UVParameter('ant_array', description=desc,
+                                          expected_type=int,
+                                          form=('Nants_data',))
+
+        desc = ('Array of baseline indices, shape (Nblts). '
+                'Only available for "baseline" type objects. '
+                'type = int; baseline = 2048 * (ant1+1) + (ant2+1) + 2^16')
+        self._baseline_array = uvp.UVParameter('baseline_array',
+                                               description=desc,
+                                               expected_type=int,
+                                               form=('Nblts',))
+
+        desc = ('Array of frequencies, center of the channel, '
+                'shape (Nspws, Nfreqs), units Hz')
+        self._freq_array = uvp.UVParameter('freq_array', description=desc,
+                                           form=('Nspws', 'Nfreqs'),
+                                           expected_type=np.float,
+                                           tols=1e-3)  # mHz
+
+        desc = ('Array of polarization integers, shape (Npols). '
+                'AIPS Memo 117 says: pseudo-stokes 1:4 (pI, pQ, pU, pV);  '
+                'circular -1:-4 (RR, LL, RL, LR); linear -5:-8 (XX, YY, XY, YX). '
+                'NOTE: AIPS Memo 117 actually calls the pseudo-Stokes polarizations '
+                '"Stokes", but this is inaccurate as visibilities cannot be in '
+                'true Stokes polarizations for physical antennas. We adopt the '
+                'term pseudo-Stokes to refer to linear combinations of instrumental '
+                'visibility polarizations (e.g. pI = xx + yy).')
+        self._polarization_array = uvp.UVParameter('polarization_array',
+                                                   description=desc,
+                                                   expected_type=int,
+                                                   acceptable_vals=list(
+                                                       np.arange(-8, 0)) + list(np.arange(1, 5)),
+                                                   form=('Npols',))
+
+        self._history = uvp.UVParameter('history', description='String of history, units English',
+                                        form='str', expected_type=str)
+
+        # ---antenna information ---
+        desc = ('Number of antennas in the array. '
+                'Only available for "baseline" type objects. '
+                'May be larger than the number of antennas with data.')
+        self._Nants_telescope = uvp.UVParameter('Nants_telescope',
+                                                description=desc,
+                                                expected_type=int,
+                                                required=False)
+        desc = ('Number of antennas with data present. '
+                'Only available for "baseline" or "antenna" type objects.'
+                'May be smaller than the number of antennas in the array')
+        self._Nants_data = uvp.UVParameter('Nants_data',
+                                           description=desc,
+                                           expected_type=int,
+                                           required=True)
+        #  --extra information ---
+        desc = ('Orientation of the physical dipole corresponding to what is '
+                'labelled as the x polarization. Options are "east" '
+                '(indicating east/west orientation) and "north" (indicating '
+                'north/south orientation)')
+        self._x_orientation = uvp.UVParameter('x_orientation', description=desc,
+                                              required=False, expected_type=str,
+                                              acceptable_vals=['east', 'north'])
+
+        # initialize the underlying UVBase properties
+        super(UVFlag, self).__init__()
+
+        if mode.lower() == "metric":
+            self._set_mode_metric()
+        elif mode.lower() == "flag":
+            self._set_mode_flag()
+        else:
+            raise ValueError("Input mode must be within acceptable values: "
+                             "{}".format((', ').join(self._mode.acceptable_vals)))
 
-        self.mode = mode.lower()  # Gets overwritten if reading file
         self.history = ''  # Added to at the end
-        # String to add to history of any files written with this version of pyuvdata
-        self.pyuvdata_version_str = uvversion.version + '.'
-        if uvversion.git_hash is not '':
-            self.pyuvdata_version_str += ('  Git origin: ' + uvversion.git_origin
-                                          + '.  Git hash: ' + uvversion.git_hash
-                                          + '.  Git branch: ' + uvversion.git_branch
-                                          + '.  Git description: '
-                                          + uvversion.git_description + '.')
+
         self.label = ''  # Added to at the end
         if isinstance(input, (list, tuple)):
             self.__init__(input[0], mode=mode, copy_flags=copy_flags,
@@ -72,16 +244,24 @@ class UVFlag(object):
             # Given a path, read input
             self.read(input, history)
         elif waterfall and issubclass(input.__class__, (UVData, UVCal)):
-            self.type = 'waterfall'
-            self.history += ('Flag object with type "waterfall" created by '
-                             + self.pyuvdata_version_str)
+            self._set_type_waterfall()
+            self.history += ('Flag object with type "waterfall" created. ')
+            if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+                self.history += self.pyuvdata_version_str
+
             self.time_array, ri = np.unique(input.time_array, return_index=True)
+            self.Ntimes = len(self.time_array)
             self.freq_array = input.freq_array[0, :]
+            self.Nspws = None
+            self.Nfreqs = len(self.freq_array)
+            self.Nblts = len(self.time_array)
             if issubclass(input.__class__, UVData):
                 self.polarization_array = input.polarization_array
+                self.Npols = len(self.polarization_array)
                 self.lst_array = input.lst_array[ri]
             else:
                 self.polarization_array = input.jones_array
+                self.Npols = len(self.polarization_array)
                 self.lst_array = lst_from_uv(input)[ri]
             if copy_flags:
                 raise NotImplementedError('Cannot copy flags when initializing '
@@ -97,23 +277,35 @@ class UVFlag(object):
                                                   len(self.polarization_array)))
 
         elif issubclass(input.__class__, UVData):
-            self.type = 'baseline'
-            self.history += ('Flag object with type "baseline" created by '
-                             + self.pyuvdata_version_str)
+            self._set_type_baseline()
+            self.history += ('Flag object with type "baseline" created. ')
+            if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+                self.history += self.pyuvdata_version_str
+
             self.baseline_array = input.baseline_array
+            self.Nbls = np.unique(self.baseline_array).size
+            self.Nblts = len(self.baseline_array)
             self.ant_1_array = input.ant_1_array
             self.ant_2_array = input.ant_2_array
+            self.Nants_data = input.Nants_data
+
             self.time_array = input.time_array
             self.lst_array = input.lst_array
+            self.Ntimes = np.unique(self.time_array).size
+
             self.freq_array = input.freq_array
+            self.Nfreqs = np.unique(self.freq_array).size
+            self.Nspws = input.Nspws
+
             self.polarization_array = input.polarization_array
+            self.Npols = len(self.polarization_array)
             self.Nants_telescope = input.Nants_telescope
             if copy_flags:
                 self.flag_array = input.flag_array
                 self.history += ' Flags copied from ' + str(input.__class__) + ' object.'
                 if self.mode == 'metric':
                     warnings.warn('Copying flags to type=="baseline" results in mode=="flag".')
-                    self.mode = 'flag'
+                    self._set_mode_flag()
             else:
                 if self.mode == 'flag':
                     self.flag_array = np.zeros_like(input.flag_array)
@@ -121,20 +313,30 @@ class UVFlag(object):
                     self.metric_array = np.zeros_like(input.flag_array).astype(np.float)
 
         elif issubclass(input.__class__, UVCal):
-            self.type = 'antenna'
-            self.history += ('Flag object with type "antenna" created by '
-                             + self.pyuvdata_version_str)
+            self._set_type_antenna()
+            self.history += ('Flag object with type "antenna" created. ')
+            if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+                self.history += self.pyuvdata_version_str
             self.ant_array = input.ant_array
+            self.Nants_data = len(self.ant_array)
+
             self.time_array = input.time_array
             self.lst_array = lst_from_uv(input)
+            self.Ntimes = np.unique(self.time_array).size
+            self.Nblts = self.Ntimes
+
             self.freq_array = input.freq_array
+            self.Nspws = input.Nspws
+            self.Nfreqs = np.unique(self.freq_array).size
+
             self.polarization_array = input.jones_array
+            self.Npols = len(self.polarization_array)
             if copy_flags:
                 self.flag_array = input.flag_array
                 self.history += ' Flags copied from ' + str(input.__class__) + ' object.'
                 if self.mode == 'metric':
                     warnings.warn('Copying flags to type=="antenna" results in mode=="flag".')
-                    self.mode = 'flag'
+                    self._set_mode_flag()
             else:
                 if self.mode == 'flag':
                     self.flag_array = np.zeros_like(input.flag_array)
@@ -156,45 +358,688 @@ class UVFlag(object):
 
         self.clear_unused_attributes()
 
-    def __eq__(self, other, check_history=False):
-        """ Function to check equality of two UVFlag objects
-        Args:
-            other: UVFlag object to check against
-        """
-        if not isinstance(other, self.__class__):
-            return False
-        if (self.type != other.type) or (self.mode != other.mode) or (self.label != other.label):
-            return False
+    @property
+    def _data_params(self):
+        """List of strings giving the data-like parameters."""
+        if not hasattr(self, "mode") or self.mode is None:
+            return None
+        elif self.mode == "flag":
+            return ['flag_array', 'weights_array']
+        elif self.mode == "metric":
+            return ['metric_array', 'weights_array']
+        else:
+            raise ValueError("Invalid mode. Mode must be one of "
+                             + ", ".join(["{}"]
+                                         * len(self._mode.acceptable_vals)
+                                         ).format(*self._mode.acceptable_vals))
+
+    @property
+    def data_like_parameters(self):
+        """Return iterator of defined parameters which are data-like (not metadata-like)."""
+        for key in self._data_params:
+            if hasattr(self, key):
+                yield getattr(self, key)
+
+    def _set_mode_flag(self):
+        """Set the mode and required parameters consistent with a flag object."""
+        self.mode = 'flag'
+        self._flag_array.required = True
+        self._metric_array.required = False
+        return
+
+    def _set_mode_metric(self):
+        """Set the mode and required parameters consistent with a metric object."""
+
+        self.mode = 'metric'
+        self._flag_array.required = False
+        self._metric_array.required = True
+        return
+
+    def _set_type_antenna(self):
+        """Set the type and required propertis consistent with an antenna object."""
+        self.type = 'antenna'
+        self._ant_array.required = True
+        self._baseline_array.required = False
+        self._ant_1_array.required = False
+        self._ant_2_array.required = False
+        self._Nants_telescope.required = False
+        self._Nants_data.required = True
+        self._Nbls.required = False
+        self._Nspws.required = True
+
+        self.Nblts = self.Ntimes
+        desc = ('Floating point metric information, '
+                'has shape (Nants_data, Nspws, Nfreqs, Ntimes, Npols).')
+        self._metric_array.desc = desc
+        self._metric_array.form = ('Nants_data', 'Nspws', 'Nfreqs', 'Ntimes', 'Npols')
+
+        desc = ('Boolean flag, True is flagged, '
+                'has shape (Nants_data, Nspws, Nfreqs, Ntimes, Npols).')
+        self._flag_array.desc = desc
+        self._flag_array.form = ('Nants_data', 'Nspws', 'Nfreqs', 'Ntimes', 'Npols')
+
+        desc = ('Floating point weight information, '
+                'has shape (Nants_data, Nspws, Nfreqs, Ntimes, Npols).')
+        self._weights_array.desc = desc
+        self._weights_array.form = ('Nants_data', 'Nspws', 'Nfreqs', 'Ntimes', 'Npols')
+
+        desc = ('Array of unique times, center of integration, shape (Ntimes), '
+                'units Julian Date')
+        self._time_array.form = ('Nblts',)
+
+        desc = ('Array of unique lsts, center of integration, shape (Ntimes), '
+                'units radians')
+        self._lst_array.form = ('Nblts',)
+
+        desc = ('Array of frequencies, center of the channel, '
+                'shape (Nspws, Nfreqs), units Hz')
+        self._freq_array.form = ('Nspws', 'Nfreqs')
+
+    def _set_type_baseline(self):
+        """Set the type and required propertis consistent with a baseline object."""
+        self.type = 'baseline'
+        self._ant_array.required = False
+        self._baseline_array.required = True
+        self._ant_1_array.required = True
+        self._ant_2_array.required = True
+        self._Nants_telescope.required = True
+        self._Nants_data.required = True
+        self._Nbls.required = True
+        self._Nspws.required = True
+
+        if self.time_array is not None:
+            self.Nblts = len(self.time_array)
+
+        desc = 'Floating point metric information, shape (Nblts, Nspws, Nfreqs, Npols).'
+        self._metric_array.desc = desc
+        self._metric_array.form = ('Nblts', 'Nspws', 'Nfreqs', 'Npols')
+
+        desc = 'Boolean flag, True is flagged, shape (Nblts, Nfreqs, Npols)'
+        self._flag_array.desc = desc
+        self._flag_array.form = ('Nblts', 'Nspws', 'Nfreqs', 'Npols')
+
+        desc = 'Floating point weight information, has shape (Nblts, Nfreqs, Npols).'
+        self._weights_array.desc = desc
+        self._weights_array.form = ('Nblts', 'Nspws', 'Nfreqs', 'Npols')
+
+        desc = ('Array of unique times, center of integration, shape (Ntimes), '
+                'units Julian Date')
+        self._time_array.form = ('Nblts',)
+
+        desc = ('Array of unique lsts, center of integration, shape (Ntimes), '
+                'units radians')
+        self._lst_array.form = ('Nblts',)
+
+        desc = ('Array of frequencies, center of the channel, '
+                'shape (Nspws, Nfreqs), units Hz')
+        self._freq_array.form = ('Nspws', 'Nfreqs')
+
+    def _set_type_waterfall(self):
+        """Set the type and required propertis consistent with a waterfall object."""
+        self.type = 'waterfall'
+        self._ant_array.required = False
+        self._baseline_array.required = False
+        self._ant_1_array.required = False
+        self._ant_2_array.required = False
+        self._Nants_telescope.required = False
+        self._Nants_data.required = False
+        self._Nbls.required = False
+        self._Nspws.required = False
+        self._weights_array.required = True
 
-        array_list = ['weights_array', 'time_array', 'lst_array', 'freq_array',
-                      'polarization_array']
-        if self.type == 'antenna':
-            array_list += ['ant_array']
-        elif self.type == 'baseline':
-            array_list += ['baseline_array', 'ant_1_array', 'ant_2_array', 'Nants_telescope']
-        if self.mode == 'flag':
-            array_list += ['flag_array']
-        elif self.mode == 'metric':
-            array_list += ['metric_array']
-        for arr in array_list:
-            self_param = getattr(self, arr)
-            other_param = getattr(other, arr)
-            if not np.all(self_param == other_param):
-                return False
+        self.Nblts = self.Ntimes
+
+        desc = 'Floating point metric information, shape (Nblts, Nfreqs, Npols).'
+        self._metric_array.desc = desc
+        self._metric_array.form = ('Nblts', 'Nfreqs', 'Npols')
+
+        desc = 'Boolean flag, True is flagged, shape (Nblts, Nfreqs, Npols)'
+        self._flag_array.desc = desc
+        self._flag_array.form = ('Nblts', 'Nfreqs', 'Npols')
+
+        desc = 'Floating point weight information, has shape (Nblts, Nfreqs, Npols).'
+        self._weights_array.desc = desc
+        self._weights_array.form = ('Nblts', 'Nfreqs', 'Npols')
+
+        desc = ('Array of unique times, center of integration, shape (Ntimes), '
+                'units Julian Date')
+        self._time_array.form = ('Ntimes',)
+
+        desc = ('Array of unique lsts, center of integration, shape (Ntimes), '
+                'units radians')
+        self._lst_array.form = ('Ntimes',)
+
+        desc = ('Array of frequencies, center of the channel, '
+                'shape (Nfreqs), units Hz')
+        self._freq_array.form = ('Nfreqs',)
 
+    def __eq__(self, other, check_history=True, check_extra=True):
+        """Check Equality of two UVFlag objects.
+
+        Parameters
+        ----------
+        other: UVFlag
+            object to check against
+        check_history : bool
+            Include the history keyword when comparing UVFlag objects.
+        check_extra : bool
+            Include non-required parameters when comparing UVFlag objects.
+
+        """
         if check_history:
-            if self.history != other.history:
+            return super(UVFlag, self).__eq__(other, check_extra=check_extra)
+
+        else:
+            # initial check that the classes are the same
+            # then strip the histories
+            if isinstance(other, self.__class__):
+                _h1 = self.history
+                self.history = None
+
+                _h2 = other.history
+                other.history = None
+
+                truth = super(UVFlag, self).__eq__(other, check_extra=check_extra)
+
+                self.history = _h1
+                other.history = _h2
+
+                return truth
+            else:
+                print('Classes do not match')
                 return False
 
-        return True
+    def __ne__(self, other, check_history=True, check_extra=True):
+        """Not Equal."""
+        return not self.__eq__(other, check_history=check_history,
+                               check_extra=check_extra)
+
+    def _select_preprocess(self, antenna_nums, bls,
+                           frequencies, freq_chans, times, polarizations,
+                           blt_inds, ant_inds):
+        """Build up blt_inds, freq_inds, pol_inds and history_update_string for select.
+
+        Parameters
+        ----------
+        antenna_nums : array_like of int, optional
+            The antennas numbers to keep in the object (antenna positions and
+            names for the removed antennas will be retained unless
+            `keep_all_metadata` is False). This cannot be provided if
+            `antenna_names` is also provided.
+        bls : list of tuple, optional
+            A list of antenna number tuples (e.g. [(0,1), (3,2)]) or a list of
+            baseline 3-tuples (e.g. [(0,1,'xx'), (2,3,'yy')]) specifying baselines
+            to keep in the object. For length-2 tuples, the ordering of the numbers
+            within the tuple does not matter. For length-3 tuples, the polarization
+            string is in the order of the two antennas. If length-3 tuples are
+            provided, `polarizations` must be None.
+        frequencies : array_like of float, optional
+            The frequencies to keep in the object, each value passed here should
+            exist in the freq_array.
+        freq_chans : array_like of int, optional
+            The frequency channel numbers to keep in the object.
+        times : array_like of float, optional
+            The times to keep in the object, each value passed here should
+            exist in the time_array.
+        polarizations : array_like of int, optional
+            The polarizations numbers to keep in the object, each value passed
+            here should exist in the polarization_array.
+        blt_inds : array_like of int, optional
+            The baseline-time indices to keep in the object. This is
+            not commonly used.
+        blt_inds : array_like of int, optional
+            The antenna indices to keep in the object. This is
+            not commonly used.
+
+        Returns
+        -------
+        blt_inds : list of int
+            list of baseline-time indices to keep. Can be None (to keep everything).
+        ant_inds : list of int
+            list of antenna number indices to keep. Can be None (keep all; only valid for "antenna" mode).
+        freq_inds : list of int
+            list of frequency indices to keep. Can be None (to keep everything).
+        pol_inds : list of int
+            list of polarization indices to keep. Can be None (to keep everything).
+        history_update_string : str
+            string to append to the end of the history.
 
-    def read(self, filename, history=''):
         """
-        Read in flag/metric data from a HDF5 file.
+        # build up history string as we go
+        history_update_string = '  Downselected to specific '
+        n_selects = 0
+
+        if self.type == "waterfall":
+            if antenna_nums is not None:
+                raise ValueError('Cannot select on antenna_nums with waterfall type '
+                                 'UVFLag objects.')
+            if bls is not None:
+                raise ValueError('Cannot select on bls with waterfall type '
+                                 'UVFlag objects.')
+
+        # Antennas, times and blt_inds all need to be combined into a set of
+        # blts indices to keep.
+
+        # test for blt_inds presence before adding inds from antennas & times
+        if blt_inds is not None:
+            blt_inds = uvutils._get_iterable(blt_inds)
+            if np.array(blt_inds).ndim > 1:
+                blt_inds = np.array(blt_inds).flatten()
+            history_update_string += 'baseline-times'
+            n_selects += 1
+
+        if antenna_nums is not None:
+            antenna_nums = uvutils._get_iterable(antenna_nums)
+            if np.array(antenna_nums).ndim > 1:
+                antenna_nums = np.array(antenna_nums).flatten()
+            if n_selects > 0:
+                history_update_string += ', antennas'
+            else:
+                history_update_string += 'antennas'
+            n_selects += 1
+
+            if self.type == "baseline":
+                inds1 = np.zeros(0, dtype=np.int)
+                inds2 = np.zeros(0, dtype=np.int)
+                for ant in antenna_nums:
+                    if ant in self.ant_1_array or ant in self.ant_2_array:
+                        wh1 = np.where(self.ant_1_array == ant)[0]
+                        wh2 = np.where(self.ant_2_array == ant)[0]
+                        if len(wh1) > 0:
+                            inds1 = np.append(inds1, list(wh1))
+                        if len(wh2) > 0:
+                            inds2 = np.append(inds2, list(wh2))
+                    else:
+                        raise ValueError('Antenna number {a} is not present in the '
+                                         'ant_1_array or ant_2_array'.format(a=ant))
+                ant_blt_inds = np.array(list(set(inds1).intersection(inds2)),
+                                        dtype=np.int)
+            if self.type == "antenna":
+                ant_blt_inds = None
+                ant_inds = np.zeros(0, dtype=np.int)
+                for ant in antenna_nums:
+                    if ant in self.ant_array:
+                        wh = np.nonzero(self.ant_array == ant)[0]
+                        if len(wh) > 0:
+                            ant_inds = np.append(ant_inds, list(wh))
+                    else:
+                        raise ValueError('Antenna number {a} is not present in the '
+                                         'ant_array'.format(a=ant))
+
+        else:
+            ant_blt_inds = None
+
+        if bls is not None:
+            if self.type != "baseline":
+                raise ValueError('Only "baseline" mode UVFlag objects may select'
+                                 ' along the baseline axis')
+            if isinstance(bls, tuple) and (len(bls) == 2 or len(bls) == 3):
+                bls = [bls]
+            if not all(isinstance(item, tuple) for item in bls):
+                raise ValueError(
+                    'bls must be a list of tuples of antenna numbers (optionally with polarization).')
+            if not all([isinstance(item[0], six.integer_types + (np.integer,)) for item in bls]
+                       + [isinstance(item[1], six.integer_types + (np.integer,)) for item in bls]):
+                raise ValueError(
+                    'bls must be a list of tuples of integer antenna numbers (optionally with polarization).')
+            if all([len(item) == 3 for item in bls]):
+                if polarizations is not None:
+                    raise ValueError('Cannot provide length-3 tuples and also specify polarizations.')
+                if not all([isinstance(item[2], str) for item in bls]):
+                    raise ValueError('The third element in each bl must be a polarization string')
+
+            if n_selects > 0:
+                history_update_string += ', baselines'
+            else:
+                history_update_string += 'baselines'
+
+            n_selects += 1
+            bls_blt_inds = np.zeros(0, dtype=np.int)
+            bl_pols = set()
+            for bl in bls:
+                if not (bl[0] in self.ant_1_array or bl[0] in self.ant_2_array):
+                    raise ValueError('Antenna number {a} is not present in the '
+                                     'ant_1_array or ant_2_array'.format(a=bl[0]))
+                if not (bl[1] in self.ant_1_array or bl[1] in self.ant_2_array):
+                    raise ValueError('Antenna number {a} is not present in the '
+                                     'ant_1_array or ant_2_array'.format(a=bl[1]))
+                wh1 = np.where(np.logical_and(
+                    self.ant_1_array == bl[0], self.ant_2_array == bl[1]))[0]
+                wh2 = np.where(np.logical_and(
+                    self.ant_1_array == bl[1], self.ant_2_array == bl[0]))[0]
+                if len(wh1) > 0:
+                    bls_blt_inds = np.append(bls_blt_inds, list(wh1))
+                    if len(bl) == 3:
+                        bl_pols.add(bl[2])
+                elif len(wh2) > 0:
+                    bls_blt_inds = np.append(bls_blt_inds, list(wh2))
+                    if len(bl) == 3:
+                        bl_pols.add(bl[2][::-1])  # reverse polarization string
+                else:
+                    raise ValueError('Antenna pair {p} does not have any data '
+                                     'associated with it.'.format(p=bl))
+            if len(bl_pols) > 0:
+                polarizations = list(bl_pols)
+
+            if ant_blt_inds is not None:
+                # Use intersection (and) to join antenna_names/nums & ant_pairs_nums
+                ant_blt_inds = np.array(list(set(ant_blt_inds).intersection(bls_blt_inds)))
+            else:
+                ant_blt_inds = bls_blt_inds
+
+        if ant_blt_inds is not None:
+            if blt_inds is not None:
+                # Use intersection (and) to join antenna_names/nums/ant_pairs_nums with blt_inds
+                blt_inds = np.array(
+                    list(set(blt_inds).intersection(ant_blt_inds)), dtype=np.int)
+            else:
+                blt_inds = ant_blt_inds
+
+        if times is not None:
+            times = uvutils._get_iterable(times)
+            if np.array(times).ndim > 1:
+                times = np.array(times).flatten()
+            if n_selects > 0:
+                history_update_string += ', times'
+            else:
+                history_update_string += 'times'
+            n_selects += 1
+
+            time_blt_inds = np.zeros(0, dtype=np.int)
+            for jd in times:
+                if jd in self.time_array:
+                    time_blt_inds = np.append(
+                        time_blt_inds, np.where(self.time_array == jd)[0])
+                else:
+                    raise ValueError(
+                        'Time {t} is not present in the time_array'.format(t=jd))
+
+            if blt_inds is not None:
+                # Use intesection (and) to join antenna_names/nums/ant_pairs_nums/blt_inds with times
+                blt_inds = np.array(
+                    list(set(blt_inds).intersection(time_blt_inds)), dtype=np.int)
+            else:
+                blt_inds = time_blt_inds
+
+        if blt_inds is not None:
+            if len(blt_inds) == 0:
+                raise ValueError(
+                    'No baseline-times were found that match criteria')
+            if max(blt_inds) >= self.Nblts:
+                raise ValueError(
+                    'blt_inds contains indices that are too large')
+            if min(blt_inds) < 0:
+                raise ValueError('blt_inds contains indices that are negative')
+
+            blt_inds = list(sorted(set(list(blt_inds))))
+
+        if freq_chans is not None:
+            freq_chans = uvutils._get_iterable(freq_chans)
+            if np.array(freq_chans).ndim > 1:
+                freq_chans = np.array(freq_chans).flatten()
+            if frequencies is None:
+                if self.type != "waterfall":
+                    frequencies = self.freq_array[0, freq_chans]
+                else:
+                    frequencies = self.freq_array[freq_chans]
+
+            else:
+                frequencies = uvutils._get_iterable(frequencies)
+                if self.type != "waterfall":
+                    frequencies = np.sort(list(set(frequencies)
+                                          | set(self.freq_array[0, freq_chans])))
+                else:
+                    frequencies = np.sort(list(set(frequencies)
+                                          | set(self.freq_array[freq_chans])))
+
+        if frequencies is not None:
+            frequencies = uvutils._get_iterable(frequencies)
+            if np.array(frequencies).ndim > 1:
+                frequencies = np.array(frequencies).flatten()
+            if n_selects > 0:
+                history_update_string += ', frequencies'
+            else:
+                history_update_string += 'frequencies'
+            n_selects += 1
+
+            freq_inds = np.zeros(0, dtype=np.int)
+            # this works because we only allow one SPW. This will have to be reworked when we support more.
+            if self.type != "waterfall":
+                freq_arr_use = self.freq_array[0, :]
+            else:
+                freq_arr_use = self.freq_array[:]
+            for f in frequencies:
+                if f in freq_arr_use:
+                    freq_inds = np.append(
+                        freq_inds, np.where(freq_arr_use == f)[0])
+                else:
+                    raise ValueError(
+                        'Frequency {f} is not present in the freq_array'.format(f=f))
+
+            freq_inds = list(sorted(set(list(freq_inds))))
+        else:
+            freq_inds = None
+
+        if polarizations is not None:
+            polarizations = uvutils._get_iterable(polarizations)
+            if np.array(polarizations).ndim > 1:
+                polarizations = np.array(polarizations).flatten()
+            if n_selects > 0:
+                history_update_string += ', polarizations'
+            else:
+                history_update_string += 'polarizations'
+            n_selects += 1
+
+            pol_inds = np.zeros(0, dtype=np.int)
+            for p in polarizations:
+                if isinstance(p, str):
+                    p_num = uvutils.polstr2num(p, x_orientation=self.x_orientation)
+                else:
+                    p_num = p
+                if p_num in self.polarization_array:
+                    pol_inds = np.append(pol_inds, np.where(
+                        self.polarization_array == p_num)[0])
+                else:
+                    raise ValueError(
+                        'Polarization {p} is not present in the polarization_array'.format(p=p))
+
+            pol_inds = list(sorted(set(list(pol_inds))))
+        else:
+            pol_inds = None
+
+        history_update_string += ' using pyuvdata.'
+
+        return blt_inds, ant_inds, freq_inds, pol_inds, history_update_string
+
+    def _select_metadata(self, blt_inds, ant_inds, freq_inds, pol_inds,
+                         history_update_string):
+        """Perform select on everything except the data-sized arrays.
+
+        Parameters
+        ----------
+        blt_inds : list of int
+            list of baseline-time indices to keep. Can be None (to keep everything).
+        freq_inds : list of int
+            list of frequency indices to keep. Can be None (to keep everything).
+        pol_inds : list of int
+            list of polarization indices to keep. Can be None (to keep everything).
+        history_update_string : str
+            string to append to the end of the history.
+        keep_all_metadata : bool
+            Option to keep metadata for antennas that are no longer in the dataset.
+
+        """
+        if blt_inds is not None:
+            self.Nblts = len(blt_inds)
+            if self.type == "baseline":
+                self.baseline_array = self.baseline_array[blt_inds]
+                self.Nbls = len(np.unique(self.baseline_array))
+                self.ant_1_array = self.ant_1_array[blt_inds]
+                self.ant_2_array = self.ant_2_array[blt_inds]
+                self.Nants_data = int(len(set(self.ant_1_array.tolist()
+                                              + self.ant_2_array.tolist())))
+
+            self.time_array = self.time_array[blt_inds]
+            self.lst_array = self.lst_array[blt_inds]
+            self.Ntimes = len(np.unique(self.time_array))
+
+        if self.type == "antenna":
+            if ant_inds is not None:
+                self.ant_array = self.ant_array[ant_inds]
+                self.Nants_data = int(len(self.ant_array))
+
+        if freq_inds is not None:
+            self.Nfreqs = len(freq_inds)
+            if self.type != "waterfall":
+                self.freq_array = self.freq_array[:, freq_inds]
+            else:
+                self.freq_array = self.freq_array[freq_inds]
+
+        if pol_inds is not None:
+            self.Npols = len(pol_inds)
+            self.polarization_array = self.polarization_array[pol_inds]
+
+        self.history = self.history + history_update_string
+
+    def select(self, antenna_nums=None, ant_inds=None,
+               bls=None, frequencies=None, freq_chans=None,
+               times=None, polarizations=None, blt_inds=None, run_check=True,
+               check_extra=True, run_check_acceptability=True, inplace=True):
+        """Downselect data to keep on the object along various axes.
+
+        Axes that can be selected along depend on the current type of the object.
+        However some axis may always be selected upon, these include frequencies, times and polarizations.
+        In "baseline" and "antenna" modes, antenna numbers may be selected.
+        In "baseline" mode, antenna pairs may be selected.
+        Specific baseline-time indices can also be selected in "baseline" mode, but this is not commonly used.
+        The history attribute on the object will be updated to identify the
+        operations performed.
+
+        Parameters
+        ----------
+        antenna_nums : array_like of int, optional
+            The antennas numbers to keep in the object (antenna positions and
+            names for the removed antennas will be retained unless
+            `keep_all_metadata` is False). This cannot be provided if
+            `antenna_names` is also provided.
+        bls : list of tuple, optional
+            A list of antenna number tuples (e.g. [(0,1), (3,2)]) or a list of
+            baseline 3-tuples (e.g. [(0,1,'xx'), (2,3,'yy')]) specifying baselines
+            to keep in the object. For length-2 tuples, the ordering of the numbers
+            within the tuple does not matter. For length-3 tuples, the polarization
+            string is in the order of the two antennas. If length-3 tuples are
+            provided, `polarizations` must be None.
+        frequencies : array_like of float, optional
+            The frequencies to keep in the object, each value passed here should
+            exist in the freq_array.
+        freq_chans : array_like of int, optional
+            The frequency channel numbers to keep in the object.
+        times : array_like of float, optional
+            The times to keep in the object, each value passed here should
+            exist in the time_array.
+        polarizations : array_like of int, optional
+            The polarizations numbers to keep in the object, each value passed
+            here should exist in the polarization_array.
+        blt_inds : array_like of int, optional
+            The baseline-time indices to keep in the object. This is
+            not commonly used.
+        ant_inds : array_like of int, optional
+            The antenna indices to keep in the object. This is
+            not commonly used.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones (the
+            default is True, meaning the optional parameters will be checked).
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+        inplace : bool
+            Option to perform the select directly on self or return a new UVData
+            object with just the selected data.
+
+        Returns
+        -------
+        UVData object or None
+            None is returned if inplace is True, otherwise a new UVData object
+            with just the selected data is returned
+
+        Raises
+        ------
+        ValueError
+            If any of the parameters are set to inappropriate values.
+
+        """
+        if inplace:
+            uv_object = self
+        else:
+            uv_object = copy.deepcopy(self)
+
+        blt_inds, ant_inds, freq_inds, pol_inds, history_update_string = \
+            uv_object._select_preprocess(antenna_nums, bls,
+                                         frequencies, freq_chans, times,
+                                         polarizations, blt_inds, ant_inds)
+
+        # do select operations on everything except data_array, flag_array and nsample_array
+        uv_object._select_metadata(blt_inds, ant_inds, freq_inds, pol_inds,
+                                   history_update_string)
+
+        if blt_inds is not None:
+            if self.type == "baseline":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[blt_inds, :, :, :])
+            if self.type == "waterfall":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[blt_inds, :, :])
+            if self.type == "antenna":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, :, blt_inds, :])
+
+        if ant_inds is not None and self.type == "antenna":
+            for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                setattr(uv_object, param_name, param[ant_inds, :, :, :])
+
+        if freq_inds is not None:
+            if self.type == "baseline":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, freq_inds, :])
+            if self.type == "waterfall":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, freq_inds, :])
+            if self.type == "antenna":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, freq_inds, :, :])
+
+        if pol_inds is not None:
+            if self.type == "baseline":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, :, pol_inds])
+            if self.type == "waterfall":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, pol_inds])
+            if self.type == "antenna":
+                for param_name, param in zip(self._data_params, uv_object.data_like_parameters):
+                    setattr(uv_object, param_name, param[:, :, :, :, pol_inds])
+
+        # check if object is uv_object-consistent
+        if run_check:
+            uv_object.check(check_extra=check_extra,
+                            run_check_acceptability=run_check_acceptability)
+
+        if not inplace:
+            return uv_object
+
+    def read(self, filename, history=''):
+        """Read in flag/metric data from a HDF5 file.
+
+        Parameters
+        ----------
+        filename : str
+            The file name to read.
+        history : str
+            History string to append to UVFlag history attribute.
 
-        Args:
-            filename: The file name to read.
-            history: History string to append to UVFlag history attribute.
         """
         import h5py
 
@@ -215,28 +1060,126 @@ class UVFlag(object):
                 header = f['/Header']
 
                 self.type = uvutils._bytes_to_str(header['type'][()])
+                if self.type == 'antenna':
+                    self._set_type_antenna()
+                elif self.type == 'baseline':
+                    self._set_type_baseline()
+                elif self.type == 'waterfall':
+                    self._set_type_waterfall()
+                else:
+                    raise ValueError("File cannot be read. Received type "
+                                     "parameter: {receive} but "
+                                     "must be within acceptable values: "
+                                     "{expect}".format(receive=self.type,
+                                                       expect=(', ').join(self._type.acceptable_vals)))
+
                 self.mode = uvutils._bytes_to_str(header['mode'][()])
+
+                if self.mode == "metric":
+                    self._set_mode_metric()
+                elif self.mode == "flag":
+                    self._set_mode_flag()
+                else:
+                    raise ValueError("File cannot be read. Received mode "
+                                     "parameter: {receive} but "
+                                     "must be within acceptable values: "
+                                     "{expect}".format(receive=self.mode,
+                                                       expect=(', ').join(self._mode.acceptable_vals)))
+
+                if 'x_orientation' in header.keys():
+                    self.x_orientation = uvutils._bytes_to_str(header['x_orientation'][()])
+
                 self.time_array = header['time_array'][()]
+                if 'Ntimes' in header.keys():
+                    self.Ntimes = int(header['Ntimes'][()])
+                else:
+                    self.Ntimes = np.unique(self.time_array).size
+
+                # for antenna and waterfall, Nblts is used to define
+                # the size of some arrays but is equivalent to _Ntimes
+                # for baseline type nblts is should be stored
+                # if not it is read later
+                if 'Nblts' in header.keys():
+                    self.Nblts = int(header['Nblts'][()])
+                else:
+                    self.Nblts = self.Ntimes
+
                 self.lst_array = header['lst_array'][()]
+
                 self.freq_array = header['freq_array'][()]
-                self.history = (uvutils._bytes_to_str(header['history'][()])
-                                + ' Read by ' + self.pyuvdata_version_str)
+                if 'Nfreqs' in header.keys():
+                    self.Nfreqs = int(header['Nfreqs'][()])
+                else:
+                    self.Nfreqs = np.unique(self.freq_array).size
+
+                self.history = uvutils._bytes_to_str(header['history'][()])
+
                 self.history += history
+
+                if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+                    self.history += self.pyuvdata_version_str
+
                 if 'label' in header.keys():
                     self.label = uvutils._bytes_to_str(header['label'][()])
-                self.polarization_array = header['polarization_array'][()]
+
+                polarization_array = header['polarization_array'][()]
+                if isinstance(polarization_array[0], np.string_):
+                    polarization_array = np.asarray(polarization_array,
+                                                    dtype=np.str_)
+                self.polarization_array = polarization_array
+
+                if 'Npols' in header.keys():
+                    self.Npols = int(header['Npols'][()])
+                else:
+                    self.Npols = len(self.polarization_array)
+
                 if self.type == 'baseline':
                     self.baseline_array = header['baseline_array'][()]
+
+                    #  if the Nblts was set via the antenna/waterfall method
+                    # it needs to be overwritten  with the correct shape.
+                    if self.Nblts == self.Ntimes:
+                        self.Nblts = len(self.baseline_array)
+
+                    if 'Nbls' in header.keys():
+                        self.Nbls = int(header['Nbls'][()])
+                    else:
+                        self.Nbls = np.unique(self.baseline_array).size
+
                     self.ant_1_array = header['ant_1_array'][()]
                     self.ant_2_array = header['ant_2_array'][()]
+
                     try:
-                        self.Nants_telescope = header['Nants_telescope'][()]
+                        self.Nants_telescope = int(header['Nants_telescope'][()])
                     except KeyError:
                         warnings.warn('Nants_telescope not available in file, '
                                       'assuming < 2048.')
                         self.Nants_telescope = None
+
+                    if 'Nants_data' in header.keys():
+                        self.Nants_data = int(header['Nants_data'][()])
+                    else:
+                        self.Nants_data = int(len(self.ant_1_array.tolist()
+                                                  + self.ant_2_array.tolist()))
+
+                    if 'Nspws' in header.keys():
+                        self.Nspws = int(header['Nspws'][()])
+                    else:
+                        self.Nspws = np.shape(self.freq_array)[0]
+
                 elif self.type == 'antenna':
                     self.ant_array = header['ant_array'][()]
+                    try:
+                        self.Nants_data = int(header['Nants_data'][()])
+                    except KeyError:
+                        warnings.warn('Nants_data not available in file, '
+                                      'attempting to calculate from ant_array.')
+                        self.Nants_data = len(self.ant_array)
+
+                    if 'Nspws' in header.keys():
+                        self.Nspws = int(header['Nspws'][()])
+                    else:
+                        self.Nspws = np.shape(self.freq_array)[0]
 
                 dgrp = f['/Data']
                 if self.mode == 'metric':
@@ -249,14 +1192,18 @@ class UVFlag(object):
             self.clear_unused_attributes()
 
     def write(self, filename, clobber=False, data_compression='lzf'):
-        """
-        Write a UVFlag object to a hdf5 file.
+        """Write a UVFlag object to a hdf5 file.
+
+        Parameters
+        ----------
+        filename : str
+            The file to write to.
+        clobber : bool
+            Option to overwrite the file if it already exists.
+         data_compression : str
+            HDF5 filter to apply when writing the data_array.
+            If no compression is wanted, set to None.
 
-        Args:
-            filename: The file to write to.
-            clobber: Option to overwrite the file if it already exists. Default is False.
-            data_compression: HDF5 filter to apply when writing the data_array. Default is
-                 LZF. If no compression is wanted, set to None.
         """
         import h5py
 
@@ -272,21 +1219,47 @@ class UVFlag(object):
             # write out metadata
             header['type'] = uvutils._str_to_bytes(self.type)
             header['mode'] = uvutils._str_to_bytes(self.mode)
+
+            header['Ntimes'] = self.Ntimes
             header['time_array'] = self.time_array
             header['lst_array'] = self.lst_array
+
             header['freq_array'] = self.freq_array
-            header['polarization_array'] = self.polarization_array
-            header['history'] = uvutils._str_to_bytes(self.history + 'Written by '
-                                                      + self.pyuvdata_version_str)
+            header['Nfreqs'] = self.Nfreqs
+
+            header['Npols'] = self.Npols
+
+            if self.x_orientation is not None:
+                header['x_orientation'] = uvutils._str_to_bytes(self.x_orientation)
+
+            if isinstance(self.polarization_array.item(0), six.string_types):
+                polarization_array = np.asarray(self.polarization_array,
+                                                dtype=np.string_)
+            else:
+                polarization_array = self.polarization_array
+            header['polarization_array'] = polarization_array
+
+            if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+                self.history += self.pyuvdata_version_str
+
+            header['history'] = uvutils._str_to_bytes(self.history)
+
             header['label'] = uvutils._str_to_bytes(self.label)
 
             if self.type == 'baseline':
                 header['baseline_array'] = self.baseline_array
+                header['Nbls'] = self.Nbls
+                header['Nblts'] = self.Nblts
                 header['ant_1_array'] = self.ant_1_array
                 header['ant_2_array'] = self.ant_2_array
+                header['Nants_data'] = self.Nants_data
                 header['Nants_telescope'] = self.Nants_telescope
+                header['Nspws'] = self.Nspws
+
             elif self.type == 'antenna':
                 header['ant_array'] = self.ant_array
+                header['Nants_data'] = self.Nants_data
+                header['Nspws'] = self.Nspws
 
             dgrp = f.create_group("Data")
             wtsdata = dgrp.create_dataset('weights_array', chunks=True,
@@ -301,14 +1274,34 @@ class UVFlag(object):
                                            data=self.flag_array,
                                            compression=data_compression)
 
-    def __add__(self, other, inplace=False, axis='time'):
+    def __add__(self, other, inplace=False, axis='time',
+                run_check=True, check_extra=True, run_check_acceptability=True):
         """Add two UVFlag objects together along a given axis.
-        Args:
-            other: second UVFlag object to concatenate with self.
-            inplace: Whether to concatenate to self, or create a new UVFlag object. Default is False.
-            axis: Axis along which to combine UVFlag objects. Default is time.
-        """
 
+        Parameters
+        ----------
+        other : UVFlag
+            object to combine with self.
+        axis : str
+            Axis along which to combine UVFlag objects.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+        inplace : bool
+            Option to perform the select directly on self or return a new UVData
+            object with just the selected data.
+
+        Returns
+        --------
+        uvf : UVFlag
+            If inplace==False, return new UVFlag object.
+
+        """
         # Handle in place
         if inplace:
             this = self
@@ -340,6 +1333,12 @@ class UVFlag(object):
                 this.baseline_array = np.concatenate([this.baseline_array, other.baseline_array])
                 this.ant_1_array = np.concatenate([this.ant_1_array, other.ant_1_array])
                 this.ant_2_array = np.concatenate([this.ant_2_array, other.ant_2_array])
+                this.Nants_data = int(len(set(self.ant_1_array.tolist()
+                                              + self.ant_2_array.tolist())))
+
+            this.Ntimes = np.unique(this.time_array).size
+            this.Nblts = len(this.time_array)
+
         elif axis == 'baseline':
             if self.type != 'baseline':
                 raise ValueError('Flag object of type ' + self.type + ' cannot be '
@@ -349,16 +1348,27 @@ class UVFlag(object):
             this.baseline_array = np.concatenate([this.baseline_array, other.baseline_array])
             this.ant_1_array = np.concatenate([this.ant_1_array, other.ant_1_array])
             this.ant_2_array = np.concatenate([this.ant_2_array, other.ant_2_array])
+            this.Nants_data = int(len(set(self.ant_1_array.tolist()
+                                          + self.ant_2_array.tolist())))
+
+            this.Nbls = np.unique(this.baseline_array).size
+            this.Nblts = len(this.baseline_array)
+
         elif axis == 'antenna':
             if self.type != 'antenna':
                 raise ValueError('Flag object of type ' + self.type + ' cannot be '
                                  'concatenated along antenna axis.')
             this.ant_array = np.concatenate([this.ant_array, other.ant_array])
+            this.Nants_data = len(this.ant_array)
         elif axis == 'frequency':
-            this.freq_array = np.concatenate([this.freq_array, other.freq_array])
+            this.freq_array = np.concatenate([this.freq_array,
+                                              other.freq_array],
+                                             axis=-1)
+            this.Nfreqs = np.unique(this.freq_array.flatten()).size
         elif axis in ['polarization', 'pol', 'jones']:
             this.polarization_array = np.concatenate([this.polarization_array,
                                                       other.polarization_array])
+            this.Npols = len(this.polarization_array)
 
         if this.mode == 'flag':
             this.flag_array = np.concatenate([this.flag_array, other.flag_array],
@@ -368,27 +1378,68 @@ class UVFlag(object):
                                                 other.metric_array], axis=ax)
         this.weights_array = np.concatenate([this.weights_array,
                                              other.weights_array], axis=ax)
-        this.history += 'Data combined along ' + axis + ' axis with ' + self.pyuvdata_version_str
 
+        this.history += 'Data combined along ' + axis + ' axis. '
+        if not uvutils._check_history_version(this.history, this.pyuvdata_version_str):
+            this.history += this.pyuvdata_version_str
+
+        this.Ntimes = np.unique(this.time_array).size
+
+        if run_check:
+            this.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
         if not inplace:
             return this
 
-    def __iadd__(self, other, axis='time'):
-        """
-        In place add.
+    def __iadd__(self, other, axis='time',
+                 run_check=True, check_extra=True, run_check_acceptability=True):
+        """In place add.
+
+        Parameters
+        ----------
+        other : UVFlag
+            object to combine with self.
+        axis : str
+            Axis along which to combine UVFlag objects.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
 
-        Args:
-            other: Another UVFlag object which will be added to self.
-            axis: Axis along which to combine UVFlag objects. Default is time.
         """
-        self.__add__(other, inplace=True, axis=axis)
+        self.__add__(other, inplace=True, axis=axis, run_check=True,
+                     check_extra=True, run_check_acceptability=True)
         return self
 
-    def __or__(self, other, inplace=False):
+    def __or__(self, other, inplace=False, run_check=True,
+               check_extra=True, run_check_acceptability=True,):
         """Combine two UVFlag objects in "flag" mode by "OR"-ing their flags.
-        Args:
-            other: second UVFlag object to combine with self.
-            inplace: Whether to combine to self, or create a new UVFlag object. Default is False.
+
+        Parameters
+        ----------
+        other : UVFlag
+            object to combine with self.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+        inplace : bool
+            Option to perform the select directly on self or return a new UVData
+            object with just the selected data.
+
+        Returns
+        --------
+        uvf : UVFlag
+            If inplace==False, return new UVFlag object.
+
         """
         if (self.mode != 'flag') or (other.mode != 'flag'):
             raise ValueError('UVFlag object must be in "flag" mode to use "or" function.')
@@ -402,57 +1453,84 @@ class UVFlag(object):
         if other.history not in this.history:
             this.history += "Flags OR'd with: " + other.history
 
+        if not uvutils._check_history_version(this.history, this.pyuvdata_version_str):
+            this.history += this.pyuvdata_version_str
+
+        if run_check:
+            this.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
         if not inplace:
             return this
 
-    def __ior__(self, other):
-        """In place or
-        Args:
-            other: second UVFlag object to combine with self.
+    def __ior__(self, other, run_check=True,
+                check_extra=True, run_check_acceptability=True):
+        """Perform an inplace logical or.
+
+        Parameters
+        ----------
+        other : UVFlag
+            object to combine with self.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
-        self.__or__(other, inplace=True)
+        self.__or__(other, inplace=True, run_check=True,
+                    check_extra=True, run_check_acceptability=True)
         return self
 
     def clear_unused_attributes(self):
+        """Remove unused attributes.
+
+        Useful when changing type or mode or to save memory.
+        Will set all non-required attributes to None, except x_orientation.
+
+
         """
-        Remove unused attributes. Useful when changing type or mode.
-        """
-        if hasattr(self, 'baseline_array') and self.type != 'baseline':
-            del(self.baseline_array)
-        if hasattr(self, 'ant_1_array') and self.type != 'baseline':
-            del(self.ant_1_array)
-        if hasattr(self, 'ant_2_array') and self.type != 'baseline':
-            del(self.ant_2_array)
-        if hasattr(self, 'Nants_telescope') and self.type != 'baseline':
-            del(self.Nants_telescope)
-        if hasattr(self, 'ant_array') and self.type != 'antenna':
-            del(self.ant_array)
-        if hasattr(self, 'metric_array') and self.mode != 'metric':
-            del(self.metric_array)
-        if hasattr(self, 'flag_array') and self.mode != 'flag':
-            del(self.flag_array)
+        for p in self:
+            attr = getattr(self, p)
+            if not attr.required and attr.value is not None and attr.name != 'x_orientation':
+                attr.value = None
+                setattr(self, p, attr)
 
     def copy(self):
-        """ Simply return a copy of this object """
+        """Return a copy of this object."""
         return copy.deepcopy(self)
 
-    def combine_metrics(self, others, method='quadmean', inplace=True):
-        """
-        Combine metric arrays between different UVFlag objects together.
-        Args:
-            others (UVFlag or list of UVFlags): Other UVFlag objects to combine
-                metrics with this one.
-            method (str, optional): Method to combine metrics. Default is "quadmean".
-            inplace (bool, optional): Perform combination in place. Default is True.
-        Returns:
-            uvf (UVFlag): If inplace==False, return new UVFlag object with combined metrics.
+    def combine_metrics(self, others, method='quadmean', inplace=True,
+                        run_check=True, check_extra=True,
+                        run_check_acceptability=True):
+        """Combine metric arrays between different UVFlag objects together.
+
+        Parameters
+        ----------
+        others : UVFlag or list of UVFlags
+            Other UVFlag objects to combine metrics with this one.
+        method : str, {"quadmean", "absmean", "mean", "or", "and"}
+            Method to combine metrics.
+        inplace : bool, optional
+            Perform combination in place.
+
+        Returns
+        --------
+        uvf : UVFlag
+            If inplace==False, return new UVFlag object with combined metrics.
+
         """
         # Ensure others is iterable (in case of single UVFlag object)
-        others = uvutils._get_iterable(others)
+        # cannot use uvutils._get_iterable because the object itself is iterable
+        if not isinstance(others, (list, tuple, np.ndarray)):
+            others = [others]
+
         if np.any([not isinstance(other, UVFlag) for other in others]):
             raise ValueError('"others" must be UVFlag or list of UVFlag objects')
         if (self.mode != 'metric') or np.any([other.mode != 'metric' for other in others]):
-            raise ValueError('UVFlag object and "others" must be in "flag" mode '
+            raise ValueError('UVFlag object and "others" must be in "metric" mode '
                              'to use "add_metrics" function.')
         if inplace:
             this = self
@@ -469,20 +1547,38 @@ class UVFlag(object):
         darray, warray = uvutils.collapse(darray, method, weights=warray, axis=0, return_weights=True)
         this.metric_array = darray
         this.weights_array = warray
-        this.history += 'Combined metric arrays using ' + self.pyuvdata_version_str
+        this.history += 'Combined metric arrays. '
+
+        if not uvutils._check_history_version(this.history, this.pyuvdata_version_str):
+            this.history += this.pyuvdata_version_str
+
+        if run_check:
+            this.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
         if not inplace:
             return this
 
-    def collapse_pol(self, method='quadmean'):
-        """
-        Collapse the polarization axis using a given method.
+    def collapse_pol(self, method='quadmean', run_check=True, check_extra=True,
+                     run_check_acceptability=True):
+        """Collapse the polarization axis using a given method.
 
         If the original UVFlag object has more than one polarization,
         the resulting polarization_array will be a single element array with a
         comma separated string encoding the original polarizations.
 
-        Args:
-            method: How to collapse the dimension(s)
+        Parameters
+        ----------
+        method : str, {"quadmean", "absmean", "mean", "or", "and"}
+            How to collapse the dimension(s).
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
         method = method.lower()
         if self.mode == 'flag':
@@ -496,7 +1592,13 @@ class UVFlag(object):
             darr = np.expand_dims(d, axis=d.ndim)
             self.weights_array = np.expand_dims(w, axis=w.ndim)
             self.polarization_array = np.array([','.join(map(str, self.polarization_array))],
-                                               dtype=np.string_)
+                                               dtype=np.str_)
+
+            self.Npols = len(self.polarization_array)
+            # this typing is a nightmare. np.string_ casts as '|S' but is not
+            # the same as np.string_
+            self._polarization_array.expected_type = six.string_types
+            self._polarization_array.acceptable_vals = None
         else:
             warnings.warn('Cannot collapse polarization axis when only one pol present.')
             return
@@ -504,20 +1606,40 @@ class UVFlag(object):
             self.flag_array = darr
         else:
             self.metric_array = darr
-            self.mode = 'metric'
+            self._set_mode_metric()
         self.clear_unused_attributes()
-        self.history += 'Pol axis collapsed with ' + self.pyuvdata_version_str
+        self.history += 'Pol axis collapse. '
+
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
+
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
+
+    def to_waterfall(self, method='quadmean', keep_pol=True, run_check=True,
+                     check_extra=True, run_check_acceptability=True):
+        """Convert an 'antenna' or 'baseline' type object to waterfall.
+
+        Parameters
+        ----------
+        method : str, {"quadmean", "absmean", "mean", "or", "and"}
+            How to collapse the dimension(s).
+        keep_pol : bool
+            Whether to also collapse the polarization dimension
+            If keep_pol is False, and the original UVFlag object has more
+            than one polarization, the resulting polarization_array
+            will be a single element array with a comma separated string
+            encoding the original polarizations.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
 
-    def to_waterfall(self, method='quadmean', keep_pol=True):
-        """
-        Convert an 'antenna' or 'baseline' type object to waterfall using a given method.
-        Args:
-            method: How to collapse the dimension(s)
-            keep_pol: Whether to also collapse the polarization dimension
-                      If keep_pol is False, and the original UVFlag object has more
-                      than one polarization, the resulting polarization_array
-                      will be a single element array with a comma separated string
-                      encoding the original polarizations.
         """
         method = method.lower()
         if self.type == 'waterfall' and (keep_pol or (len(self.polarization_array) == 1)):
@@ -554,26 +1676,51 @@ class UVFlag(object):
             self.lst_array = self.lst_array[ri]
         if ((method == 'or') or (method == 'and')) and (self.mode == 'flag'):
             # If using a boolean operation (AND/OR) and in flag mode, stay in flag
-            self.flag_array = darr
+            # flags should be bool, but somehow it is cast as float64
+            # is reacasting to bool like this best?
+            self.flag_array = darr.astype(bool)
         else:
             # Otherwise change to (or stay in) metric
             self.metric_array = darr
-            self.mode = 'metric'
+            self._set_mode_metric()
         self.freq_array = self.freq_array.flatten()
-        self.type = 'waterfall'
-        self.history += 'Collapsed to type "waterfall" with ' + self.pyuvdata_version_str
+        self.Nspws = None
+        self._set_type_waterfall()
+        self.history += 'Collapsed to type "waterfall". '  # + self.pyuvdata_version_str
+
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
+
         self.clear_unused_attributes()
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
 
-    def to_baseline(self, uv, force_pol=False):
+    def to_baseline(self, uv, force_pol=False, run_check=True,
+                    check_extra=True, run_check_acceptability=True):
         """Convert a UVFlag object of type "waterfall" to type "baseline".
+
         Broadcasts the flag array to all baselines.
         This function does NOT apply flags to uv.
-        Args:
-            uv: UVData or UVFlag object of type baseline to match.
-            force_pol: If True, will use 1 pol to broadcast to any other pol.
-                       Otherwise, will require polarizations match.
-                       For example, this keyword is useful if one flags on all
-                       pols combined, and wants to broadcast back to individual pols.
+
+        Parameters
+        ----------
+        uv : UVData or UVFlag object
+            Objcet with type baseline to match.
+        force_pol : bool
+            If True, will use 1 pol to broadcast to any other pol.
+            Otherwise, will require polarizations match.
+            For example, this keyword is useful if one flags on all
+            pols combined, and wants to broadcast back to individual pols.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
         if self.type == 'baseline':
             return
@@ -600,7 +1747,7 @@ class UVFlag(object):
             else:
                 raise ValueError('Polarizations could not be made to match.')
         # Populate arrays
-        warr = np.zeros_like(uv.flag_array)
+        warr = np.zeros_like(uv.flag_array, dtype=np.float)
         if self.mode == 'flag':
             arr = np.zeros_like(uv.flag_array)
             sarr = self.flag_array
@@ -618,24 +1765,56 @@ class UVFlag(object):
         self.weights_array = warr
 
         self.baseline_array = uv.baseline_array
+        self.Nbls = np.unique(self.baseline_array).size
+
+        # Check the frequency array for Nspws, otherwise broadcast to 1,Nfreqs
+        self.freq_array = np.atleast_2d(self.freq_array)
+        self.Nspws = self.freq_array.shape[0]
+
         self.ant_1_array = uv.ant_1_array
         self.ant_2_array = uv.ant_2_array
+        self.Nants_data = int(len(set(self.ant_1_array.tolist()
+                                      + self.ant_2_array.tolist())))
+
         self.time_array = uv.time_array
         self.lst_array = uv.lst_array
-        self.Nants_telescope = uv.Nants_telescope
-        self.type = 'baseline'
-        self.history += 'Broadcast to type "baseline" with ' + self.pyuvdata_version_str
 
-    def to_antenna(self, uv, force_pol=False):
+        self.Nants_telescope = int(uv.Nants_telescope)
+        self._set_type_baseline()
+        self.history += 'Broadcast to type "baseline". '
+
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
+
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
+
+    def to_antenna(self, uv, force_pol=False, run_check=True,
+                   check_extra=True, run_check_acceptability=True):
         """Convert a UVFlag object of type "waterfall" to type "antenna".
+
         Broadcasts the flag array to all antennas.
         This function does NOT apply flags to uv.
-        Args:
-            uv: UVCal or UVFlag object of type antenna to match.
-            force_pol: If True, will use 1 pol to broadcast to any other pol.
-                       Otherwise, will require polarizations match.
-                       For example, this keyword is useful if one flags on all
-                       pols combined, and wants to broadcast back to individual pols.
+
+        Parameters
+        ----------
+        uv : UVCal or UVFlag object
+            object of type antenna to match.
+        force_pol : bool
+            If True, will use 1 pol to broadcast to any other pol.
+            Otherwise, will require polarizations match.
+            For example, this keyword is useful if one flags on all
+            pols combined, and wants to broadcast back to individual pols.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
         if self.type == 'antenna':
             return
@@ -678,46 +1857,92 @@ class UVFlag(object):
                                                                    :, :, :]
         self.weights_array = self.weights_array.repeat(len(uv.ant_array), axis=0)
         self.ant_array = uv.ant_array
-        self.type = 'antenna'
-        self.history += 'Broadcast to type "antenna" with ' + self.pyuvdata_version_str
+        self.Nants_data = len(uv.ant_array)
+        # Check the frequency array for Nspws, otherwise broadcast to 1,Nfreqs
+        self.freq_array = np.atleast_2d(self.freq_array)
+        self.Nspws = self.freq_array.shape[0]
+
+        self._set_type_antenna()
+        self.history += 'Broadcast to type "antenna". '
 
-    def to_flag(self, threshold=np.inf):
-        """Convert to flag mode. NOT SMART. Removes metric_array and creates a
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
+
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
+
+    def to_flag(self, threshold=np.inf, run_check=True,
+                check_extra=True, run_check_acceptability=True):
+        """Convert to flag mode.
+
+        This function is NOT SMART. Removes metric_array and creates a
         flag_array from a simple threshold on the metric values.
 
-        Args:
-            threshold (float): Metric value over which the corresponding flag is
-                set to True. Default is np.inf, which results in flags of all False.
+        Parameters
+        ----------
+        threshold : float
+            Metric value over which the corresponding flag is
+            set to True. Default is np.inf, which results in flags of all False.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
         if self.mode == 'flag':
             return
         elif self.mode == 'metric':
             self.flag_array = np.where(self.metric_array >= threshold,
                                        True, False)
-            self.mode = 'flag'
+            self._set_mode_flag()
             self.weights_array = np.ones_like(self.metric_array, dtype=np.float)
         else:
             raise ValueError('Unknown UVFlag mode: ' + self.mode + '. Cannot convert to flag.')
-        self.history += 'Converted to mode "flag" with ' + self.pyuvdata_version_str
+        self.history += 'Converted to mode "flag". '
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
         self.clear_unused_attributes()
 
-    def to_metric(self, convert_wgts=False):
-        """Convert to metric mode. NOT SMART. Simply recasts flag_array as float
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
+
+    def to_metric(self, convert_wgts=False, run_check=True,
+                  check_extra=True, run_check_acceptability=True):
+        """Convert to metric mode.
+
+        This function is NOT SMART. Simply recasts flag_array as float
         and uses this as the metric array.
 
-        Args:
-            convert_wgts : bool, if True convert self.weights_array to ones
-                unless a column or row is completely flagged, in which case
-                convert those pixels to zero. This is used when reinterpretting
-                flags as metrics to calculate flag fraction. Zero weighting
-                completely flagged rows/columns prevents those from counting
-                against a threshold along the other dimension.
+        Parameters
+        ----------
+        convert_wgts : bool
+            if True convert self.weights_array to ones
+            unless a column or row is completely flagged, in which case
+            convert those pixels to zero. This is used when reinterpretting
+            flags as metrics to calculate flag fraction. Zero weighting
+            completely flagged rows/columns prevents those from counting
+            against a threshold along the other dimension.
+        run_check : bool
+            Option to check for the existence and proper shapes of parameters
+            after downselecting data on this object.
+        check_extra : bool
+            Option to check optional parameters as well as required ones.
+        run_check_acceptability : bool
+            Option to check acceptable range of the values of parameters after
+            downselecting data on this object.
+
         """
         if self.mode == 'metric':
             return
         elif self.mode == 'flag':
             self.metric_array = self.flag_array.astype(np.float)
-            self.mode = 'metric'
+            self._set_mode_metric()
             if convert_wgts:
                 self.weights_array = np.ones_like(self.weights_array)
                 if self.type == 'waterfall':
@@ -734,12 +1959,31 @@ class UVFlag(object):
                             self.weights_array[j, 0, :, :, i] *= ~and_rows_cols(self.flag_array[j, 0, :, :, i])
         else:
             raise ValueError('Unknown UVFlag mode: ' + self.mode + '. Cannot convert to metric.')
-        self.history += 'Converted to mode "metric" with ' + self.pyuvdata_version_str
+        self.history += 'Converted to mode "metric". '
+
+        if not uvutils._check_history_version(self.history, self.pyuvdata_version_str):
+            self.history += self.pyuvdata_version_str
         self.clear_unused_attributes()
 
+        if run_check:
+            self.check(check_extra=check_extra,
+                       run_check_acceptability=run_check_acceptability)
+
     def antpair2ind(self, ant1, ant2):
-        """
-        Get blt indices for given (ordered) antenna pair.
+        """Get blt indices for given (ordered) antenna pair.
+
+        Parameters
+        ----------
+        ant1 : int or array_like of int
+            Number of the first antenna
+        ant2 : int or array_like of int
+            Number of the second antenna
+
+        Returns
+        -------
+        int or array_like of int
+            baseline number(s) corresponding to the input antenna number
+
         """
         if self.type != 'baseline':
             raise ValueError('UVFlag object of type ' + self.type + ' does not '
@@ -747,48 +1991,56 @@ class UVFlag(object):
         return np.where((self.ant_1_array == ant1) & (self.ant_2_array == ant2))[0]
 
     def baseline_to_antnums(self, baseline):
-        """
-        Get the antenna numbers corresponding to a given baseline number.
+        """Get the antenna numbers corresponding to a given baseline number.
+
+        Parameters
+        ----------
+        baseline : int
+            baseline number
 
-        Args:
-            baseline(int): baseline number
+        Returns
+        -------
+        tuple
+            Antenna numbers corresponding to baseline.
 
-        Returns:
-            (tuple): Antenna numbers corresponding to baseline.
         """
         assert self.type == 'baseline', 'Must be "baseline" type UVFlag object.'
         return uvutils.baseline_to_antnums(baseline, self.Nants_telescope)
 
     def get_baseline_nums(self):
-        """
-        Returns numpy array of unique baseline numbers in data.
-        """
+        """Return numpy array of unique baseline numbers in data."""
         assert self.type == 'baseline', 'Must be "baseline" type UVFlag object.'
         return np.unique(self.baseline_array)
 
     def get_antpairs(self):
-        """
-        Returns list of unique antpair tuples (ant1, ant2) in data.
-        """
+        """Return list of unique antpair tuples (ant1, ant2) in data."""
         assert self.type == 'baseline', 'Must be "baseline" type UVFlag object.'
         return [self.baseline_to_antnums(bl) for bl in self.get_baseline_nums()]
 
 
 def flags2waterfall(uv, flag_array=None, keep_pol=False):
-    """
-    Convert a flag array to a 2D waterfall of dimensions (Ntimes, Nfreqs).
+    """Convert a flag array to a 2D waterfall of dimensions (Ntimes, Nfreqs).
+
     Averages over baselines and polarizations (in the case of visibility data),
     or antennas and jones parameters (in case of calibrationd data).
-    Args:
-        uv -- A UVData or UVCal object which defines the times and frequencies,
-              and supplies the flag_array to convert (if flag_array not specified)
-        flag_array -- Optional flag array to convert instead of uv.flag_array.
-                      Must have same dimensions as uv.flag_array.
-        keep_pol -- Option to keep the polarization axis intact. Default is False.
-    Returns:
-        waterfall -- 2D waterfall of averaged flags, for example fraction of baselines
-                     which are flagged for every time and frequency (in case of UVData input)
-                     Size is (Ntimes, Nfreqs) or (Ntimes, Nfreqs, Npols).
+    Parameters
+    ----------
+    uv : A UVData or UVCal object
+        Object defines the times and frequencies, and supplies the
+        flag_array to convert (if flag_array not specified)
+    flag_array :  Optional,
+        flag array to convert instead of uv.flag_array.
+        Must have same dimensions as uv.flag_array.
+    keep_pol : bool
+        Option to keep the polarization axis intact.
+
+    Returns
+    -------
+    waterfall : 2D array or 3D array
+        Waterfall of averaged flags, for example fraction of baselines
+        which are flagged for every time and frequency (in case of UVData input)
+        Size is (Ntimes, Nfreqs) or (Ntimes, Nfreqs, Npols).
+
     """
     if not isinstance(uv, (UVData, UVCal)):
         raise ValueError('flags2waterfall() requires a UVData or UVCal object as '
@@ -819,13 +2071,21 @@ def flags2waterfall(uv, flag_array=None, keep_pol=False):
 
 
 def and_rows_cols(waterfall):
-    """ For a 2D flag waterfall, flag pixels only if fully flagged along
+    """Perform logical and over rows and cols of a waterfall.
+
+    For a 2D flag waterfall, flag pixels only if fully flagged along
     time and/or frequency
-    Args:
-        waterfall - 2D boolean array of shape (Ntimes, Nfreqs)
-    Returns:
-        wf (2D array): A 2D array (size same as input) where only times/integrations
-            that were fully flagged are flagged.
+
+    Parameters
+    ----------
+    waterfall : 2D boolean array of shape (Ntimes, Nfreqs)
+
+    Returns
+    -------
+    wf : 2D array
+        A 2D array (size same as input) where only times/integrations
+        that were fully flagged are flagged.
+
     """
     wf = np.zeros_like(waterfall, dtype=np.bool)
     Ntimes, Nfreqs = waterfall.shape
@@ -835,12 +2095,19 @@ def and_rows_cols(waterfall):
 
 
 def lst_from_uv(uv):
-    """ Calculate the lst_array for a UVData or UVCal object.
-    Args:
-        uv: a UVData or UVCal object.
-    Returns:
-        lst_array: lst_array corresponding to time_array and at telescope location.
-                   Units are radian.
+    """Calculate the lst_array for a UVData or UVCal object.
+
+    Parameters
+    ----------
+    uv : a UVData or UVCal object.
+        Object from which lsts are calculated
+
+    Returns
+    -------
+    lst_array: array of float
+        lst_array corresponding to time_array and at telescope location.
+        Units are radian.
+
     """
     if not isinstance(uv, (UVCal, UVData)):
         raise ValueError('Function lst_from_uv can only operate on '


Test patch:
diff --git a/ci/pyuvdata_min_deps_tests.yml b/ci/pyuvdata_min_deps_tests.yml
index 3c82117b..1fd6937e 100644
--- a/ci/pyuvdata_min_deps_tests.yml
+++ b/ci/pyuvdata_min_deps_tests.yml
@@ -9,3 +9,7 @@ dependencies:
   - six
   - coverage
   - pytest-cov
+  - setuptools_scm
+  - pip
+  - pip:
+     - pytest-cases
diff --git a/ci/pyuvdata_tests.yml b/ci/pyuvdata_tests.yml
index 7cf34a53..08797ca2 100644
--- a/ci/pyuvdata_tests.yml
+++ b/ci/pyuvdata_tests.yml
@@ -13,3 +13,7 @@ dependencies:
   - six
   - coverage
   - pytest-cov
+  - setuptools_scm 
+  - pip
+  - pip:
+      - pytest-cases
diff --git a/pyuvdata/tests/__init__.py b/pyuvdata/tests/__init__.py
index f770a3de..b82d74cb 100644
--- a/pyuvdata/tests/__init__.py
+++ b/pyuvdata/tests/__init__.py
@@ -20,6 +20,16 @@ from astropy.utils import iers
 
 import pyuvdata.utils as uvutils
 
+# define a pytest marker for skipping pytest-cases
+try:
+    import pytest_cases
+
+    cases_installed = True
+except ImportError:
+    cases_installed = False
+reason = 'pytest-cases is not installed, skipping tests that require it.'
+skipIf_no_pytest_cases = pytest.mark.skipif(not cases_installed, reason=reason)
+
 # define a pytest marker for skipping casacore tests
 try:
     import casacore
diff --git a/pyuvdata/tests/test_uvflag.py b/pyuvdata/tests/test_uvflag.py
index 4418cf39..2003515d 100644
--- a/pyuvdata/tests/test_uvflag.py
+++ b/pyuvdata/tests/test_uvflag.py
@@ -10,6 +10,7 @@ import numpy as np
 import pyuvdata.tests as uvtest
 from pyuvdata import UVData
 from pyuvdata import UVCal
+from pyuvdata import utils as uvutils
 from pyuvdata.data import DATA_PATH
 from pyuvdata import UVFlag
 from pyuvdata.uvflag import lst_from_uv
@@ -18,21 +19,101 @@ from pyuvdata.uvflag import and_rows_cols
 from pyuvdata import version as uvversion
 import shutil
 import copy
+import warnings
 
 
+# The following three fixtures are used regularly
+# to initizize UVFlag objects from standard files
+# We need to define these here in order to set up
+# some skips for developers who do not have `pytest-cases` installed
+@pytest.fixture(scope='function')
+def uvf_from_miriad():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv)
+
+    # yield the object for the test
+    yield uvf
+
+    # do some cleanup
+    del(uvf, uv)
+
+
+@pytest.fixture(scope='function')
+def uvf_from_uvcal():
+    uvc = UVCal()
+    uvc.read_calfits(test_c_file)
+    uvf = UVFlag(uvc)
+
+    # yield the object for the test
+    yield uvf
+
+    # do some cleanup
+    del(uvf, uvc)
+
+
+@pytest.fixture(scope='function')
+def uvf_from_waterfall():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, waterfall=True)
+
+    # yield the object for the test
+    yield uvf
+
+    # do some cleanup
+    del(uvf, uv)
+
+
+# Try to import `pytest-cases` and define decorators used to
+# iterate over the three main types of UVFLag objects
+# otherwise make the decorators skip the tests that use these iterators
+try:
+    import pytest_cases
+
+    cases_decorator = pytest_cases.pytest_parametrize_plus(
+        "input_uvf", [pytest_cases.fixture_ref(uvf_from_miriad),
+                      pytest_cases.fixture_ref(uvf_from_uvcal),
+                      pytest_cases.fixture_ref(uvf_from_waterfall)])
+
+    cases_decorator_no_waterfall = pytest_cases.pytest_parametrize_plus(
+        "input_uvf", [pytest_cases.fixture_ref(uvf_from_miriad),
+                      pytest_cases.fixture_ref(uvf_from_uvcal)])
+
+    # This warning is raised by pytest_cases
+    # It is due to a feature the developer does
+    # not know how to handle yet. ignore for now.
+    warnings.filterwarnings("ignore",
+                            message="WARNING the new order is not"
+                                    + " taken into account !!", append=True)
+
+except ImportError:
+    cases_decorator = uvtest.skipIf_no_pytest_cases
+    cases_decorator_no_waterfall = uvtest.skipIf_no_pytest_cases
+
 test_d_file = os.path.join(DATA_PATH, 'zen.2457698.40355.xx.HH.uvcAA')
 test_c_file = os.path.join(DATA_PATH, 'zen.2457555.42443.HH.uvcA.omni.calfits')
 test_f_file = test_d_file + '.testuvflag.h5'
 test_outfile = os.path.join(DATA_PATH, 'test', 'outtest_uvflag.h5')
 
-pyuvdata_version_str = uvversion.version + '.'
-if uvversion.git_hash is not '':
+pyuvdata_version_str = ('  Read/written with pyuvdata version: '
+                        + uvversion.version + '.')
+if uvversion.git_hash != '':
     pyuvdata_version_str += ('  Git origin: ' + uvversion.git_origin
                              + '.  Git hash: ' + uvversion.git_hash
                              + '.  Git branch: ' + uvversion.git_branch
                              + '.  Git description: ' + uvversion.git_description + '.')
 
 
+def test_init_bad_mode():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    with pytest.raises(ValueError) as cm:
+        UVFlag(uv, mode='bad_mode',
+               history='I made a UVFlag object', label='test')
+    assert str(cm.value).startswith('Input mode must be within acceptable')
+
+
 def test_init_UVData():
     uv = UVData()
     uv.read_miriad(test_d_file)
@@ -61,7 +142,9 @@ def test_init_UVData_copy_flags():
     uv.read_miriad(test_d_file)
     uvf = uvtest.checkWarnings(UVFlag, [uv], {'copy_flags': True, 'mode': 'metric'},
                                nwarnings=1, message='Copying flags to type=="baseline"')
-    assert not hasattr(uvf, 'metric_array')  # Should be flag due to copy flags
+    #  with copy flags uvf.metric_array should be none
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     assert np.array_equal(uvf.flag_array, uv.flag_array)
     assert uvf.weights_array.shape == uv.flag_array.shape
     assert np.all(uvf.weights_array == 1)
@@ -103,7 +186,9 @@ def test_init_cal_copy_flags():
     uv.read_calfits(test_c_file)
     uvf = uvtest.checkWarnings(UVFlag, [uv], {'copy_flags': True, 'mode': 'metric'},
                                nwarnings=1, message='Copying flags to type=="antenna"')
-    assert not hasattr(uvf, 'metric_array')  # Should be flag due to copy flags
+    #  with copy flags uvf.metric_array should be none
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     assert np.array_equal(uvf.flag_array, uv.flag_array)
     assert uvf.weights_array.shape == uv.flag_array.shape
     assert uvf.type == 'antenna'
@@ -169,14 +254,15 @@ def test_init_waterfall_flag():
 def test_init_waterfall_copy_flags():
     uv = UVCal()
     uv.read_calfits(test_c_file)
-    pytest.raises(NotImplementedError, UVFlag, uv, copy_flags=True, mode='flag',
-                  waterfall=True)
+    with pytest.raises(NotImplementedError) as cm:
+        UVFlag(uv, copy_flags=True, mode='flag', waterfall=True)
+    assert str(cm.value).startswith('Cannot copy flags when initializing')
 
 
 def test_init_invalid_input():
     # input is not UVData, UVCal, path, or list/tuple
     with pytest.raises(ValueError) as cm:
-        foo = UVFlag(14)
+        UVFlag(14)
     assert str(cm.value).startswith('input to UVFlag.__init__ must be one of:')
 
 
@@ -198,6 +284,16 @@ def test_init_list_files_weights(tmpdir):
     assert np.all(uvf2.weights_array == np.concatenate([wts1, wts2], axis=0))
 
 
+def test_data_like_property_mode_tamper():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.mode = 'test'
+    with pytest.raises(ValueError) as cm:
+        list(uvf.data_like_parameters)
+    assert str(cm.value).startswith('Invalid mode. Mode must be one of')
+
+
 @uvtest.skipIf_no_h5py
 def test_read_write_loop():
     uv = UVData()
@@ -205,12 +301,102 @@ def test_read_write_loop():
     uvf = UVFlag(uv, label='test')
     uvf.write(test_outfile, clobber=True)
     uvf2 = UVFlag(test_outfile)
-    # Update history to match expected additions that were made
-    uvf.history += 'Written by ' + pyuvdata_version_str
-    uvf.history += ' Read by ' + pyuvdata_version_str
     assert uvf.__eq__(uvf2, check_history=True)
 
 
+@uvtest.skipIf_no_h5py
+def test_read_write_loop_with_optional_x_orientation():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.x_orientation = 'east'
+    uvf.write(test_outfile, clobber=True)
+    uvf2 = UVFlag(test_outfile)
+    assert uvf.__eq__(uvf2, check_history=True)
+
+
+@uvtest.skipIf_no_h5py
+def test_read_write_loop_waterfal():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.to_waterfall()
+    uvf.write(test_outfile, clobber=True)
+    uvf2 = UVFlag(test_outfile)
+    assert uvf.__eq__(uvf2, check_history=True)
+
+
+@uvtest.skipIf_no_h5py
+def test_bad_mode_savefile():
+    # redundant skip definition but a good example
+    h5py = pytest.importorskip('h5py')
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.write(test_outfile, clobber=True)
+    # manually re-read and tamper with parameters
+    with h5py.File(test_outfile, 'a') as h5:
+        mode = h5['Header/mode']
+        mode[...] = 'test'
+
+    with pytest.raises(ValueError) as cm:
+        uvf = UVFlag(test_outfile)
+    assert str(cm.value).startswith('File cannot be read. Received mode')
+
+
+@uvtest.skipIf_no_h5py
+def test_bad_type_savefile():
+    # redundant skip definition but a good example
+    h5py = pytest.importorskip('h5py')
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.write(test_outfile, clobber=True)
+    # manually re-read and tamper with parameters
+    with h5py.File(test_outfile, 'a') as h5:
+        mode = h5['Header/type']
+        mode[...] = 'test'
+
+    with pytest.raises(ValueError) as cm:
+        uvf = UVFlag(test_outfile)
+    assert str(cm.value).startswith('File cannot be read. Received type')
+
+
+@uvtest.skipIf_no_h5py
+def test_write_add_version_str():
+    h5py = pytest.importorskip('h5py')
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+
+    assert pyuvdata_version_str not in uvf.history
+    uvf.write(test_outfile, clobber=True)
+
+    with h5py.File(test_outfile, 'r') as h5:
+        hist = uvutils._bytes_to_str(h5['Header/history'][()])
+    assert pyuvdata_version_str in hist
+
+
+@uvtest.skipIf_no_h5py
+def test_read_add_version_str():
+    h5py = pytest.importorskip('h5py')
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv, label='test')
+
+    assert pyuvdata_version_str in uvf.history
+    uvf.write(test_outfile, clobber=True)
+
+    with h5py.File(test_outfile, 'r') as h5:
+        hist = h5['Header/history']
+        del hist
+
+    uvf2 = UVFlag(test_outfile)
+    assert pyuvdata_version_str in uvf2.history
+    assert uvf == uvf2
+
+
 @uvtest.skipIf_no_h5py
 def test_read_write_ant():
     uv = UVCal()
@@ -218,9 +404,50 @@ def test_read_write_ant():
     uvf = UVFlag(uv, mode='flag', label='test')
     uvf.write(test_outfile, clobber=True)
     uvf2 = UVFlag(test_outfile)
-    # Update history to match expected additions that were made
-    uvf.history += 'Written by ' + pyuvdata_version_str
-    uvf.history += ' Read by ' + pyuvdata_version_str
+    assert uvf.__eq__(uvf2, check_history=True)
+
+
+@uvtest.skipIf_no_h5py
+def test_read_missing_nants_data():
+    # redundant skip definition but a good example
+    h5py = pytest.importorskip('h5py')
+    uv = UVCal()
+    uv.read_calfits(test_c_file)
+    uvf = UVFlag(uv, mode='flag', label='test')
+    uvf.write(test_outfile, clobber=True)
+
+    with h5py.File(test_outfile, 'a') as h5:
+        del h5['Header/Nants_data']
+
+    uvf2 = uvtest.checkWarnings(UVFlag, [test_outfile], {}, nwarnings=1,
+                                message=['Nants_data not available in file,'],
+                                category=UserWarning)
+
+    # make sure this was set to None
+    assert uvf2.Nants_data == len(uvf2.ant_array)
+
+    uvf2.Nants_data = uvf.Nants_data
+    # verify no other elements were changed
+    assert uvf.__eq__(uvf2, check_history=True)
+
+
+@uvtest.skipIf_no_h5py
+def test_read_missing_nspws():
+    # redundant skip definition but a good example
+    h5py = pytest.importorskip('h5py')
+    uv = UVCal()
+    uv.read_calfits(test_c_file)
+    uvf = UVFlag(uv, mode='flag', label='test')
+    uvf.write(test_outfile, clobber=True)
+
+    with h5py.File(test_outfile, 'a') as h5:
+        del h5['Header/Nspws']
+
+    uvf2 = UVFlag(test_outfile)
+    # make sure Nspws was calculated
+    assert uvf2.Nspws == 1
+
+    # verify no other elements were changed
     assert uvf.__eq__(uvf2, check_history=True)
 
 
@@ -231,9 +458,6 @@ def test_read_write_nocompress():
     uvf = UVFlag(uv, label='test')
     uvf.write(test_outfile, clobber=True, data_compression=None)
     uvf2 = UVFlag(test_outfile)
-    # Update history to match expected additions that were made
-    uvf.history += 'Written by ' + pyuvdata_version_str
-    uvf.history += ' Read by ' + pyuvdata_version_str
     assert uvf.__eq__(uvf2, check_history=True)
 
 
@@ -244,9 +468,6 @@ def test_read_write_nocompress_flag():
     uvf = UVFlag(uv, mode='flag', label='test')
     uvf.write(test_outfile, clobber=True, data_compression=None)
     uvf2 = UVFlag(test_outfile)
-    # Update history to match expected additions that were made
-    uvf.history += 'Written by ' + pyuvdata_version_str
-    uvf.history += ' Read by ' + pyuvdata_version_str
     assert uvf.__eq__(uvf2, check_history=True)
 
 
@@ -304,7 +525,9 @@ def test_read_list():
 
 @uvtest.skipIf_no_h5py
 def test_read_error():
-    pytest.raises(IOError, UVFlag, 'foo')
+    with pytest.raises(IOError) as cm:
+        UVFlag('foo')
+    assert str(cm.value).startswith('foo not found')
 
 
 @uvtest.skipIf_no_h5py
@@ -317,15 +540,21 @@ def test_read_change_type():
     uvf.write(test_outfile, clobber=True)
     assert hasattr(uvf, 'ant_array')
     uvf.read(test_f_file)
-    assert not hasattr(uvf, 'ant_array')
+
+    # clear sets these to None now
+    assert hasattr(uvf, 'ant_array')
+    assert uvf.ant_array is None
     assert hasattr(uvf, 'baseline_array')
     assert hasattr(uvf, 'ant_1_array')
     assert hasattr(uvf, 'ant_2_array')
     uvf.read(test_outfile)
     assert hasattr(uvf, 'ant_array')
-    assert not hasattr(uvf, 'baseline_array')
-    assert not hasattr(uvf, 'ant_1_array')
-    assert not hasattr(uvf, 'ant_2_array')
+    assert hasattr(uvf, 'baseline_array')
+    assert uvf.baseline_array is None
+    assert hasattr(uvf, 'ant_1_array')
+    assert uvf.ant_1_array is None
+    assert hasattr(uvf, 'ant_2_array')
+    assert uvf.ant_2_array is None
 
 
 @uvtest.skipIf_no_h5py
@@ -334,20 +563,25 @@ def test_read_change_mode():
     uv.read_miriad(test_d_file)
     uvf = UVFlag(uv, mode='flag')
     assert hasattr(uvf, 'flag_array')
-    assert not hasattr(uvf, 'metric_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     uvf.write(test_outfile, clobber=True)
     uvf.read(test_f_file)
     assert hasattr(uvf, 'metric_array')
-    assert not hasattr(uvf, 'flag_array')
+    assert hasattr(uvf, 'flag_array')
+    assert uvf.flag_array is None
     uvf.read(test_outfile)
     assert hasattr(uvf, 'flag_array')
-    assert not hasattr(uvf, 'metric_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
 
 
 @uvtest.skipIf_no_h5py
 def test_write_no_clobber():
     uvf = UVFlag(test_f_file)
-    pytest.raises(ValueError, uvf.write, test_f_file)
+    with pytest.raises(ValueError) as cm:
+        uvf.write(test_f_file)
+    assert str(cm.value).startswith('File ' + test_f_file + ' exists;')
 
 
 def test_lst_from_uv():
@@ -358,7 +592,9 @@ def test_lst_from_uv():
 
 
 def test_lst_from_uv_error():
-    pytest.raises(ValueError, lst_from_uv, 4)
+    with pytest.raises(ValueError) as cm:
+        lst_from_uv(4)
+    assert str(cm.value).startswith('Function lst_from_uv can only operate on')
 
 
 @uvtest.skipIf_no_h5py
@@ -385,7 +621,20 @@ def test_add():
     assert uv3.type == 'baseline'
     assert uv3.mode == 'metric'
     assert np.array_equal(uv1.polarization_array, uv3.polarization_array)
-    assert 'Data combined along time axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along time axis. ' in uv3.history
+
+
+@uvtest.skipIf_no_h5py
+def test_add_add_version_str():
+    uv1 = UVFlag(test_f_file)
+    uv1.history = uv1.history.replace(pyuvdata_version_str, '')
+
+    assert pyuvdata_version_str not in uv1.history
+
+    uv2 = copy.deepcopy(uv1)
+    uv2.time_array += 1  # Add a day
+    uv3 = uv1 + uv2
+    assert pyuvdata_version_str in uv3.history
 
 
 @uvtest.skipIf_no_h5py
@@ -412,7 +661,7 @@ def test_add_baseline():
     assert uv3.type == 'baseline'
     assert uv3.mode == 'metric'
     assert np.array_equal(uv1.polarization_array, uv3.polarization_array)
-    assert 'Data combined along baseline axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along baseline axis. ' in uv3.history
 
 
 def test_add_antenna():
@@ -434,7 +683,7 @@ def test_add_antenna():
     assert uv3.type == 'antenna'
     assert uv3.mode == 'metric'
     assert np.array_equal(uv1.polarization_array, uv3.polarization_array)
-    assert 'Data combined along antenna axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along antenna axis. ' in uv3.history
 
 
 @uvtest.skipIf_no_h5py
@@ -443,7 +692,7 @@ def test_add_frequency():
     uv2 = copy.deepcopy(uv1)
     uv2.freq_array += 1e4  # Arbitrary
     uv3 = uv1.__add__(uv2, axis='frequency')
-    assert np.array_equal(np.concatenate((uv1.freq_array, uv2.freq_array)),
+    assert np.array_equal(np.concatenate((uv1.freq_array, uv2.freq_array), axis=-1),
                           uv3.freq_array)
     assert np.array_equal(uv1.time_array, uv3.time_array)
     assert np.array_equal(uv1.baseline_array, uv3.baseline_array)
@@ -457,7 +706,7 @@ def test_add_frequency():
     assert uv3.type == 'baseline'
     assert uv3.mode == 'metric'
     assert np.array_equal(uv1.polarization_array, uv3.polarization_array)
-    assert 'Data combined along frequency axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along frequency axis. ' in uv3.history
 
 
 @uvtest.skipIf_no_h5py
@@ -480,7 +729,7 @@ def test_add_pol():
     assert uv3.mode == 'metric'
     assert np.array_equal(np.concatenate((uv1.polarization_array, uv2.polarization_array)),
                           uv3.polarization_array)
-    assert 'Data combined along polarization axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along polarization axis. ' in uv3.history
 
 
 def test_add_flag():
@@ -508,7 +757,7 @@ def test_add_flag():
     assert uv3.type == 'baseline'
     assert uv3.mode == 'flag'
     assert np.array_equal(uv1.polarization_array, uv3.polarization_array)
-    assert 'Data combined along time axis with ' + pyuvdata_version_str in uv3.history
+    assert 'Data combined along time axis. ' in uv3.history
 
 
 def test_add_errors():
@@ -518,16 +767,30 @@ def test_add_errors():
     uvc.read_calfits(test_c_file)
     uv1 = UVFlag(uv)
     # Mismatched classes
-    pytest.raises(ValueError, uv1.__add__, 3)
+    with pytest.raises(ValueError) as cm:
+        uv1.__add__(3)
+    assert str(cm.value).startswith('Only UVFlag objects can be added to a UVFlag object')
+
     # Mismatched types
     uv2 = UVFlag(uvc)
-    pytest.raises(ValueError, uv1.__add__, uv2)
+    with pytest.raises(ValueError) as cm:
+        uv1.__add__(uv2)
+    assert str(cm.value).startswith('UVFlag object of type ')
+
     # Mismatched modes
     uv3 = UVFlag(uv, mode='flag')
-    pytest.raises(ValueError, uv1.__add__, uv3)
+    with pytest.raises(ValueError) as cm:
+        uv1.__add__(uv3)
+    assert str(cm.value).startswith('UVFlag object of mode ')
+
     # Invalid axes
-    pytest.raises(ValueError, uv1.__add__, uv1, axis='antenna')
-    pytest.raises(ValueError, uv2.__add__, uv2, axis='baseline')
+    with pytest.raises(ValueError) as cm:
+        uv1.__add__(uv1, axis='antenna')
+    assert str(cm.value).endswith('concatenated along antenna axis.')
+
+    with pytest.raises(ValueError) as cm:
+        uv2.__add__(uv2, axis='baseline')
+    assert str(cm.value).endswith('concatenated along baseline axis.')
 
 
 @uvtest.skipIf_no_h5py
@@ -547,24 +810,34 @@ def test_clear_unused_attributes():
     assert hasattr(uv, 'ant_1_array')
     assert hasattr(uv, 'ant_2_array')
     assert hasattr(uv, 'Nants_telescope')
-    uv.type = 'antenna'
+    uv._set_type_antenna()
     uv.clear_unused_attributes()
-    assert not hasattr(uv, 'baseline_array')
-    assert not hasattr(uv, 'ant_1_array')
-    assert not hasattr(uv, 'ant_2_array')
-    assert not hasattr(uv, 'Nants_telescope')
-    uv.mode = 'flag'
+    # clear_unused_attributes now sets these to None
+    print(uv._baseline_array.required)
+    assert hasattr(uv, 'baseline_array')
+    assert uv.baseline_array is None
+    assert hasattr(uv, 'ant_1_array')
+    assert uv.ant_1_array is None
+    assert hasattr(uv, 'ant_2_array')
+    assert uv.ant_2_array is None
+    assert hasattr(uv, 'Nants_telescope')
+    assert uv.Nants_telescope is None
+
+    uv._set_mode_flag()
     assert hasattr(uv, 'metric_array')
     uv.clear_unused_attributes()
-    assert not hasattr(uv, 'metric_array')
+    assert hasattr(uv, 'metric_array')
+    assert uv.metric_array is None
 
     # Start over
     uv = UVFlag(test_f_file)
     uv.ant_array = np.array([4])
     uv.flag_array = np.array([5])
     uv.clear_unused_attributes()
-    assert not hasattr(uv, 'ant_array')
-    assert not hasattr(uv, 'flag_array')
+    assert hasattr(uv, 'ant_array')
+    assert uv.ant_array is None
+    assert hasattr(uv, 'flag_array')
+    assert uv.flag_array is None
 
 
 @uvtest.skipIf_no_h5py
@@ -601,6 +874,17 @@ def test_to_waterfall_bl():
     assert uvf.weights_array.shape == uvf.metric_array.shape
 
 
+@uvtest.skipIf_no_h5py
+def test_to_waterfall_add_version_str():
+    uvf = UVFlag(test_f_file)
+    uvf.weights_array = np.ones_like(uvf.weights_array)
+
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+    uvf.to_waterfall()
+    assert pyuvdata_version_str in uvf.history
+
+
 @uvtest.skipIf_no_h5py
 def test_to_waterfall_bl_multi_pol():
     uvf = UVFlag(test_f_file)
@@ -621,7 +905,7 @@ def test_to_waterfall_bl_multi_pol():
     assert uvf2.metric_array.shape == (len(uvf2.time_array), len(uvf.freq_array), 1)
     assert uvf2.weights_array.shape == uvf2.metric_array.shape
     assert len(uvf2.polarization_array) == 1
-    assert uvf2.polarization_array[0] == np.string_(','.join(map(str, uvf.polarization_array)))
+    assert uvf2.polarization_array[0] == np.str_(','.join(map(str, uvf.polarization_array)))
 
 
 @uvtest.skipIf_no_h5py
@@ -634,10 +918,11 @@ def test_collapse_pol():
     uvf2 = uvf.copy()
     uvf2.collapse_pol()
     assert len(uvf2.polarization_array) == 1
-    assert uvf2.polarization_array[0] == np.string_(','.join(map(str, uvf.polarization_array)))
+    assert uvf2.polarization_array[0] == np.str_(','.join(map(str, uvf.polarization_array)))
     assert uvf2.mode == 'metric'
     assert hasattr(uvf2, 'metric_array')
-    assert not hasattr(uvf2, 'flag_array')
+    assert hasattr(uvf2, 'flag_array')
+    assert uvf2.flag_array is None
 
     # test writing it out and reading in to make sure polarization_array has correct type
     uvf2.write(test_outfile, clobber=True)
@@ -657,10 +942,30 @@ def test_collapse_pol_or():
     uvf2 = uvf.copy()
     uvf2.collapse_pol(method='or')
     assert len(uvf2.polarization_array) == 1
-    assert uvf2.polarization_array[0] == np.string_(','.join(map(str, uvf.polarization_array)))
+    assert uvf2.polarization_array[0] == np.str_(','.join(map(str, uvf.polarization_array)))
     assert uvf2.mode == 'flag'
     assert hasattr(uvf2, 'flag_array')
-    assert not hasattr(uvf2, 'metric_array')
+    assert hasattr(uvf2, 'metric_array')
+    assert uvf2.metric_array is None
+
+
+@uvtest.skipIf_no_h5py
+def test_collapse_pol_add_version_str():
+    uvf = UVFlag(test_f_file)
+    uvf.to_flag()
+    uvf.weights_array = np.ones_like(uvf.weights_array)
+
+    uvf2 = uvf.copy()
+    uvf2.polarization_array[0] = -4
+    uvf.__add__(uvf2, inplace=True, axis='pol')  # Concatenate to form multi-pol object
+
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+
+    uvf2 = uvf.copy()
+    uvf2.collapse_pol(method='or')
+
+    assert pyuvdata_version_str in uvf2.history
 
 
 @uvtest.skipIf_no_h5py
@@ -684,10 +989,11 @@ def test_collapse_pol_flag():
     uvf2 = uvf.copy()
     uvf2.collapse_pol()
     assert len(uvf2.polarization_array) == 1
-    assert uvf2.polarization_array[0] == np.string_(','.join(map(str, uvf.polarization_array)))
+    assert uvf2.polarization_array[0] == np.str_(','.join(map(str, uvf.polarization_array)))
     assert uvf2.mode == 'metric'
     assert hasattr(uvf2, 'metric_array')
-    assert not hasattr(uvf2, 'flag_array')
+    assert hasattr(uvf2, 'flag_array')
+    assert uvf2.flag_array is None
 
 
 @uvtest.skipIf_no_h5py
@@ -796,6 +1102,21 @@ def test_to_baseline_metric():
                       (3.2 * nt0 + 2.1 * nt1) / uvf.metric_array.size)
 
 
+def test_to_baseline_add_version_str():
+    uv = UVData()
+    uv.read_miriad(test_d_file)
+    uvf = UVFlag(uv)
+    uvf.to_waterfall()
+    uvf.metric_array[0, 10, 0] = 3.2  # Fill in time0, chan10
+    uvf.metric_array[1, 15, 0] = 2.1  # Fill in time1, chan15
+
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+
+    uvf.to_baseline(uv)
+    assert pyuvdata_version_str in uvf.history
+
+
 def test_baseline_to_baseline():
     uv = UVData()
     uv.read_miriad(test_d_file)
@@ -813,16 +1134,27 @@ def test_to_baseline_errors():
     uv.read_miriad(test_d_file)
     uvf = UVFlag(test_f_file)
     uvf.to_waterfall()
-    pytest.raises(ValueError, uvf.to_baseline, 7.3)  # invalid matching object
+    with pytest.raises(ValueError) as cm:
+        uvf.to_baseline(7.3)  # invalid matching object
+    assert str(cm.value).startswith('Must pass in UVData object or UVFlag object')
+
     uvf = UVFlag(uvc)
-    pytest.raises(ValueError, uvf.to_baseline, uv)  # Cannot pass in antenna type
+    with pytest.raises(ValueError) as cm:
+        uvf.to_baseline(uv)  # Cannot pass in antenna type
+    assert str(cm.value).startswith('Cannot convert from type "'
+                                    + uvf.type + '" to "baseline"')
     uvf = UVFlag(test_f_file)
     uvf.to_waterfall()
     uvf2 = uvf.copy()
     uvf.polarization_array[0] = -4
-    pytest.raises(ValueError, uvf.to_baseline, uv)  # Mismatched pols
+    with pytest.raises(ValueError) as cm:
+        uvf.to_baseline(uv)  # Mismatched pols
+    assert str(cm.value).startswith('Polarizations do not match.')
     uvf.__iadd__(uvf2, axis='polarization')
-    pytest.raises(ValueError, uvf.to_baseline, uv)  # Mismatched pols, can't be forced
+
+    with pytest.raises(ValueError) as cm:
+        uvf.to_baseline(uv)  # Mismatched pols, can't be forced
+    assert str(cm.value).startswith('Polarizations could not be made to match.')
 
 
 def test_to_baseline_force_pol():
@@ -889,6 +1221,21 @@ def test_to_antenna_flags():
     assert uvf.flag_array.mean() == 2. * uvc.Nants_data / uvf.flag_array.size
 
 
+def test_to_antenna_add_version_str():
+    uvc = UVCal()
+    uvc.read_calfits(test_c_file)
+    uvf = UVFlag(uvc)
+    uvf.to_waterfall()
+    uvf.to_flag()
+    uvf.flag_array[0, 10, 0] = True  # Flag time0, chan10
+    uvf.flag_array[1, 15, 0] = True  # Flag time1, chan15
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+
+    uvf.to_antenna(uvc)
+    assert pyuvdata_version_str in uvf.history
+
+
 def test_to_antenna_metric():
     uvc = UVCal()
     uvc.read_calfits(test_c_file)
@@ -939,16 +1286,27 @@ def test_to_antenna_errors():
     uv.read_miriad(test_d_file)
     uvf = UVFlag(test_f_file)
     uvf.to_waterfall()
-    pytest.raises(ValueError, uvf.to_antenna, 7.3)  # invalid matching object
+    with pytest.raises(ValueError) as cm:
+        uvf.to_antenna(7.3)  # invalid matching object
+    assert str(cm.value).startswith('Must pass in UVCal object or UVFlag object ')
+
     uvf = UVFlag(uv)
-    pytest.raises(ValueError, uvf.to_antenna, uvc)  # Cannot pass in baseline type
+    with pytest.raises(ValueError) as cm:
+        uvf.to_antenna(uvc)  # Cannot pass in baseline type
+    assert str(cm.value).startswith('Cannot convert from type "baseline" to "antenna".')
+
     uvf = UVFlag(test_f_file)
     uvf.to_waterfall()
     uvf2 = uvf.copy()
     uvf.polarization_array[0] = -4
-    pytest.raises(ValueError, uvf.to_antenna, uvc)  # Mismatched pols
+    with pytest.raises(ValueError) as cm:
+        uvf.to_antenna(uvc)  # Mismatched pols
+    assert str(cm.value).startswith('Polarizations do not match. ')
+
     uvf.__iadd__(uvf2, axis='polarization')
-    pytest.raises(ValueError, uvf.to_antenna, uvc)  # Mismatched pols, can't be forced
+    with pytest.raises(ValueError) as cm:
+        uvf.to_antenna(uvc)  # Mismatched pols, can't be forced
+    assert str(cm.value).startswith('Polarizations could not be made to match.')
 
 
 def test_to_antenna_force_pol():
@@ -1014,12 +1372,31 @@ def test_or():
     assert np.all(uvf3.flag_array[2:])
 
 
+@uvtest.skipIf_no_h5py
+def test_or_add_version_str():
+    uvf = UVFlag(test_f_file)
+    uvf.to_flag()
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+
+    assert pyuvdata_version_str not in uvf.history
+    uvf2 = uvf.copy()
+    uvf2.flag_array = np.ones_like(uvf2.flag_array)
+    uvf.flag_array[0] = True
+    uvf2.flag_array[0] = False
+    uvf2.flag_array[1] = False
+    uvf3 = uvf | uvf2
+
+    assert pyuvdata_version_str in uvf3.history
+
+
 @uvtest.skipIf_no_h5py
 def test_or_error():
     uvf = UVFlag(test_f_file)
     uvf2 = uvf.copy()
     uvf.to_flag()
-    pytest.raises(ValueError, uvf.__or__, uvf2)
+    with pytest.raises(ValueError) as cm:
+        uvf.__or__(uvf2)
+    assert str(cm.value).startswith('UVFlag object must be in "flag" mode')
 
 
 @uvtest.skipIf_no_h5py
@@ -1054,11 +1431,22 @@ def test_to_flag():
     uvf = UVFlag(test_f_file)
     uvf.to_flag()
     assert hasattr(uvf, 'flag_array')
-    assert not hasattr(uvf, 'metric_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     assert uvf.mode == 'flag'
     assert 'Converted to mode "flag"' in uvf.history
 
 
+@uvtest.skipIf_no_h5py
+def test_to_flag_add_version_str():
+    uvf = UVFlag(test_f_file)
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+
+    uvf.to_flag()
+    assert pyuvdata_version_str in uvf.history
+
+
 @uvtest.skipIf_no_h5py
 def test_to_flag_threshold():
     uvf = UVFlag(test_f_file)
@@ -1066,7 +1454,8 @@ def test_to_flag_threshold():
     uvf.metric_array[0, 0, 4, 0] = 2.
     uvf.to_flag(threshold=1.)
     assert hasattr(uvf, 'flag_array')
-    assert not hasattr(uvf, 'metric_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     assert uvf.mode == 'flag'
     assert uvf.flag_array[0, 0, 4, 0]
     assert np.sum(uvf.flag_array) == 1.
@@ -1086,7 +1475,9 @@ def test_flag_to_flag():
 def test_to_flag_unknown_mode():
     uvf = UVFlag(test_f_file)
     uvf.mode = 'foo'
-    pytest.raises(ValueError, uvf.to_flag)
+    with pytest.raises(ValueError) as cm:
+        uvf.to_flag()
+    assert str(cm.value).startswith('Unknown UVFlag mode: foo')
 
 
 @uvtest.skipIf_no_h5py
@@ -1096,17 +1487,37 @@ def test_to_metric_baseline():
     uvf.flag_array[:, :, 10] = True
     uvf.flag_array[1, :, :] = True
     assert hasattr(uvf, 'flag_array')
-    assert not hasattr(uvf, 'metric_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
     assert uvf.mode == 'flag'
     uvf.to_metric(convert_wgts=True)
     assert hasattr(uvf, 'metric_array')
-    assert not hasattr(uvf, 'flag_array')
+    assert hasattr(uvf, 'flag_array')
+    assert uvf.flag_array is None
     assert uvf.mode == 'metric'
     assert 'Converted to mode "metric"' in uvf.history
     assert np.isclose(uvf.weights_array[1], 0.0).all()
     assert np.isclose(uvf.weights_array[:, :, 10], 0.0).all()
 
 
+@uvtest.skipIf_no_h5py
+def test_to_metric_add_version_str():
+    uvf = UVFlag(test_f_file)
+    uvf.to_flag()
+    uvf.flag_array[:, :, 10] = True
+    uvf.flag_array[1, :, :] = True
+    assert hasattr(uvf, 'flag_array')
+    assert hasattr(uvf, 'metric_array')
+    assert uvf.metric_array is None
+    assert uvf.mode == 'flag'
+
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+    assert pyuvdata_version_str not in uvf.history
+
+    uvf.to_metric(convert_wgts=True)
+    assert pyuvdata_version_str in uvf.history
+
+
 @uvtest.skipIf_no_h5py
 def test_to_metric_waterfall():
     uvf = UVFlag(test_f_file)
@@ -1142,7 +1553,9 @@ def test_metric_to_metric():
 def test_to_metric_unknown_mode():
     uvf = UVFlag(test_f_file)
     uvf.mode = 'foo'
-    pytest.raises(ValueError, uvf.to_metric)
+    with pytest.raises(ValueError) as cm:
+        uvf.to_metric()
+    assert str(cm.value).startswith('Unknown UVFlag mode: foo')
 
 
 @uvtest.skipIf_no_h5py
@@ -1157,7 +1570,11 @@ def test_antpair2ind():
 def test_antpair2ind_nonbaseline():
     uvf = UVFlag(test_f_file)
     uvf.to_waterfall()
-    pytest.raises(ValueError, uvf.antpair2ind, 0, 3)
+    with pytest.raises(ValueError) as cm:
+        uvf.antpair2ind(0, 3)
+    assert str(cm.value).startswith('UVFlag object of type ' + uvf.type
+                                    + ' does not contain antenna '
+                                    + 'pairs to index.')
 
 
 @uvtest.skipIf_no_h5py
@@ -1196,7 +1613,7 @@ def test_missing_Nants_telescope():
     with h5py.File(testfile, 'r+') as f:
         del(f['/Header/Nants_telescope'])
     uvf = uvtest.checkWarnings(UVFlag, [testfile], {}, nwarnings=1,
-                               message='Nants_telescope not availabe')
+                               message=['Nants_telescope not available in file,'])
     uvf2 = UVFlag(test_f_file)
     uvf2.Nants_telescope = None
     assert uvf == uvf2
@@ -1239,7 +1656,9 @@ def test_combine_metrics_not_uvflag():
     uvc = UVCal()
     uvc.read_calfits(test_c_file)
     uvf = UVFlag(uvc)
-    pytest.raises(ValueError, uvf.combine_metrics, 'bubblegum')
+    with pytest.raises(ValueError) as cm:
+        uvf.combine_metrics('bubblegum')
+    assert str(cm.value).startswith('"others" must be UVFlag or list of UVFlag objects')
 
 
 def test_combine_metrics_not_metric():
@@ -1250,7 +1669,9 @@ def test_combine_metrics_not_metric():
     uvf.metric_array = np.random.normal(size=uvf.metric_array.shape)
     uvf2 = uvf.copy()
     uvf2.to_flag()
-    pytest.raises(ValueError, uvf.combine_metrics, uvf2)
+    with pytest.raises(ValueError) as cm:
+        uvf.combine_metrics(uvf2)
+    assert str(cm.value).startswith('UVFlag object and "others" must be in "metric"')
 
 
 def test_combine_metrics_wrong_shape():
@@ -1261,7 +1682,27 @@ def test_combine_metrics_wrong_shape():
     uvf.metric_array = np.random.normal(size=uvf.metric_array.shape)
     uvf2 = uvf.copy()
     uvf2.to_waterfall()
-    pytest.raises(ValueError, uvf.combine_metrics, uvf2)
+    with pytest.raises(ValueError) as cm:
+        uvf.combine_metrics(uvf2)
+    assert str(cm.value).startswith('UVFlag metric array shapes do not match.')
+
+
+def test_combine_metrics_add_version_str():
+    uvc = UVCal()
+    uvc.read_calfits(test_c_file)
+    uvf = UVFlag(uvc)
+    uvf.history = uvf.history.replace(pyuvdata_version_str, '')
+
+    assert pyuvdata_version_str not in uvf.history
+    np.random.seed(44)
+    uvf.metric_array = np.random.normal(size=uvf.metric_array.shape)
+    uvf2 = uvf.copy()
+    uvf2.metric_array *= 2
+    uvf3 = uvf.copy()
+    uvf3.metric_array *= 3
+    uvf4 = uvf.combine_metrics([uvf2, uvf3], inplace=False)
+
+    assert pyuvdata_version_str in uvf4.history
 
 
 def test_super():
@@ -1324,12 +1765,17 @@ def test_flags2waterfall():
 def test_flags2waterfall_errors():
 
     # First argument must be UVData or UVCal object
-    pytest.raises(ValueError, flags2waterfall, 5)
+    with pytest.raises(ValueError) as cm:
+        flags2waterfall(5)
+    assert str(cm.value).startswith('flags2waterfall() requires a UVData or '
+                                    + 'UVCal object')
 
     uv = UVData()
     uv.read_miriad(test_d_file)
     # Flag array must have same shape as uv.flag_array
-    pytest.raises(ValueError, flags2waterfall, uv, np.array([4, 5]))
+    with pytest.raises(ValueError) as cm:
+        flags2waterfall(uv, np.array([4, 5]))
+    assert str(cm.value).startswith('Flag array must align with UVData or UVCal')
 
 
 def test_and_rows_cols():
@@ -1344,3 +1790,660 @@ def test_and_rows_cols():
     assert o[:, 2].all()
     assert not o[5, :].all()
     assert not o[:, 5].all()
+
+
+def test_select_waterfall_errors(uvf_from_waterfall):
+    uvf = uvf_from_waterfall
+    with pytest.raises(ValueError) as cm:
+        uvf.select(antenna_nums=[0, 1, 2])
+    assert str(cm.value).startswith('Cannot select on antenna_nums with waterfall')
+
+    with pytest.raises(ValueError) as cm:
+        uvf.select(bls=[(0, 1), (0, 2)])
+    assert str(cm.value).startswith('Cannot select on bls with waterfall')
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_blt_inds(input_uvf, uvf_mode):
+    uvf = input_uvf
+
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    blt_inds = np.random.choice(uvf.Nblts, size=uvf.Nblts // 2, replace=False)
+    new_nblts = uvf.Nblts // 2
+    uvf1 = uvf.select(blt_inds=blt_inds, inplace=False)
+
+    # test the data was extracted correctly for each case
+    for param_name, new_param in zip(uvf._data_params, uvf1.data_like_parameters):
+        old_param = getattr(uvf, param_name)
+        if uvf.type == "baseline":
+            assert np.allclose(old_param[blt_inds], new_param)
+        if uvf.type == "antenna":
+            assert np.allclose(old_param[:, :, :, blt_inds], new_param)
+        if uvf.type == "waterfall":
+            assert np.allclose(old_param[blt_inds], new_param)
+
+    assert uvf1.Nblts == new_nblts
+    # verify that histories are different
+    assert not uvutils._check_histories(uvf.history, uvf1.history)
+
+    assert uvutils._check_histories(uvf.history + '  Downselected to '
+                                    'specific baseline-times using pyuvdata.',
+                                    uvf1.history)
+
+    # test works with higher dimension arrays:
+    uvf1 = uvf.select(blt_inds=np.atleast_3d(blt_inds), inplace=False)
+
+    # test the data was extraced
+    # assert np.allclose(uvf.metric_array[blt_inds], uvf1.metric_array)
+    assert uvf1.Nblts == new_nblts
+    assert 'baseline-times' in uvf1.history
+    # verify that histories are different
+    assert not uvutils._check_histories(uvf.history, uvf1.history)
+
+    assert uvutils._check_histories(uvf.history + '  Downselected to '
+                                    'specific baseline-times using pyuvdata.',
+                                    uvf1.history)
+
+    # test the error modes of blt_inds
+    with pytest.raises(ValueError) as cm:
+        uvf.select(blt_inds=[])
+    assert str(cm.value).startswith('No baseline-times were found')
+
+    with pytest.raises(ValueError) as cm:
+        uvf.select(blt_inds=[np.max(uvf.Nblts) + 1])
+    assert str(cm.value).startswith('blt_inds contains indices that are too large')
+
+    with pytest.raises(ValueError) as cm:
+        uvf.select(blt_inds=[-1])
+    assert str(cm.value).startswith('blt_inds contains indices that are negative')
+
+
+@cases_decorator_no_waterfall
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_antenna_nums(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+
+    old_history = copy.deepcopy(uvf.history)
+    np.random.seed(0)
+    if uvf.type == "baseline":
+        unique_ants = np.unique(uvf.ant_1_array.tolist()
+                                + uvf.ant_2_array.tolist())
+        ants_to_keep = np.random.choice(unique_ants,
+                                        size=unique_ants.size // 2,
+                                        replace=False)
+
+        blts_select = [(a1 in ants_to_keep) & (a2 in ants_to_keep) for (a1, a2) in
+                       zip(uvf.ant_1_array, uvf.ant_2_array)]
+        Nblts_selected = np.sum(blts_select)
+    else:
+        unique_ants = np.unique(uvf.ant_array)
+        ants_to_keep = np.random.choice(unique_ants,
+                                        size=unique_ants.size // 2,
+                                        replace=False)
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(antenna_nums=ants_to_keep)
+
+    assert len(ants_to_keep) == uvf2.Nants_data
+    if uvf2.type == "baseline":
+        assert Nblts_selected == uvf2.Nblts
+        for ant in ants_to_keep:
+            assert ant in uvf2.ant_1_array or ant in uvf2.ant_2_array
+        for ant in np.unique(uvf2.ant_1_array.tolist()
+                             + uvf2.ant_2_array.tolist()):
+            assert ant in ants_to_keep
+    else:
+        for ant in ants_to_keep:
+            assert ant in uvf2.ant_array
+        for ant in np.unique(uvf2.ant_array):
+            assert ant in ants_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific antennas using pyuvdata.',
+                                    uvf2.history)
+
+    # check that it also works with higher dimension array
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(antenna_nums=np.atleast_3d(ants_to_keep))
+
+    assert len(ants_to_keep) == uvf2.Nants_data
+    assert len(ants_to_keep) == uvf2.Nants_data
+    if uvf2.type == "baseline":
+        assert Nblts_selected == uvf2.Nblts
+        for ant in ants_to_keep:
+            assert ant in uvf2.ant_1_array or ant in uvf2.ant_2_array
+        for ant in np.unique(uvf2.ant_1_array.tolist()
+                             + uvf2.ant_2_array.tolist()):
+            assert ant in ants_to_keep
+    else:
+        for ant in ants_to_keep:
+            assert ant in uvf2.ant_array
+        for ant in np.unique(uvf2.ant_array):
+            assert ant in ants_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific antennas using pyuvdata.',
+                                    uvf2.history)
+
+    # also test for error if antenna numbers not present in data
+    with pytest.raises(ValueError) as cm:
+        uvf.select(antenna_nums=np.max(unique_ants) + np.arange(1, 3))
+    assert str(cm.value).startswith('Antenna number '
+                                    '{a} is not present'.format(a=np.max(unique_ants) + 1))
+
+
+def sort_bl(p):
+    """Sort a tuple that starts with a pair of antennas, and may have stuff after."""
+    if p[1] >= p[0]:
+        return p
+    return (p[1], p[0]) + p[2:]
+
+
+@cases_decorator_no_waterfall
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_bls(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+
+    if uvf.type != "baseline":
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=[(0, 1)])
+        assert str(cm.value).startswith('Only "baseline" mode UVFlag '
+                                        'objects may select along the '
+                                        'baseline axis')
+    else:
+        old_history = copy.deepcopy(uvf.history)
+        bls_select = np.random.choice(uvf.baseline_array,
+                                      size=uvf.Nbls // 2,
+                                      replace=False)
+        first_ants, second_ants = uvf.baseline_to_antnums(bls_select)
+
+        # give the conjugate bls for a few baselines
+        first_ants[5:8], second_ants[5:8] = copy.copy(second_ants[5:8]), copy.copy(first_ants[5:8])
+
+        new_unique_ants = np.unique(first_ants.tolist() + second_ants.tolist())
+        ant_pairs_to_keep = list(zip(first_ants, second_ants))
+        sorted_pairs_to_keep = [sort_bl(p) for p in ant_pairs_to_keep]
+
+        blts_select = [sort_bl((a1, a2)) in sorted_pairs_to_keep for (a1, a2) in
+                       zip(uvf.ant_1_array, uvf.ant_2_array)]
+        Nblts_selected = np.sum(blts_select)
+
+        uvf2 = copy.deepcopy(uvf)
+        uvf2.select(bls=ant_pairs_to_keep)
+        sorted_pairs_object2 = [sort_bl(p) for p in zip(
+            uvf2.ant_1_array, uvf2.ant_2_array)]
+
+        assert len(new_unique_ants) == uvf2.Nants_data
+        assert Nblts_selected == uvf2.Nblts
+        for ant in new_unique_ants:
+            assert ant in uvf2.ant_1_array or ant in uvf2.ant_2_array
+        for ant in np.unique(uvf2.ant_1_array.tolist() + uvf2.ant_2_array.tolist()):
+            assert ant in new_unique_ants
+        for pair in sorted_pairs_to_keep:
+            assert pair in sorted_pairs_object2
+        for pair in sorted_pairs_object2:
+            assert pair in sorted_pairs_to_keep
+
+        assert uvutils._check_histories(old_history + '  Downselected to '
+                                        'specific baselines using pyuvdata.',
+                                        uvf2.history)
+
+        # Check with polarization too
+        first_ants, second_ants = uvf.baseline_to_antnums(bls_select)
+        # conjugate a few bls
+        first_ants[5:8], second_ants[5:8] = copy.copy(second_ants[5:8]), copy.copy(first_ants[5:8])
+
+        pols = ['xx'] * len(first_ants)
+
+        new_unique_ants = np.unique(first_ants.tolist() + second_ants.tolist())
+        ant_pairs_to_keep = list(zip(first_ants, second_ants, pols))
+        sorted_pairs_to_keep = [sort_bl(p) for p in ant_pairs_to_keep]
+
+        blts_select = [sort_bl((a1, a2, 'xx')) in sorted_pairs_to_keep for (a1, a2) in
+                       zip(uvf.ant_1_array, uvf.ant_2_array)]
+        Nblts_selected = np.sum(blts_select)
+
+        uvf2 = copy.deepcopy(uvf)
+
+        uvf2.select(bls=ant_pairs_to_keep)
+        sorted_pairs_object2 = [sort_bl(p) + ('xx',) for p in zip(
+            uvf2.ant_1_array, uvf2.ant_2_array)]
+
+        assert len(new_unique_ants) == uvf2.Nants_data
+        assert Nblts_selected == uvf2.Nblts
+        for ant in new_unique_ants:
+            assert ant in uvf2.ant_1_array or ant in uvf2.ant_2_array
+        for ant in np.unique(uvf2.ant_1_array.tolist() + uvf2.ant_2_array.tolist()):
+            assert ant in new_unique_ants
+        for pair in sorted_pairs_to_keep:
+            assert pair in sorted_pairs_object2
+        for pair in sorted_pairs_object2:
+            assert pair in sorted_pairs_to_keep
+
+        assert uvutils._check_histories(old_history + '  Downselected to '
+                                        'specific baselines, polarizations using pyuvdata.',
+                                        uvf2.history)
+
+        # check that you can specify a single pair without errors
+        assert isinstance(ant_pairs_to_keep[0], tuple)
+        uvf2.select(bls=ant_pairs_to_keep[0])
+        sorted_pairs_object2 = [sort_bl(p) + ('xx', ) for p in zip(
+            uvf2.ant_1_array, uvf2.ant_2_array)]
+        assert list(set(sorted_pairs_object2)) == [ant_pairs_to_keep[0]]
+
+        # test some error modes
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=[3])
+        assert str(cm.value).startswith('bls must be a list of tuples')
+
+        # must be integers
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=[(np.pi, 2 * np.pi)])
+        assert str(cm.value).startswith('bls must be a list of tuples of integer')
+
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=(0, 1, 'xx'), polarizations=[-5])
+        assert str(cm.value).startswith('Cannot provide length-3 tuples and also specify polarizations.')
+
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=(0, 1, 5))
+        assert str(cm.value).startswith('The third element in each bl must be a polarization string')
+
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=(455, 456))
+        assert str(cm.value).startswith('Antenna number 455 is not present')
+
+        with pytest.raises(ValueError) as cm:
+            uvf.select(bls=(first_ants[0], 456))
+        assert str(cm.value).startswith('Antenna number 456 is not present')
+
+        uvf2 = copy.deepcopy(uvf)
+        uvf2.select(bls=[(97, 104), (97, 105), (88, 97)])
+        with pytest.raises(ValueError) as cm:
+            uvf2.select(bls=(97, 97))
+        assert str(cm.value).startswith("Antenna pair (97, 97) does not have any")
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_times(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    old_history = uvf.history
+    unique_times = np.unique(uvf.time_array)
+    times_to_keep = np.random.choice(unique_times, size=unique_times.size // 2,
+                                     replace=False)
+
+    Nblts_selected = np.sum([t in times_to_keep for t in uvf.time_array])
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(times=times_to_keep)
+
+    assert len(times_to_keep) == uvf2.Ntimes
+    assert Nblts_selected == uvf2.Nblts
+    for t in times_to_keep:
+        assert t in uvf2.time_array
+    for t in np.unique(uvf2.time_array):
+        assert t in times_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific times using pyuvdata.',
+                                    uvf2.history)
+    # check that it also works with higher dimension array
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(times=times_to_keep[np.newaxis, :])
+
+    assert len(times_to_keep) == uvf2.Ntimes
+    assert Nblts_selected == uvf2.Nblts
+    for t in times_to_keep:
+        assert t in uvf2.time_array
+    for t in np.unique(uvf2.time_array):
+        assert t in times_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific times using pyuvdata.',
+                                    uvf2.history)
+    # check for errors associated with times not included in data
+    with pytest.raises(ValueError) as cm:
+        bad_time = [np.min(unique_times) - .005]
+        uvf.select(times=bad_time)
+    assert str(cm.value).startswith('Time {t} is not present in'
+                                    ' the time_array'.format(t=bad_time[0]))
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_frequencies(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    old_history = uvf.history
+
+    freqs_to_keep = np.random.choice(uvf.freq_array.squeeze(), size=uvf.Nfreqs // 10,
+                                     replace=False)
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(frequencies=freqs_to_keep)
+
+    assert len(freqs_to_keep) == uvf2.Nfreqs
+    for f in freqs_to_keep:
+        assert f in uvf2.freq_array
+    for f in np.unique(uvf2.freq_array):
+        assert f in freqs_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific frequencies using pyuvdata.',
+                                    uvf2.history)
+
+    # check that it also works with higher dimension array
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(frequencies=freqs_to_keep[np.newaxis, :])
+
+    assert len(freqs_to_keep) == uvf2.Nfreqs
+    for f in freqs_to_keep:
+        assert f in uvf2.freq_array
+    for f in np.unique(uvf2.freq_array):
+        assert f in freqs_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific frequencies using pyuvdata.',
+                                    uvf2.history)
+
+    # check that selecting one frequency works
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(frequencies=freqs_to_keep[0])
+    assert 1 == uvf2.Nfreqs
+    assert freqs_to_keep[0] in uvf2.freq_array
+    for f in uvf2.freq_array:
+        assert f in [freqs_to_keep[0]]
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific frequencies using pyuvdata.',
+                                    uvf2.history)
+
+    # check for errors associated with frequencies not included in data
+    with pytest.raises(ValueError) as cm:
+        bad_freq = [np.max(uvf.freq_array) + 100]
+        uvf.select(frequencies=bad_freq)
+    assert str(cm.value).startswith('Frequency {f} is not present in the freq_array'.format(f=bad_freq[0]))
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_freq_chans(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    old_history = uvf.history
+
+    old_history = uvf.history
+    chans = np.random.choice(uvf.Nfreqs, 2)
+    c1, c2 = np.sort(chans)
+    chans_to_keep = np.arange(c1, c2)
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(freq_chans=chans_to_keep)
+
+    assert len(chans_to_keep) == uvf2.Nfreqs
+    for chan in chans_to_keep:
+        if uvf2.type != "waterfall":
+            assert uvf.freq_array[0, chan] in uvf2.freq_array
+        else:
+            assert uvf.freq_array[chan] in uvf2.freq_array
+
+    for f in np.unique(uvf2.freq_array):
+        if uvf2.type != "waterfall":
+            assert f in uvf.freq_array[0, chans_to_keep]
+        else:
+            assert f in uvf.freq_array[chans_to_keep]
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific frequencies using pyuvdata.',
+                                    uvf2.history)
+
+    # check that it also works with higher dimension array
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(freq_chans=chans_to_keep[np.newaxis, :])
+
+    assert len(chans_to_keep) == uvf2.Nfreqs
+    for chan in chans_to_keep:
+        if uvf2.type != "waterfall":
+            assert uvf.freq_array[0, chan] in uvf2.freq_array
+        else:
+            assert uvf.freq_array[chan] in uvf2.freq_array
+
+    for f in np.unique(uvf2.freq_array):
+        if uvf2.type != "waterfall":
+            assert f in uvf.freq_array[0, chans_to_keep]
+        else:
+            assert f in uvf.freq_array[chans_to_keep]
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific frequencies using pyuvdata.',
+                                    uvf2.history)
+
+    # Test selecting both channels and frequencies
+    chans = np.random.choice(uvf.Nfreqs, 2)
+    c1, c2 = np.sort(chans)
+    chans_to_keep = np.arange(c1, c2)
+
+    if uvf.type != "waterfall":
+        freqs_to_keep = uvf.freq_array[0, np.arange(c1 + 1, c2)]  # Overlaps with chans
+    else:
+        freqs_to_keep = uvf.freq_array[np.arange(c1 + 1, c2)]  # Overlaps with chans
+
+    all_chans_to_keep = np.arange(c1, c2)
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(frequencies=freqs_to_keep, freq_chans=chans_to_keep)
+
+    assert len(all_chans_to_keep) == uvf2.Nfreqs
+    for chan in chans_to_keep:
+        if uvf2.type != "waterfall":
+            assert uvf.freq_array[0, chan] in uvf2.freq_array
+        else:
+            assert uvf.freq_array[chan] in uvf2.freq_array
+
+    for f in np.unique(uvf2.freq_array):
+        if uvf2.type != "waterfall":
+            assert f in uvf.freq_array[0, chans_to_keep]
+        else:
+            assert f in uvf.freq_array[chans_to_keep]
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select_polarizations(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    old_history = uvf.history
+
+    pols_to_keep = [-5]
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(polarizations=pols_to_keep)
+
+    assert len(pols_to_keep) == uvf2.Npols
+    for p in pols_to_keep:
+        assert p in uvf2.polarization_array
+    for p in np.unique(uvf2.polarization_array):
+        assert p in pols_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific polarizations using pyuvdata.',
+                                    uvf2.history)
+
+    # check that it also works with higher dimension array
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(polarizations=[pols_to_keep])
+
+    assert len(pols_to_keep) == uvf2.Npols
+    for p in pols_to_keep:
+        assert p in uvf2.polarization_array
+    for p in np.unique(uvf2.polarization_array):
+        assert p in pols_to_keep
+
+    assert uvutils._check_histories(old_history + '  Downselected to '
+                                    'specific polarizations using pyuvdata.',
+                                    uvf2.history)
+
+    # check for errors associated with polarizations not included in data
+    with pytest.raises(ValueError) as cm:
+        uvf2.select(polarizations=[-3])
+    assert str(cm.value).startswith('Polarization {p} is not present in the polarization_array'.format(p=-3))
+
+
+@cases_decorator
+@pytest.mark.parametrize("uvf_mode", ["to_flag", "to_metric"])
+def test_select(input_uvf, uvf_mode):
+    uvf = input_uvf
+    # used to set the mode depending on which input is given to uvf_mode
+    getattr(uvf, uvf_mode)()
+    np.random.seed(0)
+    old_history = uvf.history
+
+    # make new blts
+    blt_inds = np.arange(uvf.Nblts - 1)
+
+    # new freqs
+    freqs_to_keep = np.random.choice(uvf.freq_array.squeeze(),
+                                     size=uvf.Nfreqs - 1,
+                                     replace=False)
+    # new ants
+    if uvf.type == "baseline":
+        unique_ants = np.unique(uvf.ant_1_array.tolist()
+                                + uvf.ant_2_array.tolist())
+        ants_to_keep = np.random.choice(unique_ants,
+                                        size=unique_ants.size - 1,
+                                        replace=False)
+
+    elif uvf.type == "antenna":
+        unique_ants = np.unique(uvf.ant_array)
+        ants_to_keep = np.random.choice(unique_ants,
+                                        size=unique_ants.size - 1,
+                                        replace=False)
+    else:
+        ants_to_keep = None
+
+    if uvf.type == "baseline":
+        #  new bls
+        bls_select = np.random.choice(uvf.baseline_array,
+                                      size=uvf.Nbls - 1,
+                                      replace=False)
+        first_ants, second_ants = uvf.baseline_to_antnums(bls_select)
+        # give the conjugate bls for a few baselines
+        first_ants[2:4], second_ants[2:4] = second_ants[2:4], first_ants[2:4]
+
+        ant_pairs_to_keep = list(zip(first_ants, second_ants))
+        sorted_pairs_to_keep = [sort_bl(p) for p in ant_pairs_to_keep]
+
+    else:
+        ant_pairs_to_keep = None
+
+    # new times
+    unique_times = np.unique(uvf.time_array)
+    times_to_keep = np.random.choice(unique_times,
+                                     size=unique_times.size - 1,
+                                     replace=False)
+
+    # new pols
+    pols_to_keep = [-5]
+
+    # Independently count blts that should be selected
+    if uvf.type == "baseline":
+        blts_blt_select = [i in blt_inds for i in np.arange(uvf.Nblts)]
+        blts_ant_select = [(a1 in ants_to_keep) & (a2 in ants_to_keep) for (a1, a2) in
+                           zip(uvf.ant_1_array, uvf.ant_2_array)]
+        blts_pair_select = [sort_bl((a1, a2)) in sorted_pairs_to_keep for (a1, a2) in
+                            zip(uvf.ant_1_array, uvf.ant_2_array)]
+        blts_time_select = [t in times_to_keep for t in uvf.time_array]
+        Nblts_select = np.sum([bi & (ai & pi) & ti for (bi, ai, pi, ti) in
+                               zip(blts_blt_select, blts_ant_select, blts_pair_select,
+                                   blts_time_select)])
+    else:
+        blts_blt_select = [i in blt_inds for i in np.arange(uvf.Nblts)]
+        blts_time_select = [t in times_to_keep for t in uvf.time_array]
+        Nblts_select = np.sum([bi & ti for (bi, ti) in
+                               zip(blts_blt_select, blts_time_select)])
+
+    uvf2 = copy.deepcopy(uvf)
+    uvf2.select(blt_inds=blt_inds, antenna_nums=ants_to_keep,
+                bls=ant_pairs_to_keep, frequencies=freqs_to_keep,
+                times=times_to_keep, polarizations=pols_to_keep)
+
+    assert Nblts_select == uvf2.Nblts
+    if uvf.type == "baseline":
+        for ant in np.unique(uvf2.ant_1_array.tolist()
+                             + uvf2.ant_2_array.tolist()):
+            assert ant in ants_to_keep
+    elif uvf.type == "antenna":
+        for ant in np.unique(uvf2.ant_array):
+            assert ant in ants_to_keep
+
+    assert len(freqs_to_keep) == uvf2.Nfreqs
+    for f in freqs_to_keep:
+        assert f in uvf2.freq_array
+    for f in np.unique(uvf2.freq_array):
+        assert f in freqs_to_keep
+
+    for t in np.unique(uvf2.time_array):
+        assert t in times_to_keep
+
+    assert len(pols_to_keep) == uvf2.Npols
+    for p in pols_to_keep:
+        assert p in uvf2.polarization_array
+    for p in np.unique(uvf2.polarization_array):
+        assert p in pols_to_keep
+
+    if uvf.type == "baseline":
+        assert uvutils._check_histories(old_history + '  Downselected to '
+                                        'specific baseline-times, antennas, '
+                                        'baselines, times, frequencies, '
+                                        'polarizations using pyuvdata.',
+                                        uvf2.history)
+    elif uvf.type == "antenna":
+        assert uvutils._check_histories(old_history + '  Downselected to '
+                                        'specific baseline-times, antennas, '
+                                        'times, frequencies, '
+                                        'polarizations using pyuvdata.',
+                                        uvf2.history)
+    else:
+        assert uvutils._check_histories(old_history + '  Downselected to '
+                                        'specific baseline-times, '
+                                        'times, frequencies, '
+                                        'polarizations using pyuvdata.',
+                                        uvf2.history)
+
+
+def test_equality_no_history(uvf_from_miriad):
+    uvf = uvf_from_miriad
+    uvf2 = uvf.copy()
+    assert uvf.__eq__(uvf2, check_history=True)
+    uvf2.history += "different text"
+    assert uvf.__eq__(uvf2, check_history=False)
+
+
+def test_inequality_different_classes(uvf_from_miriad):
+    uvf = uvf_from_miriad
+
+    class test_class(object):
+        def __init__(self):
+            pass
+
+    other_class = test_class()
+
+    assert uvf.__ne__(other_class, check_history=False)