Repository: cjtu/craterpy (language: python)

Issue:
Fix .environment.yml on OSX
Installing from provided .environment.yml on OSX throws error; attached version works (rename to .environment.yml using mv environment.txt .environment.yml)
[environment.txt](https://github.com/cjtu/craterpy/files/1432233/environment.txt)


Investigate releasing on conda-forge 
As noted in (#8), quick installation is bulky right now due to gdal dependency issues that arise with pip that are mitigated by conda.  

1) What would it take to get craterpy hosted on conda-forge?
2) Would this simplify installation instructions  (e.g., would dependencies be correctly built with `conda install -c conda-forge craterpy`)?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/code_quality_checks.yml b/.github/workflows/code_quality_checks.yml
new file mode 100644
index 0000000..24b6666
--- /dev/null
+++ b/.github/workflows/code_quality_checks.yml
@@ -0,0 +1,45 @@
+---
+name: Code Quality Checks
+
+on:
+  push:
+    branches: [trunk]
+  pull_request:
+    branches: [trunk]
+
+jobs:
+  build:
+
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        python-version: ['3.7', '3.8']
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Display Python version
+        run: python -c "import sys; print(sys.version)"
+      - name: Install Poetry
+        uses: Gr1N/setup-poetry@v4
+      - name: Load cached venv
+        id: cached-poetry-dependencies
+        uses: actions/cache@v2
+        with:
+          path: ~/.cache/pypoetry/virtualenvs
+          key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
+      - name: Install dependencies
+        run: poetry install
+        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
+      - name: Check format with black
+        run: poetry run black --check craterpy/
+      - name: Check style with pylint
+        run: poetry run pylint craterpy/
+      - name: Unit & Coverage test with pytest
+        run: poetry run pytest --cov-report=xml
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v1
+      
diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml
new file mode 100644
index 0000000..42a5008
--- /dev/null
+++ b/.github/workflows/pypi_publish.yml
@@ -0,0 +1,26 @@
+---
+name: PyPI publish
+
+on:
+  release:
+    types: [released]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python 3.7
+        uses: actions/setup-python@v1
+        with:
+          python-version: 3.7
+      - name: Install poetry
+        run: pip install poetry
+      - name: Bump version number
+        run: poetry version ${{ github.event.release.tag_name }}
+      - name: Build package
+        run: poetry build
+      - name: Publish package
+        run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}
diff --git a/README.rst b/README.rst
index 39b09f0..e8794c6 100644
--- a/README.rst
+++ b/README.rst
@@ -21,7 +21,7 @@ This package is in the alpha stage of development. You can direct any questions
 You can use craterpy to:
 
   - work with tables of crater data in Python (using pandas),
-  - load and manipulate image data in Python (using gdal),
+  - load and manipulate image data in Python (using rasterio),
   - easily extract, mask, filter, plot, and compute stats on craters located in your images.
 
 .. `issue tracker`_: https://github.com/cjtu/craterpy/issues
@@ -40,71 +40,98 @@ A code-snippet and plot is worth a thousand words::
     stat_df = cs.ejecta_stats(df, moon, 4, ['mean', 'median', 'std'], plot=True)
 
 
-.. image:: /craterpy/data/_images/readme_crater_ejecta.png
+.. image:: https://raw.githubusercontent.com/cjtu/craterpy/trunk/craterpy/data/_images/readme_crater_ejecta.png
 
 ::
 
   stats_df.head()
 
-.. image:: /craterpy/data/_images/readme_stat_df.png
+.. image:: https://raw.githubusercontent.com/cjtu/craterpy/trunk/craterpy/data/_images/readme_stat_df.png
 
 
 New users should start with the IPython notebook `tutorial`_ for typical usage with examples.
 
-**Note**: This package currently **only accepts image data in simple-cylindrical (Plate Caree) projection**. If your data is in another projection, please reproject it to simple-cylindrical before importing it with craterpy (check out `GDAL`_). If you would like add reprojection functionality to craterpy, consider `Contributing`_.
+**Note**: This package currently **only accepts image data in simple-cylindrical (Plate Caree) projection**. If your data is in another projection, please reproject it to simple-cylindrical before importing it with craterpy. If you would like add reprojection functionality to craterpy, consider `Contributing`_.
 
 .. _`tutorial`: https://gist.github.com/cjtu/560f121049b342aa0b2bf70e038358b7
-.. _`GDAL`: http://www.gdal.org/
 
 
 Requires
 --------
-craterpy currently supports the following python versions on Linux, OS X and Windows:
+Craterpy requires python >3.7 and is tested on Ubuntu and OS X. If you would like to use craterpy on Windows, we recommend getting the Windows Subsystem for Linux (`WSL`_) and installing it from the bash shell.
 
-- 3.5
-- 3.6
+.. _`WSL`: https://docs.microsoft.com/en-us/windows/wsl/install-win10
 
 It's core dependencies are:
 
+- rasterio
 - pandas
-- gdal=2.2.2
 - numpy
-- scipy
 - matplotlib
 
-We recommend using the `Anaconda`_ package manager to install craterpy. Anaconda automatically resolves dependency conflicts and allows you to get virtual environments working quickly.
+Installation
+------------
+
+The most reliable way to get craterpy is by installing `git <https://git-scm.com>`_ and `poetry <https://python-poetry.org/docs/>_` to clone and install the package.
 
-.. _`Anaconda`: https://www.anaconda.com/distribution/
+You can clone and install craterpy with the following steps.::
 
-Quick Installation with Anaconda
---------------------------------
+    # Clone this repository
+    $ cd ~
+    $ git clone https://github.com/cjtu/craterpy.git
 
-1. `Install Anaconda <https://www.anaconda.com/download/>`_.
+    # Enter the repository
+    $ cd roughness
 
-2. Open a terminal window and create a `conda virtual environment`_ (name it anything you like, and set the python version to a compatible version in `Requires`_)::
+    # Configure poetry
+    poetry config virtualenvs.create true --local
+    poetry config virtualenvs.in-project true --local
 
-    conda create --name your_env_name python=3.6
+    # Install craterpy with poetry
+    $ poetry install
 
-3. Activate the environment (on Windows, omit "source")::
+    # Check installation
+    poetry version
 
-    source activate your_env_name
+    # Either open a Jupyter server
+    $ poetry run jupyter notebook
 
-4. Install the dependencies (via `conda-forge`_)::
+    # Or activate the venv from your Python editor of choice
+    # The venv is path is ~/craterpy/.venv/bin/python
 
-    conda install -c conda-forge numpy scipy matplotlib pandas gdal
+Installing from pip
+-------------------
 
-5. Install craterpy with pip::
+Craterpy is also listed on `PYPI <https://pypi.org/project/craterpy/>`_ and can be installed with `pip <https://packaging.python.org/tutorials/installing-packages/>`_.::
 
     pip install craterpy
+    python -c "import craterpy; print(craterpy.__version__)"
 
-6. Check that your installation succeeded with ``conda list``.
+Installing into a conda environment
+-----------------------------------
 
-Now that you have craterpy installed, head over to the `tutorial`_ to get started!
+You can similarly install craterpy into a `conda environment`_ using pip.::
+
+    # Create conda environment
+    conda create -n craterpy python=3.7
+
+    # Activate the environment
+    conda activate craterpy
+
+    # Install dependencies (optional, but may fix dependency issues on some platforms)
+    conda install -c conda-forge rasterio pandas, numpy, matplotlib
+
+    # Install craterpy
+    pip install craterpy
 
-**Note**: Remember to activate your virtual environment each time you use craterpy.
+    # Test installation
+    python -c "import craterpy; print(craterpy.__version__)"
 
-.. _`conda virtual environment`: https://conda.io/docs/using/envs
-.. _`conda-forge`: https://conda-forge.org/
+Trouble installing craterpy? Let us know on the `issues`_ board.
+
+Now that you have craterpy installed, head over to the `tutorial`_ to get started!
+
+.. _`conda environment`: https://conda.io/docs/using/envs
 
 Installing from a fork
 ^^^^^^^^^^^^^^^^^^^^^^
@@ -131,7 +158,9 @@ There are two major ways you can help improve craterpy:
 
 Bug Reporting and Feature Requests
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-You can report bugs or request new features on the `issue tracker <https://github.com/cjtu/craterpy/issues>`_. If you are reporting a bug, please give a detailed description about how it came up and what your build environment is (e.g. with ``conda list``).
+You can report bugs or request new features on the `issues`_ board. If you are reporting a bug, please give a detailed description about how it came up and what your build environment is (e.g. with ``conda list``).
+
+.. _`issues`: https://github.com/cjtu/craterpy/issues
 
 Becoming a contributor
 ^^^^^^^^^^^^^^^^^^^^^^
@@ -147,21 +176,27 @@ The development environment is specified in `.environment.yml`. It can be built
 
 2. Clone your fork, then cd into your local craterpy repository.
 
-3. Create the ``craterpy-dev`` environment with::
+3. Install the dependencies using poetry (steps above).
+
+4. Run a jupyter notebook with::
+
+    poetry run jupyter notebook
+
+5. Test your changes::
 
-    conda env create -f .environment.yml
+    poetry run pytest craterpy
 
-4. Activate the dev environment with (ignore "source" on Windows)::
+6. Apply code formatting with black::
 
-    source activate craterpy-env
+    poetry run black craterpy
 
-5. Test the environment with::
+7. Run pylint to check your code style::
 
-    py.test craterpy
+    poetry run pylint craterpy
 
-6. Hack away!
+8. Hack away!
 
-The dev environment comes pre-installed with craterpy and all of its dependencies, as well as some handy libraries like ``pytest``, ``pytest-cov``, and ``flake8``. Read more about testing, covereage and style in `CONTRIBUTING.rst`_.
+Read more about testing, contributing and style in `CONTRIBUTING.rst`_.
 
 
 Updating .environment.yml
diff --git a/craterpy/__init__.py b/craterpy/__init__.py
index e69de29..a483450 100644
--- a/craterpy/__init__.py
+++ b/craterpy/__init__.py
@@ -0,0 +1,17 @@
+"""craterpy module"""
+from pathlib import Path
+
+
+def get_version():
+    """Return version from pyproject.toml"""
+    pyproj_path = Path(__file__).parent.parent.joinpath("pyproject.toml")
+    with Path(pyproj_path).open("r") as f:
+        line = f.readline()
+        while "version" not in line.lower():
+            line = f.readline()
+        if line:
+            return line.split("=")[1].strip()
+        return "?.?.?"
+
+
+__version__ = get_version()
diff --git a/craterpy/data/moon.tif b/craterpy/data/moon.tif
index 2a82a87..98490df 100644
Binary files a/craterpy/data/moon.tif and b/craterpy/data/moon.tif differ
diff --git a/craterpy/data/moon_nogeoref.tif b/craterpy/data/moon_nogeoref.tif
new file mode 100644
index 0000000..7978ed4
Binary files /dev/null and b/craterpy/data/moon_nogeoref.tif differ
diff --git a/craterpy/dataset.py b/craterpy/dataset.py
index 63b38ed..458c104 100644
--- a/craterpy/dataset.py
+++ b/craterpy/dataset.py
@@ -1,21 +1,19 @@
-"""Contains the CraterpyDataset object which wraps gdal.Dataset."""
-from __future__ import division, print_function, absolute_import
-from craterpy.exceptions import DataImportError
+"""Contains the CraterpyDataset object which wraps rasterio."""
+import warnings
 import numpy as np
-import gdal
+import rasterio as rio
 import craterpy.helper as ch
-gdal.UseExceptions()  # configure gdal to use Python exceptions
+import craterpy.roi as croi
 
 
-class CraterpyDataset(object):
-    """The CraterpyDataset is a specialized version of the GDAL Dataset object.
+class CraterpyDataset:
+    """The CraterpyDataset reads in images with the rasterio dataset.
 
-    The CraterpyDataset only supports simple cylindrically projected datasets.
-    It opens files supported by gdal.Open(). If the input file is a GeoTIFF,
-    the geographical bounds and resolution will be read automatically.
-    Otherwise, all attributes must be passed in the constructor.
+    If the input file is georeferenced, the geographical bounds and transform
+    will be read automatically. Otherwise, all attributes must be passed in the
+    constructor.
 
-    CraterpyDataset inherits all attributes and methods from gdal.Dataset.
+    Inherits all attributes and methods of rasterio.DatasetReader.
 
 
     Attributes
@@ -23,7 +21,7 @@ class CraterpyDataset(object):
     nlat, slat, wlon, elon : int or float
         North, south, west, and east bounds of dataset [degrees].
     radius : int or float
-        Radius of the planeary body [km].
+        Radius of the planetary body [km].
     ppd : int or float
         Resolution of dataset in [pixels per degree].
     nodata : int or float
@@ -36,64 +34,81 @@ class CraterpyDataset(object):
 
     See Also
     --------
-    gdal.Dataset
+    https://rasterio.readthedocs.io/en/latest/topics/reading.html
 
     Examples
     --------
     >>> import os.path as p
-    >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'examples')
+    >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
     >>> dsfile = p.join(datadir, 'moon.tif')
     >>> ds = CraterpyDataset(dsfile, radius=1737)
-    >>> roi = ds.get_roi(-27.2, 80.9, 207)  # Humboldt crater
+    >>> ds.get_roi(-27.6, -27.0, 80.5, 81.1).shape
+    (2, 2)
     """
-    def __init__(self, dataset, nlat=None, slat=None, wlon=None, elon=None,
-                 radius=None, ppd=None, nodata=None):
-        """Initialize CraterpyDataset object."""
-        # gdal.Dataset is not easily inherited from so we wrap it instead
-        if isinstance(dataset, gdal.Dataset):
-            self.gdalDataset = dataset
-        else:
-            self.gdalDataset = gdal.Open(dataset)
-
 
-        args = [nlat, slat, wlon, elon, radius, ppd]
-        attrs = ['nlat', 'slat', 'wlon', 'elon', 'radius', 'ppd']
-        geotags = self._get_geotiff_info()  # Attempt to read geotiff tags
-        for i, arg in enumerate(args):
+    def __init__(
+        self,
+        dataset,
+        nlat=None,
+        slat=None,
+        wlon=None,
+        elon=None,
+        radius=None,
+        xres=None,
+        yres=None,
+    ):
+        """Initialize CraterpyDataset object."""
+        with warnings.catch_warnings(record=True) as w:
+            self._rioDataset = rio.open(dataset)
+        if w and isinstance(w[0].message, rio.errors.NotGeoreferencedWarning):
+            self.transform = ()
+        args = [nlat, slat, wlon, elon, radius, xres, yres]
+        attrs = ["nlat", "slat", "wlon", "elon", "radius", "xres", "yres"]
+        if not self.transform and any(a is None for a in args[:4]):
+            msg = (
+                "No geotransform detected. Please specify nlat, slat,"
+                + "wlon, elon, and planet radius for CraterpyDataset."
+            )
+            raise ImportError(msg)
+        geotags = [None] * len(args)
+        if self.transform:
+            geotags = self._get_geotiff_info()  # Attempt to read geotiff tags
+        for arg, attr, geotag in zip(args, attrs, geotags):
             if arg is None:  # Get missing attrs from geotiff tags
-                setattr(self, attrs[i], geotags[i])
+                setattr(self, attr, geotag)
             else:  # If argument is supplied, override geotiff tags
-                setattr(self, attrs[i], arg)
-        if not self.ppd:
-            self.ppd = self.RasterXSize/(self.elon-self.wlon)
-        self.latarr = np.linspace(self.nlat, self.slat, self.RasterYSize)
-        self.lonarr = np.linspace(self.wlon, self.elon, self.RasterXSize)
+                setattr(self, attr, arg)
+        if getattr(self, "xres") is None:
+            self.xres = self.width / (self.elon - self.wlon)
+        if getattr(self, "yres") is None:
+            self.yres = self.xres
+        self.latarr = np.linspace(self.nlat, self.slat, self.height)
+        self.lonarr = np.linspace(self.wlon, self.elon, self.width)
 
     def __getattr__(self, name):
-        """Wraps self.gdalDataset."""
-        if name not in self.__dict__:  # Redirect if not in CraterpyDataset
-            try:
-                func = getattr(self.__dict__['gdalDataset'], name)
-                if callable(func):  # Call method
-                    def gdalDataset_wrapper(*args, **kwargs):
-                        return func(*args, **kwargs)
-                    return gdalDataset_wrapper
-                else:  # Not callable so must be attribute
-                    return func
-            except AttributeError:
-                raise AttributeError('Object has no attribute {}'.format(name))
+        """Wraps the self._rioDataset DatasetReader object."""
+        if name in self.__dict__:
+            return getattr(self, name)
+        return getattr(self._rioDataset, name)
 
     def __repr__(self):
         """Representation of CraterpyDataset with attribute info"""
-        attrs = (self.nlat, self.slat, self.wlon, self.elon,
-                 self.radius, self.ppd)
-        rep = 'CraterpyDataset with extent ({}N, {}N), '.format(*attrs[:2])
-        rep += '({}E, {}E), radius {} km, '.format(*attrs[2:5])
-        rep += 'and resolution {} ppd'.format(attrs[5])
+        attrs = (
+            self.nlat,
+            self.slat,
+            self.wlon,
+            self.elon,
+            self.radius,
+            self.xres,
+            self.yres,
+        )
+        rep = "CraterpyDataset with extent ({}N, {}N), ".format(*attrs[:2])
+        rep += "({}E, {}E), radius {} km, ".format(*attrs[2:5])
+        rep += "xres {} ppd, and yres {} ppd".format(*attrs[5:7])
         return rep
 
     def _get_geotiff_info(self):
-        """Get geotiff args from gdal.Datast.GetGeoTransform() method.
+        """Get geotiff info from self._rioDataset, if it exists.
 
         Returns
         -------
@@ -105,29 +120,34 @@ def _get_geotiff_info(self):
             West longitude [degrees].
         elon : int or float
             East longitude [degrees].
-        ppd : float
-            Resolution [pixels/degree].
+        xres : float
+            Longitude resolution [pixels/degree].
+        yres : float
+            Latitude resolution [pixels/degree].
+        radius : float
+            Radius of body [km]
 
         Examples
         --------
-        >>> import os
-        >>> f = os.path.dirname(os.path.abspath('__file__'))+'/tests/moon.tif'
-        >>> ds = CraterpyDataset(f)
-        >>> ds.get_info()
-        (90.0, -90.0, -180.0, 180.0, 6378.137, 4.0)
+        >>> import os.path as p
+        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
+        >>> dsfile = p.join(datadir, 'moon.tif')
+        >>> ds = CraterpyDataset(dsfile)
+        >>> ds._get_geotiff_info()
+        (90.0, -90.0, -180.0, 180.0, 6378.137, 4.0, 4.0)
         """
-        xsize, ysize = self.RasterXSize, self.RasterYSize
-        geotrans = self.GetGeoTransform()
-        try:  # Try to get info assuming WKT format
-            radius = 0.001*float(self.GetProjection().split(',')[3])
-        except Exception:
-            #print('Dataset radius not defined')
-            radius = None
-        wlon, dpp, nlat = (geotrans[0], geotrans[1], geotrans[3])
-        elon = wlon + xsize*dpp
-        slat = nlat - ysize*dpp
-        ppd = 1/dpp if dpp else xsize/(elon-wlon)
-        return nlat, slat, wlon, elon, radius, ppd
+        nlat = slat = wlon = elon = xres = yres = radius = None
+        # print(hasattr(self, 'transform'))
+        width, height = self.width, self.height
+        transform = self.transform
+        wlon, nlat = transform * (0, 0)
+        elon, slat = transform * (width, height)
+        xres = 1 / transform[0]
+        yres = -1 / transform[4]
+
+        if hasattr(self.crs, "wkt"):
+            radius = 0.001 * ch.get_spheroid_rad_from_wkt(self.crs.to_wkt())
+        return nlat, slat, wlon, elon, radius, xres, yres
 
     def calc_mpp(self, lat=0):
         """Return the ground resolution in meters/pixel at the given latitude.
@@ -143,20 +163,20 @@ def calc_mpp(self, lat=0):
         Examples
         --------
         >>> import os.path as p
-        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'examples')
+        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
         >>> dsfile = p.join(datadir, 'moon.tif')
         >>> ds = CraterpyDataset(dsfile, radius=1737)
-        >>> '{:.0f}'.format(ds.calc_mpp())
+        >>> '{:.1f}'.format(ds.calc_mpp())
         '7579.1'
-        >>> '{:.0f}'.format(ds.calc_mpp(50))
+        >>> '{:.1f}'.format(ds.calc_mpp(50))
         '4871.7'
         """
         if abs(lat) > 90:
             raise ValueError("Latitude out of bounds")
         # calculate circumference at lat in [m], divide by num pixels
-        circ = 1000*2*np.pi*self.radius*np.cos(np.radians(lat))
-        npix = 360*self.ppd  # num pixels in one circumference [pix]
-        return circ/npix  # num meters in one pixel at lat [m]/[pix]
+        circ = 1000 * 2 * np.pi * self.radius * np.cos(np.radians(lat))
+        npix = 360 * self.xres  # num pixels in one circumference [pix]
+        return circ / npix  # num meters in one pixel at lat [m]/[pix]
 
     def inbounds(self, lat, lon):
         """Return True if (lat, lon) point in Dataset bounds.
@@ -170,6 +190,9 @@ def inbounds(self, lat, lon):
 
         Examples
         --------
+        >>> import os.path as p
+        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
+        >>> dsfile = p.join(datadir, 'moon.tif')
         >>> ds = CraterpyDataset(dsfile, nlat=20, slat=0, wlon=10, elon=20)
         >>> ds.inbounds(10, 15)
         True
@@ -183,9 +206,9 @@ def inbounds(self, lat, lon):
         """
         if self.is_global():
             return self.slat <= lat <= self.nlat
-        else:
-            return ((self.slat <= lat <= self.nlat) and
-                    (self.wlon <= lon <= self.elon))
+        return (self.slat <= lat <= self.nlat) and (
+            self.wlon <= lon <= self.elon
+        )
 
     def is_global(self):
         """
@@ -194,7 +217,7 @@ def is_global(self):
         Examples
         --------
         >>> import os.path as p
-        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'examples')
+        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
         >>> dsfile = p.join(datadir, 'moon.tif')
         >>> ds = CraterpyDataset(dsfile, radius=1737)
         >>> ds.is_global()
@@ -223,58 +246,17 @@ def get_roi(self, minlon, maxlon, minlat, maxlat):
 
         Examples
         --------
-        # TODO
+        >>> import os.path as p
+        >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
+        >>> dsfile = p.join(datadir, 'moon.tif')
+        >>> ds = CraterpyDataset(dsfile, radius=1737)
+        >>> ds.get_roi(-27.6, -27.0, 80.5, 81.1).shape
+        (2, 2)
         """
-        if (not self.inbounds(minlat, minlon) or not
-                self.inbounds(maxlat, maxlon)):
-            raise DataImportError("Roi extent out of dataset bounds.")
-        topind = ch.deg2pix(self.nlat-maxlat, self.ppd)
-        height = ch.deg2pix(maxlat-minlat, self.ppd)
-        if self.is_global() and (minlon < self.wlon or maxlon > self.elon):
-            roi = self._wrap_roi_360(minlon, maxlon, topind, height)
-        else:
-            leftind = ch.deg2pix(minlon-self.wlon, self.ppd)
-            width = ch.deg2pix(maxlon-minlon, self.ppd)
-            roi = self.ReadAsArray(leftind, topind, width, height)
-        return roi.astype(float)
-
-    def _wrap_roi_360(self, minlon, maxlon, topind, height):
-            """Return roi that is split by the 360 degree edge of a global dataset.
-
-            Read the left and right sub-arrays and then concatenate them into
-            the full roi.
-
-            Parameters
-            ----------
-            minlon : int or float
-                Western longitude bound [degrees].
-            maxlon : int or float
-                Eastern longitude bound [degrees].
-            topind : int
-                Top index of returned roi.
-            height : int
-                Height of returned roi.
-
-            Returns
-            --------
-            roi: 2Darray
-                Concatenated roi wrapped around lon bound.
-            """
-            if minlon < self.wlon:
-                leftind = ch.deg2pix(minlon-(self.wlon-360), self.ppd)
-                leftwidth = ch.deg2pix(self.wlon - minlon, self.ppd)
-                rightind = 0
-                rightwidth = ch.deg2pix(maxlon - self.wlon, self.ppd)
-            elif maxlon > self.elon:
-                leftind = ch.deg2pix(self.elon - minlon, self.ppd)
-                leftwidth = ch.deg2pix(self.elon - minlon, self.ppd)
-                rightind = 0
-                rightwidth = ch.deg2pix(maxlon - self.elon, self.ppd)
-            left_roi = self.ReadAsArray(leftind, topind, leftwidth, height)
-            right_roi = self.ReadAsArray(rightind, topind, rightwidth, height)
-            return np.concatenate((left_roi, right_roi), axis=1)
+        return croi.get_roi_latlon(self, minlon, maxlon, minlat, maxlat)
 
 
 if __name__ == "__main__":
     import doctest
+
     doctest.testmod()
diff --git a/craterpy/examples/__init__.py b/craterpy/examples/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/craterpy/exceptions.py b/craterpy/exceptions.py
deleted file mode 100644
index c1c15e2..0000000
--- a/craterpy/exceptions.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Custom exceptions used by this project."""
-
-
-class DataImportError(Exception):
-    pass
diff --git a/craterpy/helper.py b/craterpy/helper.py
index aa4582a..85aaaef 100644
--- a/craterpy/helper.py
+++ b/craterpy/helper.py
@@ -1,37 +1,37 @@
 """This file contains various helper functions for craterpy"""
-from __future__ import division, print_function, absolute_import
 import numpy as np
 
 
 # Geospatial helpers
 def lon360(lon):
     """Return longitude in range [0, 360)."""
-    return ((lon + 180) % 360) + 180
+    return (lon + 360) % 360
 
 
 def lon180(lon):
     """Return longitude in range (-180, 180]."""
     return ((lon + 180) % 360) - 180
-    
+
+
 def deg2pix(degrees, ppd):
     """Return degrees converted to pixels at ppd pixels/degree."""
-    return int(degrees*ppd)
+    return int(degrees * ppd)
 
 
 def get_ind(value, array):
     """Return closest index of a value from array."""
-    ind = np.abs(array-value).argmin()
+    ind = np.abs(array - value).argmin()
     return int(ind)
 
 
 def km2deg(dist, mpp, ppd):
     """Return dist converted from kilometers to degrees."""
-    return 1000*dist/(mpp*ppd)
+    return 1000 * dist / (mpp * ppd)
 
 
 def km2pix(dist, mpp):
     """Return dist converted from kilometers to pixels"""
-    return int(1000*dist/mpp)
+    return int(1000 * dist / mpp)
 
 
 def greatcircdist(lat1, lon1, lat2, lon2, radius):
@@ -48,12 +48,15 @@ def greatcircdist(lat1, lon1, lat2, lon2, radius):
     lat1, lon1, lat2, lon2 = np.radians([lat1, lon1, lat2, lon2])
     # Haversine
     dlat, dlon = abs(lat2 - lat1), abs(lon2 - lon1)
-    a = np.sin(dlat/2)**2 + np.cos(lat1)*np.cos(lat2)*np.sin(dlon/2)**2
+    a = (
+        np.sin(dlat / 2) ** 2
+        + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
+    )
     theta = 2 * np.arcsin(np.sqrt(a))
-    return radius*theta
+    return radius * theta
 
 
-def inglobal(lat, lon, mode=None):
+def inglobal(lat, lon):
     """True if lat and lon within global coordinates.
 
     Default coords: lat in (-90, 90) and lon in (-180, 180).
@@ -61,17 +64,19 @@ def inglobal(lat, lon, mode=None):
 
     Examples
     --------
-    >>> lat = -10
-    >>> lon = -10
-    >>> inbounds(lat, lon)
+    >>> inglobal(0, 0)
     True
-    >>> inbounds(lat, lon, 'pos')
+    >>> inglobal(91, 0)
     False
+    >>> inglobal(0, -50)
+    True
     """
-    if mode == 'pos':
-        return (-90 <= lat <= 90) and (0 <= lon <= 360)
-    else:
-        return (-90 <= lat <= 90) and (-180 <= lon <= 180)
+    return (-90 <= lat <= 90) and (0 <= lon360(lon) <= 360)
+
+
+def get_spheroid_rad_from_wkt(wkt):
+    """Return body radius from Well-Known Text coordinate reference system."""
+    return float(wkt.lower().split("spheroid")[1].split(",")[1])
 
 
 # DataFrame helpers
@@ -109,9 +114,9 @@ def findcol(df, names):
 
 def get_crater_cols(df):
     """Return name of latitude, longitude, and radius columns from df"""
-    latcol = findcol(df, ['Latitude', 'Lat'])
-    loncol = findcol(df, ['Longitude', 'Lon'])
-    radcol = findcol(df, ['Radius', 'Rad'])
+    latcol = findcol(df, ["Latitude", "Lat"])
+    loncol = findcol(df, ["Longitude", "Lon"])
+    radcol = findcol(df, ["Radius", "Rad"])
     if not all((latcol, loncol, radcol)):
         e = "Unable to read latitude, longitude and/or radius from DataFrame"
         raise RuntimeError(e)
@@ -121,8 +126,8 @@ def get_crater_cols(df):
 def diam2radius(df, diamcol=None):
     """Return dataframe with diameter column converted to radius."""
     if not diamcol:
-        diamcol = findcol(df, ['diam', 'diameter'])
-    df.update(df[diamcol]/2)
+        diamcol = findcol(df, ["diam", "diameter"])
+    df.update(df[diamcol] / 2)
     df.rename(columns={diamcol: "Radius"}, inplace=True)
     return df
 
diff --git a/craterpy/masking.py b/craterpy/masking.py
index b743d8a..2df262f 100644
--- a/craterpy/masking.py
+++ b/craterpy/masking.py
@@ -1,5 +1,6 @@
-from __future__ import division, print_function, absolute_import
+"""Mask CraterRoi objects."""
 import numpy as np
+from matplotlib.path import Path
 import craterpy.helper as ch
 
 
@@ -31,11 +32,11 @@ def circle_mask(shape, radius, center=None):
            [False,  True,  True],
            [False, False,  True]], dtype=bool)
     """
-    cy, cx = center if center else np.array(shape)/2 - 0.5
+    cy, cx = center if center else np.array(shape) / 2 - 0.5
     height, width = shape
     x = np.arange(width) - cx
     y = np.arange(height).reshape(-1, 1) - cy
-    return x*x + y*y <= radius*radius
+    return x * x + y * y <= radius * radius
 
 
 def ellipse_mask(shape, ysize, xsize, center=None):
@@ -70,15 +71,16 @@ def ellipse_mask(shape, ysize, xsize, center=None):
            [False,  True,  True,  True,  True],
            [False, False, False,  True, False]], dtype=bool)
     """
-    cy, cx = center if center else np.array(shape)/2 - 0.5
+    cy, cx = center if center else np.array(shape) / 2 - 0.5
     height, width = shape
-    y, x = np.ogrid[-cy:height-cy, -cx:width-cx]
+    y, x = np.ogrid[-cy : height - cy, -cx : width - cx]
     if (xsize != 0) and (ysize != 0):
-        mask = (x*x)/(xsize*xsize) + (y*y)/(ysize*ysize) <= 1
+        mask = (x * x) / (xsize * xsize) + (y * y) / (ysize * ysize) <= 1
     else:
-        mask = np.array((x+y)*0, dtype=bool)
+        mask = np.array((x + y) * 0, dtype=bool)
     return mask
 
+
 def ring_mask(shape, rmin, rmax, center=None):
     """Return bool array of True in a circular ring from rmin to rmax.
 
@@ -115,7 +117,7 @@ def ring_mask(shape, rmin, rmax, center=None):
     """
     inner = circle_mask(shape, rmin, center)
     outer = circle_mask(shape, rmax, center)
-    return outer*~inner
+    return outer * ~inner
 
 
 def crater_floor_mask(croi, buffer=1):
@@ -135,8 +137,8 @@ def crater_floor_mask(croi, buffer=1):
     Examples
     --------
     """
-    pixwidth = ch.km2pix(croi.rad*buffer, croi.cds.calc_mpp(croi.lat))
-    pixheight = ch.km2pix(croi.rad*buffer, croi.cds.calc_mpp())
+    pixwidth = ch.km2pix(croi.rad * buffer, croi.cds.calc_mpp(croi.lat))
+    pixheight = ch.km2pix(croi.rad * buffer, croi.cds.calc_mpp())
     return ellipse_mask(croi.roi.shape, pixheight, pixwidth)
 
 
@@ -200,16 +202,19 @@ def polygon_mask(croi, poly_verts):
     >>> croi.mask(mask)
     >>> croi.plot()
     """
-    from matplotlib.path import Path
-    minlon, maxlon, minlat, maxlat = croi.extent
+    minlon, _, minlat, _ = croi.extent
     # Create grid
     nlat, nlon = croi.roi.shape
     x, y = np.meshgrid(np.arange(nlon), np.arange(nlat))
     x, y = x.flatten(), y.flatten()
     gridpoints = np.vstack((x, y)).T
-    poly_pix = [(ch.deg2pix(lon-minlon, croi.cds.ppd),
-                ch.deg2pix(lat-minlat, croi.cds.ppd))
-                for lon, lat in poly_verts]
+    poly_pix = [
+        (
+            ch.deg2pix(lon - minlon, croi.cds.xres),
+            ch.deg2pix(lat - minlat, croi.cds.yres),
+        )
+        for lon, lat in poly_verts
+    ]
     path = Path(poly_pix)
     mask = path.contains_points(gridpoints).reshape((nlat, nlon))
     return mask
diff --git a/craterpy/plotting.py b/craterpy/plotting.py
index 73fdfe9..b79e991 100644
--- a/craterpy/plotting.py
+++ b/craterpy/plotting.py
@@ -1,10 +1,8 @@
-"""This file contains helper functions for plotting.
-"""
-from __future__ import division, print_function, absolute_import
+"""This file contains helper functions for plotting."""
 import matplotlib.pyplot as plt
 
 
-def plot_CraterRoi(croi, figsize=((4, 4)), title=None, cmap='gray', **kwargs):
+def plot_CraterRoi(croi, figsize=((4, 4)), title=None, cmap="gray", **kwargs):
     """
     Plot 2D CraterRoi.
 
@@ -33,23 +31,6 @@ def plot_CraterRoi(croi, figsize=((4, 4)), title=None, cmap='gray', **kwargs):
     plt.figure(title, figsize=figsize)
     plt.imshow(croi.roi, extent=croi.extent, cmap=cmap, **kwargs)
     plt.title(title)
-    plt.xlabel('Longitude (degrees)')
-    plt.ylabel('Latitude (degrees)')
+    plt.xlabel("Longitude (degrees)")
+    plt.ylabel("Latitude (degrees)")
     plt.show()
-
-
-def plot_ejecta_stats():
-    """Plot ejecta statistics.
-    """
-    pass  # TODO: implement this
-
-
-def plot_ejecta_profile_stats():
-    """Plot ejecta profile statistics.
-    """
-    pass  # TODO: implement this
-
-#
-# if __name__ == "__main__":
-#    import doctest
-#    doctest.testmod()
diff --git a/craterpy/quickstats.py b/craterpy/quickstats.py
index a74f9a3..9367267 100644
--- a/craterpy/quickstats.py
+++ b/craterpy/quickstats.py
@@ -5,9 +5,7 @@
 All functions must take a numpy 2D array and return a numerical value that can
 be input into a table, DataFrame, or written to a csv.
 """
-from __future__ import division, print_function, absolute_import
 import numpy as np
-import scipy.stats
 
 
 def size(roi):
@@ -27,7 +25,9 @@ def median(roi):
 
 def mode(roi):
     """Return the mode of roi"""
-    return scipy.stats.mode(roi, None)[0][0]
+    values, counts = np.unique(roi, return_counts=True)
+    m = counts.argmax()
+    return values[m]
 
 
 def std(roi):
@@ -44,10 +44,12 @@ def minimum(roi):
     """Return minimum pixel value in roi"""
     return np.min(roi)
 
+
 def pct5(roi):
     """Return the 5th percentile (5/95) value of roi"""
     return np.percentile(roi, 5)
 
+
 def q1(roi):
     """Return the first quartile value (25/75) of roi"""
     return np.percentile(roi, 25)
@@ -61,5 +63,3 @@ def q3(roi):
 def pct95(roi):
     """Return the 95th percentile (95/5) value of roi"""
     return np.percentile(roi, 95)
-
-# TODO: area
diff --git a/craterpy/roi.py b/craterpy/roi.py
index 14f7af5..ea96efb 100644
--- a/craterpy/roi.py
+++ b/craterpy/roi.py
@@ -1,10 +1,129 @@
 """Contains the CraterRoi object"""
-from __future__ import division, print_function, absolute_import
 import numpy as np
+import rasterio as rio
 import craterpy.helper as ch
 from craterpy.plotting import plot_CraterRoi
 
 
+def get_extent(cds, lat, lon, rad, wsize=1):
+    """
+    Return the extent of the CraterRoi in cds at lat, lon and size.
+
+    Parameters
+    ----------
+    cds : CraterpyDataset
+        CraterDataset to read roi from.
+    lat: float
+        The latitude of the crater center [degrees].
+    lon: float
+        The longitude of the crater center [degrees].
+    rad: float
+        The radius of the crater [km].
+    size: float
+        The window size to read in crater radii (default 1 crater radius).
+
+    Returns
+    -------
+    extent : tuple
+        Bounds of this roi (minlon, maxlon, minlat, maxlat) [degrees].
+    """
+    width = ch.km2deg(
+        2 * wsize * rad,
+        cds.calc_mpp(lat),
+        cds.xres,
+    )
+    height = ch.km2deg(2 * wsize * rad, cds.calc_mpp(), cds.yres)
+    minlon = lon - (width / 2)
+    maxlon = minlon + width
+    minlat = lat - (height / 2)
+    maxlat = minlat + height
+    return (minlon, maxlon, minlat, maxlat)
+
+
+def wrap_roi_360(cds, minlon, maxlon, topind, height):
+    """Return roi that is split by the edge of a global dataset.
+
+    Read the left and right sub-arrays and then concatenate them into
+    the full roi.
+
+    Parameters
+    ----------
+    cds : CraterpyDataset
+        CraterDataset to read roi from.
+    minlon : int or float
+        Western longitude bound [degrees].
+    maxlon : int or float
+        Eastern longitude bound [degrees].
+    topind : int
+        Top index of returned roi.
+    height : int
+        Height of returned roi.
+
+    Returns
+    --------
+    roi: 2Darray
+        Concatenated roi wrapped around lon bound.
+    """
+    elon, minlon, maxlon = ch.lon360(np.array([cds.elon, minlon, maxlon]))
+
+    # The # of pixels roi to the right of elon
+    rightwidth = ch.deg2pix(maxlon - elon, cds.xres)
+    rightind = 0
+    # The # of pixels left of the elon
+    leftwidth = ch.deg2pix(elon - minlon, cds.xres)
+    leftind = cds.width - leftwidth
+    # Read left and right ROIs and concatenate around elon
+    w_left = rio.windows.Window(leftind, topind, leftwidth, height)
+    w_right = rio.windows.Window(rightind, topind, rightwidth, height)
+    left_roi = cds.read(1, window=w_left)
+    right_roi = cds.read(1, window=w_right)
+    return np.concatenate((left_roi, right_roi), axis=1)
+
+
+def get_roi_latlon(cds, minlon, maxlon, minlat, maxlat):
+    """Return numpy array of data specified by its geographical bounds
+
+    Parameters
+    ----------
+    cds : CraterpyDataset
+        The CraterpyDataset from which to extract the roi.
+    minlon : int or float
+        Western longitude bound [degrees].
+    maxlon : int or float
+        Eastern longitude bound [degrees].
+    minlat : int or float
+        Southern latitude bound [degrees].
+    maxlat : int or float
+        Northern latitude bound [degrees].
+
+    Returns
+    -------
+    roi : numpy 2D array
+        Numpy array specified by extent given.
+
+    Examples
+    --------
+    >>> import os.path as p
+    >>> datadir = p.join(p.dirname(p.abspath('__file__')), 'craterpy', 'data')
+    >>> dsfile = p.join(datadir, 'moon.tif')
+    >>> ds = CraterpyDataset(dsfile, radius=1737)
+    >>> ds.get_roi(-27.6, -27.0, 80.5, 81.1).shape
+    (2, 2)
+    """
+    if not cds.inbounds(minlat, minlon) or not cds.inbounds(maxlat, maxlon):
+        raise ValueError("Roi extent out of dataset bounds.")
+    topind = ch.deg2pix(cds.nlat - maxlat, cds.yres)
+    height = ch.deg2pix(maxlat - minlat, cds.yres)
+    if cds.is_global() and (minlon < cds.wlon or maxlon > cds.elon):
+        roi = wrap_roi_360(cds, minlon, maxlon, topind, height)
+    else:
+        leftind = ch.deg2pix(minlon - cds.wlon, cds.xres)
+        width = ch.deg2pix(maxlon - minlon, cds.xres)
+        w = rio.windows.Window(leftind, topind, width, height)
+        roi = cds.read(1, window=w)
+    return roi.astype(float)
+
+
 class CraterRoi:
     """The CraterRoi contains a region of interest of image data for a crater.
 
@@ -48,78 +167,31 @@ class CraterRoi:
     >>> cds = CraterpyDataset(dsfile, radius=1737)
     >>> croi = CraterRoi(cds, -27.2, 80.9, 207)  # Humboldt crater
     """
+
     def __init__(self, cds, lat, lon, rad, wsize=1, plot=False):
         self.cds = cds
         self.lat = lat
-        if self.cds.wlon < 0:
-            self.lon = ch.lon180(lon)
-        else:
-            self.lon = ch.lon360(lon)
+        self.lon = ch.lon180(lon) if cds.wlon < 0 else ch.lon360(lon)
+
         self.rad = rad
         self.wsize = wsize
-        self.extent = self._get_extent()
-        self.roi = self._get_roi()
+        self.extent = get_extent(cds, self.lat, self.lon, self.rad, self.wsize)
+        self.roi = get_roi_latlon(cds, *self.extent)
         if plot:
             self.plot()
 
     def __repr__(self):
-        return "CraterRoi at ({}N, {}E) with radius {} km".format(self.lat,
-                                                                  self.lon,
-                                                                  self.rad)
-
-    def _get_extent(self):
-        """Return the bounds of this roi in degrees.
-
-        Extent is specified as a tuple of (minlon, maxlon, minlat, maxlat).
+        return "CraterRoi at ({}N, {}E) with radius {} km".format(
+            self.lat, self.lon, self.rad
+        )
 
-        Returns
-        -------
-        extent : tuple
-            Bounds of this roi (minlon, maxlon, minlat, maxlat) [degrees].
-
-        Examples
-        --------
-
-        """
-        width = ch.km2deg(2*self.wsize*self.rad, self.cds.calc_mpp(self.lat),
-                          self.cds.ppd)
-        height = ch.km2deg(2*self.wsize*self.rad, self.cds.calc_mpp(),
-                           self.cds.ppd)
-        minlon = self.lon-(width/2)
-        maxlon = minlon + width
-        minlat = self.lat-(height/2)
-        maxlat = minlat + height
-        return (minlon, maxlon, minlat, maxlat)
-
-    def _get_roi(self):
-            """Return 2D numpy array with data from self.cds.
-
-            This roi is centered on (self.lat, self.lon). The window extends
-            wsize crater radii from the crater center.
-
-            Use _wrap_lon() if cds roi crosses lon extent of global dataset.
-
-            Parameters
-            ----------
-            mask_crater : bool
-                Masks the crater floor from the resulting ROI by replacing the
-                crater with an ellipse of NaN.
-            plot_roi : bool
-                Plots the returned ROI.
-
-            Returns
-            --------
-            roi: 2Darray
-                Numpy 2D array of data centered on the specified crater and
-                extending wsize*rad distance from the crater centre.
-            """
-            # If lon > 180, switch lon convention from (0, 360) -> (-180, 180)
-            if self.lon > 180:
-                self.lon -= 360
-            return self.cds.get_roi(*self.extent)
-
-    def filter(self, vmin=float('-inf'), vmax=float('inf'), strict=False,
-               nodata=np.nan):
+    def filter(
+        self,
+        vmin=float("-inf"),
+        vmax=float("inf"),
+        strict=False,
+        nodata=np.nan,
+    ):
         """Filter roi to the inclusive range [vmin, vmax].
 
         You can specify only vmin or vmax if only a lower bound/upper bound is
@@ -148,13 +220,14 @@ def filter(self, vmin=float('-inf'), vmax=float('inf'), strict=False,
         mask = np.isfinite(self.roi)  # filter pre-existing nans and infs
         if strict:
             # Mask includes pixels >= vmin and <= vmax
-            mask[mask] = mask[mask] & ((self.roi[mask] > vmin) &
-                                       (self.roi[mask] < vmax))
+            mask[mask] = mask[mask] & (
+                (self.roi[mask] > vmin) & (self.roi[mask] < vmax)
+            )
         else:
-            mask[mask] = mask[mask] & ((self.roi[mask] >= vmin) &
-                                       (self.roi[mask] <= vmax))
+            mask[mask] = mask[mask] & (
+                (self.roi[mask] >= vmin) & (self.roi[mask] <= vmax)
+            )
         self.roi[~mask] = nodata  # set invalid pixels (~mask) with nodata
-        return
 
     def mask(self, mask, outside=False, fillvalue=np.nan):
         """Fill pixels in bool mask from masking.py with fillvalue.
@@ -175,7 +248,6 @@ def mask(self, mask, outside=False, fillvalue=np.nan):
         if outside:
             mask = ~mask
         self.roi[np.where(mask)] = fillvalue
-        return
 
     def plot(self, *args, **kwargs):
         """Plot this CraterRoi. See plotting.plot_CraterRoi()"""
diff --git a/craterpy/stats.py b/craterpy/stats.py
index b10b3b4..35f4d6e 100644
--- a/craterpy/stats.py
+++ b/craterpy/stats.py
@@ -1,11 +1,12 @@
-from __future__ import division, print_function, absolute_import
+"""Compute stats on CraterRoi objects."""
 import inspect
+import os.path as p
+from timeit import default_timer
 import numpy as np
-import scipy as sp
 import pandas as pd
 from craterpy import quickstats as qs
 from craterpy import helper as ch
-from craterpy import masking, plotting
+from craterpy import masking
 from craterpy.roi import CraterRoi
 
 # quickstats helpers
@@ -41,15 +42,28 @@ def _get_quickstats_functions(statlist=None):
         statlist = [statlist]
     invalid_stats = [stat for stat in statlist if stat not in qs_list]
     if invalid_stats:
-        raise ValueError('The following stats are not defined ' +
-                         'in quickstats.py: {}'.format(invalid_stats))
+        raise ValueError(
+            "The following stats are not defined "
+            + "in quickstats.py: {}".format(invalid_stats)
+        )
     return [[stat, qs.__dict__[stat]] for stat in statlist]
 
 
 # Main craterpy stat functions
-def ejecta_profile_stats(df, cds, ejrad=2, rspacing=0.25, stats=None,
-                         plot_roi=False, vmin=None, vmax=None, strict=False,
-                         plot_stats=False, savepath=None, timer=False, dt=60):
+def ejecta_profile_stats(
+    df,
+    cds,
+    ejrad=2,
+    rspacing=0.25,
+    stats=None,
+    plot_roi=False,
+    vmin=None,
+    vmax=None,
+    strict=False,
+    savepath=None,
+    timer=False,
+    dt=60,
+):
     """Compute stat profiles across ejecta blankets by computing stats in a
     series of concentric rings beginning at the crater rim and extending to
     ejrad crater radii from the crater centre. Each ring is rspacing thick and
@@ -80,12 +94,10 @@ def ejecta_profile_stats(df, cds, ejrad=2, rspacing=0.25, stats=None,
 
     """
     if timer:
-        from timeit import default_timer as timer
-        Tstart, Tnow = timer(), timer()
+        Tstart, Tnow = default_timer(), default_timer()
         count = 1  # count craters
     if savepath:
-        import os.path as p
-        savename = p.join(savepath, '{}_ejpstats.csv')
+        savename = p.join(savepath, "{}_ejpstats.csv")
     if stats is None:
         stats = _list_quickstats()
     stat_dict = {}
@@ -94,49 +106,63 @@ def ejecta_profile_stats(df, cds, ejrad=2, rspacing=0.25, stats=None,
         lat, lon, rad = df.loc[i, latcol], df.loc[i, loncol], df.loc[i, radcol]
         try:
             croi = CraterRoi(cds, lat, lon, rad, ejrad)
-            ring_array = np.arange(1, ejrad+rspacing, rspacing)  # inner radius
-            ring_darray = ring_array*rad  # convert inner radius to km
+            ring_array = np.arange(
+                1, ejrad + rspacing, rspacing
+            )  # inner radius
+            ring_darray = ring_array * rad  # convert inner radius to km
             stat_df = pd.DataFrame(index=ring_array[:-1], columns=stats)
-            for i in range(len(ring_array)-1):  # Loop through radii
-                rad_inner = ring_darray[i]
-                rad_outer = ring_darray[i+1]
-                mask = masking.crater_ring_mask(cds, croi, lat, lon, rad_inner,
-                                                rad_outer)
+            for j in range(len(ring_array) - 1):  # Loop through radii
+                rad_inner = ring_darray[j]
+                rad_outer = ring_darray[j + 1]
+                mask = masking.crater_ring_mask(
+                    cds, croi, lat, lon, rad_inner, rad_outer
+                )
                 ejecta_roi = croi.mask(~mask).filter(vmin, vmax, strict)
                 ejecta_roi = ejecta_roi[~np.isnan(ejecta_roi)]
                 for stat, function in _get_quickstats_functions(stats):
-                    stat_df.loc[ring_array[i], stat] = function(ejecta_roi)
+                    stat_df.loc[ring_array[j], stat] = function(ejecta_roi)
                 if plot_roi:
                     ejecta_roi.plot()
             stat_dict[i] = stat_df
-            if plot_stats:
-                plotting.plot_ejecta_profile_stats(stat_df)
             if savepath:
                 stat_df.to_csv(savename.format(i))
         except ImportError as e:  # Catches and prints out of bounds exceptions
             print(e, ". Skipping....")
         if timer:  # Print a status update approx every dt seconds
-            if (timer() - Tnow > dt) or (count == len(df)):
-                update = 'Finished crater {} out of {}'.format(count, len(df))
-                elapsed = timer()-Tstart
-                update += '\n  Time elapsed: \
-                          {}h:{}m:{}s'.format(int(elapsed//3600),
-                                              int((elapsed % 3600)//60),
-                                              round((elapsed % 3600) % 60, 2))
+            if (default_timer() - Tnow > dt) or (count == len(df)):
+                update = "Finished crater {} out of {}".format(count, len(df))
+                elapsed = default_timer() - Tstart
+                update += "\n  Time elapsed: \
+                          {}h:{}m:{}s".format(
+                    int(elapsed // 3600),
+                    int((elapsed % 3600) // 60),
+                    round((elapsed % 3600) % 60, 2),
+                )
                 print(update)
-                Tnow = timer()
+                Tnow = default_timer()
             count += 1
     return stat_dict
 
 
-def compute_stats(df, cds, stats=[], plot=False, save=False, prefix="",
-                  vmin=float('-inf'), vmax=float('inf'), strict=False, 
-                  maskfunc=None, mask_out=False, ejrad=None, buffer=1, 
-                  verbosity=1):
-    """Computes stats on craters in df with image data from cds.
-    """
+def compute_stats(
+    df,
+    cds,
+    stats=None,
+    plot=False,
+    save=False,
+    prefix="",
+    vmin=float("-inf"),
+    vmax=float("inf"),
+    strict=False,
+    maskfunc=None,
+    mask_out=False,
+    ejrad=None,
+    buffer=1,
+    verbosity=1,
+):
+    """Computes stats on craters in df with image data from cds."""
     # If stats and index not provided, assume use all stats and all rows in cdf
-    if not stats:
+    if stats is None:
         stats = _list_quickstats()
     # Initialize return Dataframe with stats as individual columns
     ret_df = df.copy()
@@ -155,29 +181,45 @@ def compute_stats(df, cds, stats=[], plot=False, save=False, prefix="",
                 if maskfunc == "crater":
                     mask = masking.crater_floor_mask(croi, buffer)
                 elif maskfunc == "ejecta":
-                    mask = masking.crater_ring_mask(croi, rad*buffer, rad*ejrad)
+                    mask = masking.crater_ring_mask(
+                        croi, rad * buffer, rad * ejrad
+                    )
                 croi.mask(mask, mask_out)
             data_arr = croi.roi[~np.isnan(croi.roi)]  # Collapses to 1D
             for stat, function in _get_quickstats_functions(stats):
                 ret_df.loc[i, stat] = function(data_arr)
             if plot:  # plot filtered and masked roi
                 croi.plot()
-            if save: # save roi to csv: prefix_lat_lon_rad.csv
-                fname = "{}croi_{:.3f}_{:.3f}_{:.3f}.csv".format(prefix, lat, lon, rad)
+            if save:  # save roi to csv: prefix_lat_lon_rad.csv
+                fname = "{}croi_{:.3f}_{:.3f}_{:.3f}.csv".format(
+                    prefix, lat, lon, rad
+                )
                 croi.save(fname)
 
-        except Exception as e:
+        except ValueError as e:
             for stat, _ in _get_quickstats_functions(stats):
-                ret_df.loc[i, stat] = np.nan    
+                ret_df.loc[i, stat] = np.nan
             if verbosity:
-                print("Exception '{}' skipping crater at ({}, {}) \
-                       with radius {}".format(e,lat, lon, rad))
+                print(
+                    "Exception '{}' skipping crater at ({}, {}) \
+                       with radius {}".format(
+                        e, lat, lon, rad
+                    )
+                )
             continue
     return ret_df
 
 
-def crater_stats(df, cds, stats=None, plot=False, vmin=float('-inf'),
-                 vmax=float('inf'), strict=False, verbosity=1):
+def crater_stats(
+    df,
+    cds,
+    stats=None,
+    plot=False,
+    vmin=float("-inf"),
+    vmax=float("inf"),
+    strict=False,
+    verbosity=1,
+):
     """Computes stats on all craters in df using image data from cds
 
     Crater latitude, longitude, and radius are read in from df. All stats are
@@ -209,13 +251,34 @@ def crater_stats(df, cds, stats=None, plot=False, vmin=float('-inf'),
     DataFrame
         Copy of df with stats appended as new columns.
     """
-    return compute_stats(df, cds, stats, plot, vmin, vmax, strict,
-                         maskfunc="crater", mask_out=True, verbosity=verbosity)
+    return compute_stats(
+        df,
+        cds,
+        stats,
+        plot,
+        vmin,
+        vmax,
+        strict,
+        maskfunc="crater",
+        mask_out=True,
+        verbosity=verbosity,
+    )
 
 
-def ejecta_stats(df, cds, ejrad=2, buffer=1, stats=None, plot=None, 
-                 save=None, prefix=None, vmin=float('-inf'), vmax=float('inf'), 
-                 strict=False, verbosity=1):
+def ejecta_stats(
+    df,
+    cds,
+    ejrad=2,
+    buffer=1,
+    stats=None,
+    plot=None,
+    save=None,
+    prefix=None,
+    vmin=float("-inf"),
+    vmax=float("inf"),
+    strict=False,
+    verbosity=1,
+):
     """Compute the specified stats from acestats.py on a circular ejecta ROI
     extending ejsize crater radii from the crater center. Return results as
     DataFrame with stats appended as extra columns.
@@ -251,9 +314,22 @@ def ejecta_stats(df, cds, ejrad=2, buffer=1, stats=None, plot=None,
     DataFrame
         Copy of df with stats appended as new columns.
     """
-    return compute_stats(df, cds, stats, plot, save, prefix, vmin, vmax, 
-                         strict, maskfunc="ejecta", mask_out=True, ejrad=ejrad, 
-                         buffer=buffer, verbosity=verbosity)
+    return compute_stats(
+        df,
+        cds,
+        stats,
+        plot,
+        save,
+        prefix,
+        vmin,
+        vmax,
+        strict,
+        maskfunc="ejecta",
+        mask_out=True,
+        ejrad=ejrad,
+        buffer=buffer,
+        verbosity=verbosity,
+    )
 
     # If stats and index not provided, assume use all stats and all rows in cdf
     # if stats is None:
@@ -280,8 +356,9 @@ def ejecta_stats(df, cds, ejrad=2, buffer=1, stats=None, plot=None,
 
 
 # Other statistics
-def histogram(roi, bins, hmin=None, hmax=None, skew=False, verbose=False,
-              *args, **kwargs):
+def histogram(
+    roi, bins, hmin=None, hmax=None, skew=False, verbose=False, **kwargs
+):
     """
     Return histogram, bins of histogram computed on ROI. See np.histogram for
     full usage and optional parameters. Set verbose=True to print a summary of
@@ -289,18 +366,18 @@ def histogram(roi, bins, hmin=None, hmax=None, skew=False, verbose=False,
     """
     roi_notnan = roi[~np.isnan(roi)]
     roi_valid = roi_notnan[hmin <= roi_notnan <= hmax]
-    hist, bins = np.histogram(roi, bins=bins, hmin=hmin, hmax=hmax, *args,
-                              **kwargs)
+    hist, bins = np.histogram(roi, bins, range=(hmin, hmax), **kwargs)
     ret = [hist, bins]
-    output = 'Histogram with {} pixels total, \
+    output = "Histogram with {} pixels total, \
               {} pixels in [hmin, hmax] inclusive, \
               {} nan pixels excluded, \
-              {} bins'.format(len(roi), len(roi_valid), len(roi_notnan),
-                              len(bins))
+              {} bins".format(
+        len(roi), len(roi_valid), len(roi_notnan), len(bins)
+    )
     if skew:
-        skewness = sp.stats.skew(hist)
+        skewness = pd.DataFrame(roi.flatten).skew
         ret.append(skewness)
-        output += ', {} skewness'.format(skewness)
+        output += ", {} skewness".format(skewness)
     if verbose:
         print(output)
     return ret
diff --git a/craterpy/version.py b/craterpy/version.py
deleted file mode 100644
index 9067bc2..0000000
--- a/craterpy/version.py
+++ /dev/null
@@ -1,21 +0,0 @@
-""" Keeps track of the current acerim version. Versions are specified as a
-string of the form "X.Y.Z" (major.minor.maintenance). Versions still in
-development append 'dev0' (e.g., "X.Y.Zdev0").
-"""
-_major = 0
-_minor = 3
-_maintenance = ''  # use '' for new major/minor release; int for 1+
-_extra = ''  # NEVER release with dev, use '' for full release
-
-
-def concatenate_version(major, minor, maintenance, extra):
-    """ Construct full version string to pass to setup.py """
-    _ver = [major, minor]
-    if maintenance:
-        _ver.append(maintenance)
-    if extra:
-        _ver.append(extra)
-    return '.'.join(map(str, _ver))
-
-
-__version__ = concatenate_version(_major, _minor, _maintenance, _extra)
diff --git a/docs/environment.yml b/docs/environment.yml
index a595057..dd2096e 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -1,6 +1,5 @@
 dependencies:
 - numpy
-- scipy
 - pandas
 - matplotlib
 - libgdal
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f7a1654..20cfbb4 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -17,12 +17,12 @@
 
 # -- Project information -----------------------------------------------------
 
-project = 'craterpy'
-copyright = '2021, Christian J. Tai Udovicic'
-author = 'Christian J. Tai Udovicic'
+project = "craterpy"
+copyright = "2021, Christian J. Tai Udovicic"
+author = "Christian J. Tai Udovicic"
 
 # The full version, including alpha/beta/rc tags
-release = '0.3'
+release = "0.3"
 
 
 # -- General configuration ---------------------------------------------------
@@ -30,15 +30,15 @@
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = ["sphinx.ext.autodoc"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
 # This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 
 # -- Options for HTML output -------------------------------------------------
@@ -46,9 +46,9 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'alabaster'
+html_theme = "alabaster"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = []
\ No newline at end of file
+html_static_path = []
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..e3be3bf
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,1969 @@
+[[package]]
+name = "affine"
+version = "2.3.0"
+description = "Matrices describing affine transformation of the plane."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["pytest (>=3.0)", "pytest-cov", "pydocstyle", "coveralls"]
+
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "appnope"
+version = "0.1.2"
+description = "Disable App Nap on macOS >= 10.9"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "argon2-cffi"
+version = "20.1.0"
+description = "The secure Argon2 password hashing algorithm."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+cffi = ">=1.0.0"
+six = "*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest", "sphinx", "wheel", "pre-commit"]
+docs = ["sphinx"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"]
+
+[[package]]
+name = "astroid"
+version = "2.5.6"
+description = "An abstract syntax tree for Python with inference support."
+category = "dev"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+lazy-object-proxy = ">=1.4.0"
+typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""}
+wrapt = ">=1.11,<1.13"
+
+[[package]]
+name = "async-generator"
+version = "1.10"
+description = "Async generators and context managers for Python 3.5+"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "attrs"
+version = "21.2.0"
+description = "Classes Without Boilerplate"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+
+[[package]]
+name = "backcall"
+version = "0.2.0"
+description = "Specifications for callback functions passed in to an API"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "black"
+version = "21.5b1"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+appdirs = "*"
+click = ">=7.1.2"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.8.1,<1"
+regex = ">=2020.1.8"
+toml = ">=0.10.1"
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""}
+typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.6.0)", "aiohttp-cors"]
+python2 = ["typed-ast (>=1.4.2)"]
+
+[[package]]
+name = "bleach"
+version = "3.3.0"
+description = "An easy safelist-based HTML-sanitizing tool."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+packaging = "*"
+six = ">=1.9.0"
+webencodings = "*"
+
+[[package]]
+name = "certifi"
+version = "2020.12.5"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "cffi"
+version = "1.14.5"
+description = "Foreign Function Interface for Python calling C code."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "click"
+version = "7.1.2"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "click-plugins"
+version = "1.1.1"
+description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+click = ">=4.0"
+
+[package.extras]
+dev = ["pytest (>=3.6)", "pytest-cov", "wheel", "coveralls"]
+
+[[package]]
+name = "cligj"
+version = "0.7.1"
+description = "Click params for commmand line interfaces to GeoJSON"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4"
+
+[package.dependencies]
+click = ">=4.0,<8"
+
+[package.extras]
+test = ["pytest-cov"]
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "coverage"
+version = "5.5"
+description = "Code coverage measurement for Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+toml = ["toml"]
+
+[[package]]
+name = "cycler"
+version = "0.10.0"
+description = "Composable style cycles"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+name = "decorator"
+version = "5.0.7"
+description = "Decorators for Humans"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+description = "XML bomb protection for Python stdlib modules"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "entrypoints"
+version = "0.3"
+description = "Discover and load entry points from installed packages."
+category = "dev"
+optional = false
+python-versions = ">=2.7"
+
+[[package]]
+name = "importlib-metadata"
+version = "4.0.1"
+description = "Read metadata from Python packages"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+
+[[package]]
+name = "iniconfig"
+version = "1.1.1"
+description = "iniconfig: brain-dead simple config-ini parsing"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ipykernel"
+version = "5.5.5"
+description = "IPython Kernel for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+appnope = {version = "*", markers = "platform_system == \"Darwin\""}
+ipython = ">=5.0.0"
+jupyter-client = "*"
+tornado = ">=4.2"
+traitlets = ">=4.1.0"
+
+[package.extras]
+test = ["pytest (!=5.3.4)", "pytest-cov", "flaky", "nose", "jedi (<=0.17.2)"]
+
+[[package]]
+name = "ipython"
+version = "7.23.1"
+description = "IPython: Productive Interactive Computing"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+appnope = {version = "*", markers = "sys_platform == \"darwin\""}
+backcall = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+decorator = "*"
+jedi = ">=0.16"
+matplotlib-inline = "*"
+pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
+pickleshare = "*"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+traitlets = ">=4.2"
+
+[package.extras]
+all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.16)", "pygments", "qtconsole", "requests", "testpath"]
+doc = ["Sphinx (>=1.3)"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["notebook", "ipywidgets"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.16)"]
+
+[[package]]
+name = "ipython-genutils"
+version = "0.2.0"
+description = "Vestigial utilities from IPython"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "ipywidgets"
+version = "7.6.3"
+description = "IPython HTML widgets for Jupyter"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ipykernel = ">=4.5.1"
+ipython = {version = ">=4.0.0", markers = "python_version >= \"3.3\""}
+jupyterlab-widgets = {version = ">=1.0.0", markers = "python_version >= \"3.6\""}
+nbformat = ">=4.2.0"
+traitlets = ">=4.3.1"
+widgetsnbextension = ">=3.5.0,<3.6.0"
+
+[package.extras]
+test = ["pytest (>=3.6.0)", "pytest-cov", "mock"]
+
+[[package]]
+name = "isort"
+version = "5.8.0"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
+optional = false
+python-versions = ">=3.6,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+
+[[package]]
+name = "jedi"
+version = "0.18.0"
+description = "An autocompletion tool for Python that can be used for text editors."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+parso = ">=0.8.0,<0.9.0"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"]
+
+[[package]]
+name = "jinja2"
+version = "3.0.0"
+description = "A very fast and expressive template engine."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+MarkupSafe = ">=2.0.0rc2"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jsonschema"
+version = "3.2.0"
+description = "An implementation of JSON Schema validation for Python"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+attrs = ">=17.4.0"
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+pyrsistent = ">=0.14.0"
+six = ">=1.11.0"
+
+[package.extras]
+format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
+format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
+
+[[package]]
+name = "jupyter"
+version = "1.0.0"
+description = "Jupyter metapackage. Install all the Jupyter components in one go."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ipykernel = "*"
+ipywidgets = "*"
+jupyter-console = "*"
+nbconvert = "*"
+notebook = "*"
+qtconsole = "*"
+
+[[package]]
+name = "jupyter-client"
+version = "6.2.0"
+description = "Jupyter protocol implementation and client libraries"
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+jupyter-core = ">=4.6.0"
+nest-asyncio = ">=1.5"
+python-dateutil = ">=2.1"
+pyzmq = ">=13"
+tornado = ">=4.1"
+traitlets = "*"
+
+[package.extras]
+doc = ["sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"]
+test = ["async-generator", "ipykernel", "ipython", "mock", "pytest-asyncio", "pytest-timeout", "pytest", "mypy", "pre-commit", "jedi (<0.18)"]
+
+[[package]]
+name = "jupyter-console"
+version = "6.4.0"
+description = "Jupyter terminal console"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+ipykernel = "*"
+ipython = "*"
+jupyter-client = "*"
+prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
+pygments = "*"
+
+[package.extras]
+test = ["pexpect"]
+
+[[package]]
+name = "jupyter-core"
+version = "4.7.1"
+description = "Jupyter core package. A base package on which Jupyter projects rely."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+traitlets = "*"
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.1.2"
+description = "Pygments theme using JupyterLab CSS variables"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pygments = ">=2.4.1,<3"
+
+[[package]]
+name = "jupyterlab-widgets"
+version = "1.0.0"
+description = "A JupyterLab extension."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "kiwisolver"
+version = "1.3.1"
+description = "A fast implementation of the Cassowary constraint solver"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "lazy-object-proxy"
+version = "1.6.0"
+description = "A fast and thorough lazy object proxy."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[[package]]
+name = "markupsafe"
+version = "2.0.0"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "matplotlib"
+version = "3.4.2"
+description = "Python plotting package"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cycler = ">=0.10"
+kiwisolver = ">=1.0.1"
+numpy = ">=1.16"
+pillow = ">=6.2.0"
+pyparsing = ">=2.2.1"
+python-dateutil = ">=2.7"
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.2"
+description = "Inline Matplotlib backend for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+traitlets = "*"
+
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "mistune"
+version = "0.8.4"
+description = "The fastest markdown parser in pure Python"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "nbclient"
+version = "0.5.3"
+description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor."
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+async-generator = "*"
+jupyter-client = ">=6.1.5"
+nbformat = ">=5.0"
+nest-asyncio = "*"
+traitlets = ">=4.2"
+
+[package.extras]
+dev = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"]
+sphinx = ["Sphinx (>=1.7)", "sphinx-book-theme", "mock", "moto", "myst-parser"]
+test = ["codecov", "coverage", "ipython", "ipykernel", "ipywidgets", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "check-manifest", "flake8", "mypy", "tox", "bumpversion", "xmltodict", "pip (>=18.1)", "wheel (>=0.31.0)", "setuptools (>=38.6.0)", "twine (>=1.11.0)", "black"]
+
+[[package]]
+name = "nbconvert"
+version = "6.0.7"
+description = "Converting Jupyter Notebooks"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+bleach = "*"
+defusedxml = "*"
+entrypoints = ">=0.2.2"
+jinja2 = ">=2.4"
+jupyter-core = "*"
+jupyterlab-pygments = "*"
+mistune = ">=0.8.1,<2"
+nbclient = ">=0.5.0,<0.6.0"
+nbformat = ">=4.4"
+pandocfilters = ">=1.4.1"
+pygments = ">=2.4.1"
+testpath = "*"
+traitlets = ">=4.2"
+
+[package.extras]
+all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
+docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
+serve = ["tornado (>=4.0)"]
+test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)"]
+webpdf = ["pyppeteer (==0.2.2)"]
+
+[[package]]
+name = "nbformat"
+version = "5.1.3"
+description = "The Jupyter Notebook format"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+ipython-genutils = "*"
+jsonschema = ">=2.4,<2.5.0 || >2.5.0"
+jupyter-core = "*"
+traitlets = ">=4.1"
+
+[package.extras]
+fast = ["fastjsonschema"]
+test = ["check-manifest", "fastjsonschema", "testpath", "pytest", "pytest-cov"]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.5.1"
+description = "Patch asyncio to allow nested event loops"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "notebook"
+version = "6.3.0"
+description = "A web-based notebook environment for interactive computing"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+argon2-cffi = "*"
+ipykernel = "*"
+ipython-genutils = "*"
+jinja2 = "*"
+jupyter-client = ">=5.3.4"
+jupyter-core = ">=4.6.1"
+nbconvert = "*"
+nbformat = "*"
+prometheus-client = "*"
+pyzmq = ">=17"
+Send2Trash = ">=1.5.0"
+terminado = ">=0.8.3"
+tornado = ">=6.1"
+traitlets = ">=4.2.1"
+
+[package.extras]
+docs = ["sphinx", "nbsphinx", "sphinxcontrib-github-alt", "sphinx-rtd-theme"]
+json-logging = ["json-logging"]
+test = ["pytest", "coverage", "requests", "nbval", "selenium", "pytest-cov", "requests-unixsocket"]
+
+[[package]]
+name = "numpy"
+version = "1.20.3"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "packaging"
+version = "20.9"
+description = "Core utilities for Python packages"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+pyparsing = ">=2.0.2"
+
+[[package]]
+name = "pandas"
+version = "1.2.4"
+description = "Powerful data structures for data analysis, time series, and statistics"
+category = "main"
+optional = false
+python-versions = ">=3.7.1"
+
+[package.dependencies]
+numpy = ">=1.16.5"
+python-dateutil = ">=2.7.3"
+pytz = ">=2017.3"
+
+[package.extras]
+test = ["pytest (>=5.0.1)", "pytest-xdist", "hypothesis (>=3.58)"]
+
+[[package]]
+name = "pandocfilters"
+version = "1.4.3"
+description = "Utilities for writing pandoc filters in python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "parso"
+version = "0.8.2"
+description = "A Python Parser"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["docopt", "pytest (<6.0.0)"]
+
+[[package]]
+name = "pathspec"
+version = "0.8.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "pexpect"
+version = "4.8.0"
+description = "Pexpect allows easy control of interactive console applications."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+name = "pickleshare"
+version = "0.7.5"
+description = "Tiny 'shelve'-like database with concurrency support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pillow"
+version = "8.2.0"
+description = "Python Imaging Library (Fork)"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
+name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+
+[[package]]
+name = "prometheus-client"
+version = "0.10.1"
+description = "Python client for the Prometheus monitoring system."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.extras]
+twisted = ["twisted"]
+
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.18"
+description = "Library for building powerful interactive command lines in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6.1"
+
+[package.dependencies]
+wcwidth = "*"
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+description = "Run a subprocess in a pseudo terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "py"
+version = "1.10.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pycparser"
+version = "2.20"
+description = "C parser in Python"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[[package]]
+name = "pygments"
+version = "2.9.0"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "pylint"
+version = "2.8.2"
+description = "python code static checker"
+category = "dev"
+optional = false
+python-versions = "~=3.6"
+
+[package.dependencies]
+astroid = ">=2.5.6,<2.7"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+isort = ">=4.2.5,<6"
+mccabe = ">=0.6,<0.7"
+toml = ">=0.7.1"
+
+[[package]]
+name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "main"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "pyrsistent"
+version = "0.17.3"
+description = "Persistent/Functional/Immutable data structures"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "pytest"
+version = "6.2.4"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
+attrs = ">=19.2.0"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<1.0.0a1"
+py = ">=1.8.2"
+toml = "*"
+
+[package.extras]
+testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "2.11.1"
+description = "Pytest plugin for measuring coverage."
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+coverage = ">=5.2.1"
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.1"
+description = "Extensions to the standard Python datetime module"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2021.1"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pywin32"
+version = "300"
+description = "Python for Window Extensions"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pywinpty"
+version = "0.5.7"
+description = "Python bindings for the winpty library"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "pyzmq"
+version = "22.0.3"
+description = "Python bindings for 0MQ"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+cffi = {version = "*", markers = "implementation_name == \"pypy\""}
+py = {version = "*", markers = "implementation_name == \"pypy\""}
+
+[[package]]
+name = "qtconsole"
+version = "5.1.0"
+description = "Jupyter Qt console"
+category = "dev"
+optional = false
+python-versions = ">= 3.6"
+
+[package.dependencies]
+ipykernel = ">=4.1"
+ipython-genutils = "*"
+jupyter-client = ">=4.1"
+jupyter-core = "*"
+pygments = "*"
+pyzmq = ">=17.1"
+qtpy = "*"
+traitlets = "*"
+
+[package.extras]
+doc = ["Sphinx (>=1.3)"]
+test = ["flaky", "pytest", "pytest-qt"]
+
+[[package]]
+name = "qtpy"
+version = "1.9.0"
+description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5, PyQt4 and PySide) and additional custom QWidgets."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "rasterio"
+version = "1.2.3"
+description = "Fast and direct raster I/O for use with Numpy and SciPy"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+affine = "*"
+attrs = "*"
+certifi = "*"
+click = ">=4.0,<8"
+click-plugins = "*"
+cligj = ">=0.5"
+numpy = "*"
+snuggs = ">=1.4.1"
+
+[package.extras]
+all = ["pytest-cov (>=2.2.0)", "ipython (>=2.0)", "matplotlib", "boto3 (>=1.2.4)", "numpydoc", "packaging", "hypothesis", "shapely", "ghp-import", "sphinx-rtd-theme", "sphinx", "pytest (>=2.8.2)"]
+docs = ["ghp-import", "numpydoc", "sphinx", "sphinx-rtd-theme"]
+ipython = ["ipython (>=2.0)"]
+plot = ["matplotlib"]
+s3 = ["boto3 (>=1.2.4)"]
+test = ["boto3 (>=1.2.4)", "hypothesis", "packaging", "pytest-cov (>=2.2.0)", "pytest (>=2.8.2)", "shapely"]
+
+[[package]]
+name = "regex"
+version = "2021.4.4"
+description = "Alternative regular expression module, to replace re."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "send2trash"
+version = "1.5.0"
+description = "Send file to trash natively under Mac OS X, Windows and Linux."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "snuggs"
+version = "1.4.7"
+description = "Snuggs are s-expressions for Numpy"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+numpy = "*"
+pyparsing = ">=2.1.6"
+
+[package.extras]
+test = ["pytest", "hypothesis"]
+
+[[package]]
+name = "terminado"
+version = "0.9.5"
+description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+ptyprocess = {version = "*", markers = "os_name != \"nt\""}
+pywinpty = {version = ">=0.5,<1", markers = "os_name == \"nt\""}
+tornado = ">=4"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "testpath"
+version = "0.4.4"
+description = "Test utilities for code working with files and commands"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["pathlib2"]
+
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
+[[package]]
+name = "tornado"
+version = "6.1"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+category = "dev"
+optional = false
+python-versions = ">= 3.5"
+
+[[package]]
+name = "traitlets"
+version = "5.0.5"
+description = "Traitlets Python configuration system"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+ipython-genutils = "*"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "typed-ast"
+version = "1.4.3"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "typing-extensions"
+version = "3.10.0.0"
+description = "Backported and Experimental Type Hints for Python 3.5+"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "widgetsnbextension"
+version = "3.5.1"
+description = "IPython HTML widgets for Jupyter"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+notebook = ">=4.4.1"
+
+[[package]]
+name = "wrapt"
+version = "1.12.1"
+description = "Module for decorators, wrappers and monkey patching."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "zipp"
+version = "3.4.1"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.7.7"
+content-hash = "174c757c6c5140a9ff2e08c5b45f002240532afa65a57a2e52b50e3da3268bf3"
+
+[metadata.files]
+affine = [
+    {file = "affine-2.3.0-py2.py3-none-any.whl", hash = "sha256:34b05b070d954c382e56f02c207a372d8a32621a87653cc30cdd31cd7f65799f"},
+    {file = "affine-2.3.0.tar.gz", hash = "sha256:2e045def1aa29e613c42e801a7e10e0b9bacfed1a7c6af0cadf8843530a15102"},
+]
+appdirs = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+appnope = [
+    {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"},
+    {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"},
+]
+argon2-cffi = [
+    {file = "argon2-cffi-20.1.0.tar.gz", hash = "sha256:d8029b2d3e4b4cea770e9e5a0104dd8fa185c1724a0f01528ae4826a6d25f97d"},
+    {file = "argon2_cffi-20.1.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6ea92c980586931a816d61e4faf6c192b4abce89aa767ff6581e6ddc985ed003"},
+    {file = "argon2_cffi-20.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:05a8ac07c7026542377e38389638a8a1e9b78f1cd8439cd7493b39f08dd75fbf"},
+    {file = "argon2_cffi-20.1.0-cp27-cp27m-win32.whl", hash = "sha256:0bf066bc049332489bb2d75f69216416329d9dc65deee127152caeb16e5ce7d5"},
+    {file = "argon2_cffi-20.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:57358570592c46c420300ec94f2ff3b32cbccd10d38bdc12dc6979c4a8484fbc"},
+    {file = "argon2_cffi-20.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7d455c802727710e9dfa69b74ccaab04568386ca17b0ad36350b622cd34606fe"},
+    {file = "argon2_cffi-20.1.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:b160416adc0f012fb1f12588a5e6954889510f82f698e23ed4f4fa57f12a0647"},
+    {file = "argon2_cffi-20.1.0-cp35-cp35m-win32.whl", hash = "sha256:9bee3212ba4f560af397b6d7146848c32a800652301843df06b9e8f68f0f7361"},
+    {file = "argon2_cffi-20.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:392c3c2ef91d12da510cfb6f9bae52512a4552573a9e27600bdb800e05905d2b"},
+    {file = "argon2_cffi-20.1.0-cp36-cp36m-win32.whl", hash = "sha256:ba7209b608945b889457f949cc04c8e762bed4fe3fec88ae9a6b7765ae82e496"},
+    {file = "argon2_cffi-20.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:da7f0445b71db6d3a72462e04f36544b0de871289b0bc8a7cc87c0f5ec7079fa"},
+    {file = "argon2_cffi-20.1.0-cp37-abi3-macosx_10_6_intel.whl", hash = "sha256:cc0e028b209a5483b6846053d5fd7165f460a1f14774d79e632e75e7ae64b82b"},
+    {file = "argon2_cffi-20.1.0-cp37-cp37m-win32.whl", hash = "sha256:18dee20e25e4be86680b178b35ccfc5d495ebd5792cd00781548d50880fee5c5"},
+    {file = "argon2_cffi-20.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6678bb047373f52bcff02db8afab0d2a77d83bde61cfecea7c5c62e2335cb203"},
+    {file = "argon2_cffi-20.1.0-cp38-cp38-win32.whl", hash = "sha256:77e909cc756ef81d6abb60524d259d959bab384832f0c651ed7dcb6e5ccdbb78"},
+    {file = "argon2_cffi-20.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:9dfd5197852530294ecb5795c97a823839258dfd5eb9420233c7cfedec2058f2"},
+    {file = "argon2_cffi-20.1.0-cp39-cp39-win32.whl", hash = "sha256:e2db6e85c057c16d0bd3b4d2b04f270a7467c147381e8fd73cbbe5bc719832be"},
+    {file = "argon2_cffi-20.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a84934bd818e14a17943de8099d41160da4a336bcc699bb4c394bbb9b94bd32"},
+]
+astroid = [
+    {file = "astroid-2.5.6-py3-none-any.whl", hash = "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e"},
+    {file = "astroid-2.5.6.tar.gz", hash = "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"},
+]
+async-generator = [
+    {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"},
+    {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"},
+]
+atomicwrites = [
+    {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
+    {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
+]
+attrs = [
+    {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+    {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+]
+backcall = [
+    {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+    {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
+black = [
+    {file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"},
+    {file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"},
+]
+bleach = [
+    {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"},
+    {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"},
+]
+certifi = [
+    {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
+    {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
+]
+cffi = [
+    {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
+    {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
+    {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"},
+    {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"},
+    {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"},
+    {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"},
+    {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"},
+    {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"},
+    {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"},
+    {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"},
+    {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"},
+    {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"},
+    {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"},
+    {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"},
+    {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"},
+    {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"},
+    {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"},
+    {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"},
+    {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"},
+    {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"},
+    {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"},
+    {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"},
+    {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"},
+    {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"},
+    {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"},
+    {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"},
+    {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"},
+    {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"},
+    {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"},
+    {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"},
+    {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"},
+    {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"},
+    {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"},
+    {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"},
+    {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"},
+    {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"},
+    {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"},
+]
+click = [
+    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
+    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+]
+click-plugins = [
+    {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
+    {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"},
+]
+cligj = [
+    {file = "cligj-0.7.1-py3-none-any.whl", hash = "sha256:07171c1e287f45511f97df4ea071abc5d19924153413d5683a8e4866369bc676"},
+    {file = "cligj-0.7.1.tar.gz", hash = "sha256:b2f1f7247d59a5387bd3013a08b9ed6829e96fafa4a6e6292341efdb46fe6220"},
+]
+colorama = [
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+coverage = [
+    {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
+    {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
+    {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
+    {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
+    {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
+    {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
+    {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
+    {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
+    {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
+    {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
+    {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
+    {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
+    {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
+    {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
+    {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
+    {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
+    {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
+    {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
+    {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
+    {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
+    {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
+    {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
+    {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
+    {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
+    {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
+    {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
+    {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
+    {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
+    {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
+    {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
+    {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
+    {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
+    {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
+]
+cycler = [
+    {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
+    {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
+]
+decorator = [
+    {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"},
+    {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"},
+]
+defusedxml = [
+    {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
+    {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
+]
+entrypoints = [
+    {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
+    {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
+]
+importlib-metadata = [
+    {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"},
+    {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"},
+]
+iniconfig = [
+    {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
+    {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
+]
+ipykernel = [
+    {file = "ipykernel-5.5.5-py3-none-any.whl", hash = "sha256:29eee66548ee7c2edb7941de60c0ccf0a7a8dd957341db0a49c5e8e6a0fcb712"},
+    {file = "ipykernel-5.5.5.tar.gz", hash = "sha256:e976751336b51082a89fc2099fb7f96ef20f535837c398df6eab1283c2070884"},
+]
+ipython = [
+    {file = "ipython-7.23.1-py3-none-any.whl", hash = "sha256:f78c6a3972dde1cc9e4041cbf4de583546314ba52d3c97208e5b6b2221a9cb7d"},
+    {file = "ipython-7.23.1.tar.gz", hash = "sha256:714810a5c74f512b69d5f3b944c86e592cee0a5fb9c728e582f074610f6cf038"},
+]
+ipython-genutils = [
+    {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
+    {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
+]
+ipywidgets = [
+    {file = "ipywidgets-7.6.3-py2.py3-none-any.whl", hash = "sha256:e6513cfdaf5878de30f32d57f6dc2474da395a2a2991b94d487406c0ab7f55ca"},
+    {file = "ipywidgets-7.6.3.tar.gz", hash = "sha256:9f1a43e620530f9e570e4a493677d25f08310118d315b00e25a18f12913c41f0"},
+]
+isort = [
+    {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"},
+    {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"},
+]
+jedi = [
+    {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"},
+    {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"},
+]
+jinja2 = [
+    {file = "Jinja2-3.0.0-py3-none-any.whl", hash = "sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6"},
+    {file = "Jinja2-3.0.0.tar.gz", hash = "sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"},
+]
+jsonschema = [
+    {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"},
+    {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
+]
+jupyter = [
+    {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"},
+    {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"},
+    {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"},
+]
+jupyter-client = [
+    {file = "jupyter_client-6.2.0-py3-none-any.whl", hash = "sha256:9715152067e3f7ea3b56f341c9a0f9715c8c7cc316ee0eb13c3c84f5ca0065f5"},
+    {file = "jupyter_client-6.2.0.tar.gz", hash = "sha256:e2ab61d79fbf8b56734a4c2499f19830fbd7f6fefb3e87868ef0545cb3c17eb9"},
+]
+jupyter-console = [
+    {file = "jupyter_console-6.4.0-py3-none-any.whl", hash = "sha256:7799c4ea951e0e96ba8260575423cb323ea5a03fcf5503560fa3e15748869e27"},
+    {file = "jupyter_console-6.4.0.tar.gz", hash = "sha256:242248e1685039cd8bff2c2ecb7ce6c1546eb50ee3b08519729e6e881aec19c7"},
+]
+jupyter-core = [
+    {file = "jupyter_core-4.7.1-py3-none-any.whl", hash = "sha256:8c6c0cac5c1b563622ad49321d5ec47017bd18b94facb381c6973a0486395f8e"},
+    {file = "jupyter_core-4.7.1.tar.gz", hash = "sha256:79025cb3225efcd36847d0840f3fc672c0abd7afd0de83ba8a1d3837619122b4"},
+]
+jupyterlab-pygments = [
+    {file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"},
+    {file = "jupyterlab_pygments-0.1.2.tar.gz", hash = "sha256:cfcda0873626150932f438eccf0f8bf22bfa92345b814890ab360d666b254146"},
+]
+jupyterlab-widgets = [
+    {file = "jupyterlab_widgets-1.0.0-py3-none-any.whl", hash = "sha256:caeaf3e6103180e654e7d8d2b81b7d645e59e432487c1d35a41d6d3ee56b3fef"},
+    {file = "jupyterlab_widgets-1.0.0.tar.gz", hash = "sha256:5c1a29a84d3069208cb506b10609175b249b6486d6b1cbae8fcde2a11584fb78"},
+]
+kiwisolver = [
+    {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"},
+    {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"},
+    {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"},
+    {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"},
+    {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"},
+    {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"},
+    {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"},
+    {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"},
+    {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"},
+]
+lazy-object-proxy = [
+    {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"},
+    {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"},
+    {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"},
+    {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"},
+    {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"},
+    {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"},
+]
+markupsafe = [
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"},
+    {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"},
+    {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"},
+    {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"},
+    {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"},
+    {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"},
+]
+matplotlib = [
+    {file = "matplotlib-3.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c541ee5a3287efe066bbe358320853cf4916bc14c00c38f8f3d8d75275a405a9"},
+    {file = "matplotlib-3.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3a5c18dbd2c7c366da26a4ad1462fe3e03a577b39e3b503bbcf482b9cdac093c"},
+    {file = "matplotlib-3.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a9d8cb5329df13e0cdaa14b3b43f47b5e593ec637f13f14db75bb16e46178b05"},
+    {file = "matplotlib-3.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:7ad19f3fb6145b9eb41c08e7cbb9f8e10b91291396bee21e9ce761bb78df63ec"},
+    {file = "matplotlib-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:7a58f3d8fe8fac3be522c79d921c9b86e090a59637cb88e3bc51298d7a2c862a"},
+    {file = "matplotlib-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6382bc6e2d7e481bcd977eb131c31dee96e0fb4f9177d15ec6fb976d3b9ace1a"},
+    {file = "matplotlib-3.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a6a44f27aabe720ec4fd485061e8a35784c2b9ffa6363ad546316dfc9cea04e"},
+    {file = "matplotlib-3.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1c1779f7ab7d8bdb7d4c605e6ffaa0614b3e80f1e3c8ccf7b9269a22dbc5986b"},
+    {file = "matplotlib-3.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5826f56055b9b1c80fef82e326097e34dc4af8c7249226b7dd63095a686177d1"},
+    {file = "matplotlib-3.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0bea5ec5c28d49020e5d7923c2725b837e60bc8be99d3164af410eb4b4c827da"},
+    {file = "matplotlib-3.4.2-cp38-cp38-win32.whl", hash = "sha256:6475d0209024a77f869163ec3657c47fed35d9b6ed8bccba8aa0f0099fbbdaa8"},
+    {file = "matplotlib-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:21b31057bbc5e75b08e70a43cefc4c0b2c2f1b1a850f4a0f7af044eb4163086c"},
+    {file = "matplotlib-3.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b26535b9de85326e6958cdef720ecd10bcf74a3f4371bf9a7e5b2e659c17e153"},
+    {file = "matplotlib-3.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:32fa638cc10886885d1ca3d409d4473d6a22f7ceecd11322150961a70fab66dd"},
+    {file = "matplotlib-3.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:956c8849b134b4a343598305a3ca1bdd3094f01f5efc8afccdebeffe6b315247"},
+    {file = "matplotlib-3.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:85f191bb03cb1a7b04b5c2cca4792bef94df06ef473bc49e2818105671766fee"},
+    {file = "matplotlib-3.4.2-cp39-cp39-win32.whl", hash = "sha256:b1d5a2cedf5de05567c441b3a8c2651fbde56df08b82640e7f06c8cd91e201f6"},
+    {file = "matplotlib-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:df815378a754a7edd4559f8c51fc7064f779a74013644a7f5ac7a0c31f875866"},
+    {file = "matplotlib-3.4.2.tar.gz", hash = "sha256:d8d994cefdff9aaba45166eb3de4f5211adb4accac85cbf97137e98f26ea0219"},
+]
+matplotlib-inline = [
+    {file = "matplotlib-inline-0.1.2.tar.gz", hash = "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"},
+    {file = "matplotlib_inline-0.1.2-py3-none-any.whl", hash = "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811"},
+]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
+mistune = [
+    {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
+    {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
+]
+mypy-extensions = [
+    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
+nbclient = [
+    {file = "nbclient-0.5.3-py3-none-any.whl", hash = "sha256:e79437364a2376892b3f46bedbf9b444e5396cfb1bc366a472c37b48e9551500"},
+    {file = "nbclient-0.5.3.tar.gz", hash = "sha256:db17271330c68c8c88d46d72349e24c147bb6f34ec82d8481a8f025c4d26589c"},
+]
+nbconvert = [
+    {file = "nbconvert-6.0.7-py3-none-any.whl", hash = "sha256:39e9f977920b203baea0be67eea59f7b37a761caa542abe80f5897ce3cf6311d"},
+    {file = "nbconvert-6.0.7.tar.gz", hash = "sha256:cbbc13a86dfbd4d1b5dee106539de0795b4db156c894c2c5dc382062bbc29002"},
+]
+nbformat = [
+    {file = "nbformat-5.1.3-py3-none-any.whl", hash = "sha256:eb8447edd7127d043361bc17f2f5a807626bc8e878c7709a1c647abda28a9171"},
+    {file = "nbformat-5.1.3.tar.gz", hash = "sha256:b516788ad70771c6250977c1374fcca6edebe6126fd2adb5a69aa5c2356fd1c8"},
+]
+nest-asyncio = [
+    {file = "nest_asyncio-1.5.1-py3-none-any.whl", hash = "sha256:76d6e972265063fe92a90b9cc4fb82616e07d586b346ed9d2c89a4187acea39c"},
+    {file = "nest_asyncio-1.5.1.tar.gz", hash = "sha256:afc5a1c515210a23c461932765691ad39e8eba6551c055ac8d5546e69250d0aa"},
+]
+notebook = [
+    {file = "notebook-6.3.0-py3-none-any.whl", hash = "sha256:cb271af1e8134e3d6fc6d458bdc79c40cbfc84c1eb036a493f216d58f0880e92"},
+    {file = "notebook-6.3.0.tar.gz", hash = "sha256:cbc9398d6c81473e9cdb891d2cae9c0d3718fca289dda6d26df5cb660fcadc7d"},
+]
+numpy = [
+    {file = "numpy-1.20.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:70eb5808127284c4e5c9e836208e09d685a7978b6a216db85960b1a112eeace8"},
+    {file = "numpy-1.20.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6ca2b85a5997dabc38301a22ee43c82adcb53ff660b89ee88dded6b33687e1d8"},
+    {file = "numpy-1.20.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c5bf0e132acf7557fc9bb8ded8b53bbbbea8892f3c9a1738205878ca9434206a"},
+    {file = "numpy-1.20.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db250fd3e90117e0312b611574cd1b3f78bec046783195075cbd7ba9c3d73f16"},
+    {file = "numpy-1.20.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:637d827248f447e63585ca3f4a7d2dfaa882e094df6cfa177cc9cf9cd6cdf6d2"},
+    {file = "numpy-1.20.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8b7bb4b9280da3b2856cb1fc425932f46fba609819ee1c62256f61799e6a51d2"},
+    {file = "numpy-1.20.3-cp37-cp37m-win32.whl", hash = "sha256:67d44acb72c31a97a3d5d33d103ab06d8ac20770e1c5ad81bdb3f0c086a56cf6"},
+    {file = "numpy-1.20.3-cp37-cp37m-win_amd64.whl", hash = "sha256:43909c8bb289c382170e0282158a38cf306a8ad2ff6dfadc447e90f9961bef43"},
+    {file = "numpy-1.20.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f1452578d0516283c87608a5a5548b0cdde15b99650efdfd85182102ef7a7c17"},
+    {file = "numpy-1.20.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6e51534e78d14b4a009a062641f465cfaba4fdcb046c3ac0b1f61dd97c861b1b"},
+    {file = "numpy-1.20.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e515c9a93aebe27166ec9593411c58494fa98e5fcc219e47260d9ab8a1cc7f9f"},
+    {file = "numpy-1.20.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1c09247ccea742525bdb5f4b5ceeacb34f95731647fe55774aa36557dbb5fa4"},
+    {file = "numpy-1.20.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66fbc6fed94a13b9801fb70b96ff30605ab0a123e775a5e7a26938b717c5d71a"},
+    {file = "numpy-1.20.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea9cff01e75a956dbee133fa8e5b68f2f92175233de2f88de3a682dd94deda65"},
+    {file = "numpy-1.20.3-cp38-cp38-win32.whl", hash = "sha256:f39a995e47cb8649673cfa0579fbdd1cdd33ea497d1728a6cb194d6252268e48"},
+    {file = "numpy-1.20.3-cp38-cp38-win_amd64.whl", hash = "sha256:1676b0a292dd3c99e49305a16d7a9f42a4ab60ec522eac0d3dd20cdf362ac010"},
+    {file = "numpy-1.20.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:830b044f4e64a76ba71448fce6e604c0fc47a0e54d8f6467be23749ac2cbd2fb"},
+    {file = "numpy-1.20.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55b745fca0a5ab738647d0e4db099bd0a23279c32b31a783ad2ccea729e632df"},
+    {file = "numpy-1.20.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5d050e1e4bc9ddb8656d7b4f414557720ddcca23a5b88dd7cff65e847864c400"},
+    {file = "numpy-1.20.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9c65473ebc342715cb2d7926ff1e202c26376c0dcaaee85a1fd4b8d8c1d3b2f"},
+    {file = "numpy-1.20.3-cp39-cp39-win32.whl", hash = "sha256:16f221035e8bd19b9dc9a57159e38d2dd060b48e93e1d843c49cb370b0f415fd"},
+    {file = "numpy-1.20.3-cp39-cp39-win_amd64.whl", hash = "sha256:6690080810f77485667bfbff4f69d717c3be25e5b11bb2073e76bb3f578d99b4"},
+    {file = "numpy-1.20.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e465afc3b96dbc80cf4a5273e5e2b1e3451286361b4af70ce1adb2984d392f9"},
+    {file = "numpy-1.20.3.zip", hash = "sha256:e55185e51b18d788e49fe8305fd73ef4470596b33fc2c1ceb304566b99c71a69"},
+]
+packaging = [
+    {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
+    {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
+]
+pandas = [
+    {file = "pandas-1.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c601c6fdebc729df4438ec1f62275d6136a0dd14d332fc0e8ce3f7d2aadb4dd6"},
+    {file = "pandas-1.2.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8d4c74177c26aadcfb4fd1de6c1c43c2bf822b3e0fc7a9b409eeaf84b3e92aaa"},
+    {file = "pandas-1.2.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b730add5267f873b3383c18cac4df2527ac4f0f0eed1c6cf37fcb437e25cf558"},
+    {file = "pandas-1.2.4-cp37-cp37m-win32.whl", hash = "sha256:2cb7e8f4f152f27dc93f30b5c7a98f6c748601ea65da359af734dd0cf3fa733f"},
+    {file = "pandas-1.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:2111c25e69fa9365ba80bbf4f959400054b2771ac5d041ed19415a8b488dc70a"},
+    {file = "pandas-1.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:167693a80abc8eb28051fbd184c1b7afd13ce2c727a5af47b048f1ea3afefff4"},
+    {file = "pandas-1.2.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:612add929bf3ba9d27b436cc8853f5acc337242d6b584203f207e364bb46cb12"},
+    {file = "pandas-1.2.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:971e2a414fce20cc5331fe791153513d076814d30a60cd7348466943e6e909e4"},
+    {file = "pandas-1.2.4-cp38-cp38-win32.whl", hash = "sha256:68d7baa80c74aaacbed597265ca2308f017859123231542ff8a5266d489e1858"},
+    {file = "pandas-1.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:bd659c11a4578af740782288cac141a322057a2e36920016e0fc7b25c5a4b686"},
+    {file = "pandas-1.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9db70ffa8b280bb4de83f9739d514cd0735825e79eef3a61d312420b9f16b758"},
+    {file = "pandas-1.2.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:298f0553fd3ba8e002c4070a723a59cdb28eda579f3e243bc2ee397773f5398b"},
+    {file = "pandas-1.2.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52d2472acbb8a56819a87aafdb8b5b6d2b3386e15c95bde56b281882529a7ded"},
+    {file = "pandas-1.2.4-cp39-cp39-win32.whl", hash = "sha256:d0877407359811f7b853b548a614aacd7dea83b0c0c84620a9a643f180060950"},
+    {file = "pandas-1.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:2b063d41803b6a19703b845609c0b700913593de067b552a8b24dd8eeb8c9895"},
+    {file = "pandas-1.2.4.tar.gz", hash = "sha256:649ecab692fade3cbfcf967ff936496b0cfba0af00a55dfaacd82bdda5cb2279"},
+]
+pandocfilters = [
+    {file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"},
+]
+parso = [
+    {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"},
+    {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"},
+]
+pathspec = [
+    {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
+    {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
+]
+pexpect = [
+    {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+    {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+    {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+    {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
+pillow = [
+    {file = "Pillow-8.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9"},
+    {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b"},
+    {file = "Pillow-8.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b"},
+    {file = "Pillow-8.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9"},
+    {file = "Pillow-8.2.0-cp36-cp36m-win32.whl", hash = "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727"},
+    {file = "Pillow-8.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f"},
+    {file = "Pillow-8.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d"},
+    {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a"},
+    {file = "Pillow-8.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9"},
+    {file = "Pillow-8.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388"},
+    {file = "Pillow-8.2.0-cp37-cp37m-win32.whl", hash = "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5"},
+    {file = "Pillow-8.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2"},
+    {file = "Pillow-8.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4"},
+    {file = "Pillow-8.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812"},
+    {file = "Pillow-8.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178"},
+    {file = "Pillow-8.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb"},
+    {file = "Pillow-8.2.0-cp38-cp38-win32.whl", hash = "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232"},
+    {file = "Pillow-8.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797"},
+    {file = "Pillow-8.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5"},
+    {file = "Pillow-8.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484"},
+    {file = "Pillow-8.2.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602"},
+    {file = "Pillow-8.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"},
+    {file = "Pillow-8.2.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef"},
+    {file = "Pillow-8.2.0-cp39-cp39-win32.whl", hash = "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713"},
+    {file = "Pillow-8.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c"},
+    {file = "Pillow-8.2.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9"},
+    {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9"},
+    {file = "Pillow-8.2.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c"},
+    {file = "Pillow-8.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b"},
+    {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4"},
+    {file = "Pillow-8.2.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120"},
+    {file = "Pillow-8.2.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e"},
+    {file = "Pillow-8.2.0.tar.gz", hash = "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1"},
+]
+pluggy = [
+    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
+    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
+]
+prometheus-client = [
+    {file = "prometheus_client-0.10.1-py2.py3-none-any.whl", hash = "sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa"},
+    {file = "prometheus_client-0.10.1.tar.gz", hash = "sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"},
+]
+prompt-toolkit = [
+    {file = "prompt_toolkit-3.0.18-py3-none-any.whl", hash = "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04"},
+    {file = "prompt_toolkit-3.0.18.tar.gz", hash = "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"},
+]
+ptyprocess = [
+    {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+    {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+]
+py = [
+    {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
+    {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
+]
+pycparser = [
+    {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
+    {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
+]
+pygments = [
+    {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
+    {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
+]
+pylint = [
+    {file = "pylint-2.8.2-py3-none-any.whl", hash = "sha256:f7e2072654a6b6afdf5e2fb38147d3e2d2d43c89f648637baab63e026481279b"},
+    {file = "pylint-2.8.2.tar.gz", hash = "sha256:586d8fa9b1891f4b725f587ef267abe2a1bad89d6b184520c7f07a253dd6e217"},
+]
+pyparsing = [
+    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
+    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+]
+pyrsistent = [
+    {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"},
+]
+pytest = [
+    {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
+    {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
+]
+pytest-cov = [
+    {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"},
+    {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"},
+]
+python-dateutil = [
+    {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
+    {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+]
+pytz = [
+    {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
+    {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
+]
+pywin32 = [
+    {file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"},
+    {file = "pywin32-300-cp35-cp35m-win_amd64.whl", hash = "sha256:350c5644775736351b77ba68da09a39c760d75d2467ecec37bd3c36a94fbed64"},
+    {file = "pywin32-300-cp36-cp36m-win32.whl", hash = "sha256:a3b4c48c852d4107e8a8ec980b76c94ce596ea66d60f7a697582ea9dce7e0db7"},
+    {file = "pywin32-300-cp36-cp36m-win_amd64.whl", hash = "sha256:27a30b887afbf05a9cbb05e3ffd43104a9b71ce292f64a635389dbad0ed1cd85"},
+    {file = "pywin32-300-cp37-cp37m-win32.whl", hash = "sha256:d7e8c7efc221f10d6400c19c32a031add1c4a58733298c09216f57b4fde110dc"},
+    {file = "pywin32-300-cp37-cp37m-win_amd64.whl", hash = "sha256:8151e4d7a19262d6694162d6da85d99a16f8b908949797fd99c83a0bfaf5807d"},
+    {file = "pywin32-300-cp38-cp38-win32.whl", hash = "sha256:fbb3b1b0fbd0b4fc2a3d1d81fe0783e30062c1abed1d17c32b7879d55858cfae"},
+    {file = "pywin32-300-cp38-cp38-win_amd64.whl", hash = "sha256:60a8fa361091b2eea27f15718f8eb7f9297e8d51b54dbc4f55f3d238093d5190"},
+    {file = "pywin32-300-cp39-cp39-win32.whl", hash = "sha256:638b68eea5cfc8def537e43e9554747f8dee786b090e47ead94bfdafdb0f2f50"},
+    {file = "pywin32-300-cp39-cp39-win_amd64.whl", hash = "sha256:b1609ce9bd5c411b81f941b246d683d6508992093203d4eb7f278f4ed1085c3f"},
+]
+pywinpty = [
+    {file = "pywinpty-0.5.7-cp27-cp27m-win32.whl", hash = "sha256:b358cb552c0f6baf790de375fab96524a0498c9df83489b8c23f7f08795e966b"},
+    {file = "pywinpty-0.5.7-cp27-cp27m-win_amd64.whl", hash = "sha256:1e525a4de05e72016a7af27836d512db67d06a015aeaf2fa0180f8e6a039b3c2"},
+    {file = "pywinpty-0.5.7-cp35-cp35m-win32.whl", hash = "sha256:2740eeeb59297593a0d3f762269b01d0285c1b829d6827445fcd348fb47f7e70"},
+    {file = "pywinpty-0.5.7-cp35-cp35m-win_amd64.whl", hash = "sha256:33df97f79843b2b8b8bc5c7aaf54adec08cc1bae94ee99dfb1a93c7a67704d95"},
+    {file = "pywinpty-0.5.7-cp36-cp36m-win32.whl", hash = "sha256:e854211df55d107f0edfda8a80b39dfc87015bef52a8fe6594eb379240d81df2"},
+    {file = "pywinpty-0.5.7-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd838de92de1d4ebf0dce9d4d5e4fc38d0b7b1de837947a18b57a882f219139"},
+    {file = "pywinpty-0.5.7-cp37-cp37m-win32.whl", hash = "sha256:5fb2c6c6819491b216f78acc2c521b9df21e0f53b9a399d58a5c151a3c4e2a2d"},
+    {file = "pywinpty-0.5.7-cp37-cp37m-win_amd64.whl", hash = "sha256:dd22c8efacf600730abe4a46c1388355ce0d4ab75dc79b15d23a7bd87bf05b48"},
+    {file = "pywinpty-0.5.7-cp38-cp38-win_amd64.whl", hash = "sha256:8fc5019ff3efb4f13708bd3b5ad327589c1a554cb516d792527361525a7cb78c"},
+    {file = "pywinpty-0.5.7.tar.gz", hash = "sha256:2d7e9c881638a72ffdca3f5417dd1563b60f603e1b43e5895674c2a1b01f95a0"},
+]
+pyzmq = [
+    {file = "pyzmq-22.0.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c0cde362075ee8f3d2b0353b283e203c2200243b5a15d5c5c03b78112a17e7d4"},
+    {file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ff1ea14075bbddd6f29bf6beb8a46d0db779bcec6b9820909584081ec119f8fd"},
+    {file = "pyzmq-22.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:26380487eae4034d6c2a3fb8d0f2dff6dd0d9dd711894e8d25aa2d1938950a33"},
+    {file = "pyzmq-22.0.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3e29f9cf85a40d521d048b55c63f59d6c772ac1c4bf51cdfc23b62a62e377c33"},
+    {file = "pyzmq-22.0.3-cp36-cp36m-win32.whl", hash = "sha256:4f34a173f813b38b83f058e267e30465ed64b22cd0cf6bad21148d3fa718f9bb"},
+    {file = "pyzmq-22.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:30df70f81fe210506aa354d7fd486a39b87d9f7f24c3d3f4f698ec5d96b8c084"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7026f0353977431fc884abd4ac28268894bd1a780ba84bb266d470b0ec26d2ed"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6d4163704201fff0f3ab0cd5d7a0ea1514ecfffd3926d62ec7e740a04d2012c7"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:763c175294d861869f18eb42901d500eda7d3fa4565f160b3b2fd2678ea0ebab"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:61e4bb6cd60caf1abcd796c3f48395e22c5b486eeca6f3a8797975c57d94b03e"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-win32.whl", hash = "sha256:b25e5d339550a850f7e919fe8cb4c8eabe4c917613db48dab3df19bfb9a28969"},
+    {file = "pyzmq-22.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3ef50d74469b03725d781a2a03c57537d86847ccde587130fe35caafea8f75c6"},
+    {file = "pyzmq-22.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60e63577b85055e4cc43892fecd877b86695ee3ef12d5d10a3c5d6e77a7cc1a3"},
+    {file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:f5831eff6b125992ec65d973f5151c48003b6754030094723ac4c6e80a97c8c4"},
+    {file = "pyzmq-22.0.3-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:9221783dacb419604d5345d0e097bddef4459a9a95322de6c306bf1d9896559f"},
+    {file = "pyzmq-22.0.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b62ea18c0458a65ccd5be90f276f7a5a3f26a6dea0066d948ce2fa896051420f"},
+    {file = "pyzmq-22.0.3-cp38-cp38-win32.whl", hash = "sha256:81e7df0da456206201e226491aa1fc449da85328bf33bbeec2c03bb3a9f18324"},
+    {file = "pyzmq-22.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:f52070871a0fd90a99130babf21f8af192304ec1e995bec2a9533efc21ea4452"},
+    {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:c5e29fe4678f97ce429f076a2a049a3d0b2660ada8f2c621e5dc9939426056dd"},
+    {file = "pyzmq-22.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d18ddc6741b51f3985978f2fda57ddcdae359662d7a6b395bc8ff2292fca14bd"},
+    {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4231943514812dfb74f44eadcf85e8dd8cf302b4d0bce450ce1357cac88dbfdc"},
+    {file = "pyzmq-22.0.3-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:23a74de4b43c05c3044aeba0d1f3970def8f916151a712a3ac1e5cd9c0bc2902"},
+    {file = "pyzmq-22.0.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:532af3e6dddea62d9c49062ece5add998c9823c2419da943cf95589f56737de0"},
+    {file = "pyzmq-22.0.3-cp39-cp39-win32.whl", hash = "sha256:33acd2b9790818b9d00526135acf12790649d8d34b2b04d64558b469c9d86820"},
+    {file = "pyzmq-22.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:a558c5bc89d56d7253187dccc4e81b5bb0eac5ae9511eb4951910a1245d04622"},
+    {file = "pyzmq-22.0.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581787c62eaa0e0db6c5413cedc393ebbadac6ddfd22e1cf9a60da23c4f1a4b2"},
+    {file = "pyzmq-22.0.3-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:38e3dca75d81bec4f2defa14b0a65b74545812bb519a8e89c8df96bbf4639356"},
+    {file = "pyzmq-22.0.3-pp36-pypy36_pp73-win32.whl", hash = "sha256:2f971431aaebe0a8b54ac018e041c2f0b949a43745444e4dadcc80d0f0ef8457"},
+    {file = "pyzmq-22.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da7d4d4c778c86b60949d17531e60c54ed3726878de8a7f8a6d6e7f8cc8c3205"},
+    {file = "pyzmq-22.0.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:13465c1ff969cab328bc92f7015ce3843f6e35f8871ad79d236e4fbc85dbe4cb"},
+    {file = "pyzmq-22.0.3-pp37-pypy37_pp73-win32.whl", hash = "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3"},
+    {file = "pyzmq-22.0.3.tar.gz", hash = "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d"},
+]
+qtconsole = [
+    {file = "qtconsole-5.1.0-py3-none-any.whl", hash = "sha256:3a2adecc43ff201a08972fb2179df22e7b3a08d71b9ed680f46ad1bfd4fb9132"},
+    {file = "qtconsole-5.1.0.tar.gz", hash = "sha256:12c734494901658787339dea9bbd82f3dc0d5e394071377a1c77b4a0954d7d8b"},
+]
+qtpy = [
+    {file = "QtPy-1.9.0-py2.py3-none-any.whl", hash = "sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea"},
+    {file = "QtPy-1.9.0.tar.gz", hash = "sha256:2db72c44b55d0fe1407be8fba35c838ad0d6d3bb81f23007886dc1fc0f459c8d"},
+]
+rasterio = [
+    {file = "rasterio-1.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5f639ebee499cc9fb84210f184412d790cbc8bb13fe5a0c088beb28f98e5756c"},
+    {file = "rasterio-1.2.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2a9bd9e4a1a78bc4bbeda1fab129c7ad0efb11419319142c479bc5d32fd518bf"},
+    {file = "rasterio-1.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2176c4876548c02e4d24582922403d799f176f1224be89a341c7b8526544dbc4"},
+    {file = "rasterio-1.2.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e852b45dd442c9d81b54950c3fef6a91edc8d108deb1b2b2bf36f1f52939286"},
+    {file = "rasterio-1.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45c548ab6c4a3ebbbf8f73c44abc75bdbc8eaa50c47267704fe1feef766f89bd"},
+    {file = "rasterio-1.2.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d28f931ece82e609b52ac9e5addfc65d0cf125c1d70b246ea08e9f8c901c8c8c"},
+    {file = "rasterio-1.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2032c9ad156a19d36ae00a4e060ac77331f3b6cc736757fd715fc30b7a5c67dd"},
+    {file = "rasterio-1.2.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2ac2984e9c403fa11cefa16ecb7e9b9265dfac391e1b3326711c0d96a12d14e8"},
+    {file = "rasterio-1.2.3.tar.gz", hash = "sha256:d8c345e01052b70ac3bbbe100c83def813c0ab19f7412c2c98e553d03720c1c5"},
+]
+regex = [
+    {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"},
+    {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"},
+    {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"},
+    {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"},
+    {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"},
+    {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"},
+    {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"},
+    {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"},
+    {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"},
+    {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"},
+    {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"},
+    {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"},
+    {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"},
+    {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"},
+    {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"},
+    {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"},
+    {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"},
+]
+send2trash = [
+    {file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
+    {file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"},
+]
+six = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+snuggs = [
+    {file = "snuggs-1.4.7-py3-none-any.whl", hash = "sha256:988dde5d4db88e9d71c99457404773dabcc7a1c45971bfbe81900999942d9f07"},
+    {file = "snuggs-1.4.7.tar.gz", hash = "sha256:501cf113fe3892e14e2fee76da5cd0606b7e149c411c271898e6259ebde2617b"},
+]
+terminado = [
+    {file = "terminado-0.9.5-py3-none-any.whl", hash = "sha256:3838414888b49a96145e277fd707a26a0fdb4bdaba88bb677a44abb68e3a1041"},
+    {file = "terminado-0.9.5.tar.gz", hash = "sha256:54c22cfb47cf79d4ff4ed2e7b5f8f98ecc28d0830f980465b6e04cf09cf553cf"},
+]
+testpath = [
+    {file = "testpath-0.4.4-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"},
+    {file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"},
+]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+tornado = [
+    {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
+    {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"},
+    {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"},
+    {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"},
+    {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"},
+    {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"},
+    {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"},
+    {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"},
+    {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"},
+    {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"},
+    {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"},
+    {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"},
+    {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"},
+    {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"},
+    {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"},
+    {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"},
+    {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"},
+    {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"},
+    {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"},
+    {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"},
+    {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"},
+    {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"},
+    {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"},
+    {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"},
+    {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"},
+    {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"},
+    {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"},
+    {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"},
+    {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"},
+    {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"},
+    {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"},
+    {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"},
+    {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"},
+    {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"},
+    {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"},
+    {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"},
+    {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"},
+    {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"},
+    {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"},
+    {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"},
+    {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"},
+]
+traitlets = [
+    {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"},
+    {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"},
+]
+typed-ast = [
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
+    {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
+    {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
+    {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
+    {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
+    {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
+    {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
+    {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
+    {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
+    {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
+    {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
+    {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
+    {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
+]
+typing-extensions = [
+    {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
+    {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
+    {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
+]
+wcwidth = [
+    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
+]
+webencodings = [
+    {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+    {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
+widgetsnbextension = [
+    {file = "widgetsnbextension-3.5.1-py2.py3-none-any.whl", hash = "sha256:bd314f8ceb488571a5ffea6cc5b9fc6cba0adaf88a9d2386b93a489751938bcd"},
+    {file = "widgetsnbextension-3.5.1.tar.gz", hash = "sha256:079f87d87270bce047512400efd70238820751a11d2d8cb137a5a5bdbaf255c7"},
+]
+wrapt = [
+    {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"},
+]
+zipp = [
+    {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
+    {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
+]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..1b562e2
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,54 @@
+[tool.poetry]
+name = "craterpy"
+version = "0.4.0"
+homepage = "https://github.com/cjtu/craterpy"
+description = "A package for impact crater data science."
+authors = ["Christian J. Tai Udovicic <cj.taiudovicic@gmail.com>"]
+license = "MIT"
+classifiers=[
+'Development Status :: 3 - Alpha',
+    'Intended Audience :: Science/Research',
+    'License :: OSI Approved :: MIT License',
+    'Natural Language :: English',
+    'Operating System :: OS Independent',
+    'Topic :: Scientific/Engineering :: Astronomy',
+    'Programming Language :: Python :: 3.7',
+    'Programming Language :: Python :: 3.8',
+]
+include = [
+    "LICENSE.txt",
+]
+
+[tool.poetry.dependencies]
+python = "^3.7.7"
+numpy = "^1.20.2"
+matplotlib = "^3.4.2"
+pandas = "^1.2.4"
+rasterio = "^1.2.3"
+
+[tool.poetry.dev-dependencies]
+pytest = "^6.2.4"
+jupyter = "^1.0.0"
+pylint = "^2.8.2"
+black = "^21.5b1"
+pytest-cov = "^2.11.1"
+
+[tool.black]
+line-length=79
+
+[tool.pylint.reports]
+output-format="colorized"
+reports="y"
+include-ids="yes"
+disable=["R0913","C0103","W0212","R0914","E1121","W0511", "R0201"]
+msg-template="{msg_id}:{line:3d},{column}: {obj}: {msg}"
+
+[tool.pytest.ini_options]
+addopts = "--cov=craterpy --cov-branch --cov-report term-missing  -vv --color=yes"
+python_files = "tests.py test_*.py *_tests.py"
+
+
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4f7a4af
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,175 @@
+affine==2.3.0; python_version >= "3.6" \
+    --hash=sha256:34b05b070d954c382e56f02c207a372d8a32621a87653cc30cdd31cd7f65799f \
+    --hash=sha256:2e045def1aa29e613c42e801a7e10e0b9bacfed1a7c6af0cadf8843530a15102
+attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" \
+    --hash=sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1 \
+    --hash=sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb
+certifi==2020.12.5; python_version >= "3.6" \
+    --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \
+    --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c
+click-plugins==1.1.1; python_version >= "3.6" \
+    --hash=sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b \
+    --hash=sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8
+click==7.1.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4" \
+    --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
+    --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
+cligj==0.7.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.6" \
+    --hash=sha256:07171c1e287f45511f97df4ea071abc5d19924153413d5683a8e4866369bc676 \
+    --hash=sha256:b2f1f7247d59a5387bd3013a08b9ed6829e96fafa4a6e6292341efdb46fe6220
+cycler==0.10.0; python_version >= "3.7" \
+    --hash=sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d \
+    --hash=sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8
+kiwisolver==1.3.1; python_version >= "3.7" \
+    --hash=sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9 \
+    --hash=sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0 \
+    --hash=sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21 \
+    --hash=sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05 \
+    --hash=sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b \
+    --hash=sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9 \
+    --hash=sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4 \
+    --hash=sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0 \
+    --hash=sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278 \
+    --hash=sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689 \
+    --hash=sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8 \
+    --hash=sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31 \
+    --hash=sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc \
+    --hash=sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454 \
+    --hash=sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72 \
+    --hash=sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3 \
+    --hash=sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131 \
+    --hash=sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de \
+    --hash=sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18 \
+    --hash=sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81 \
+    --hash=sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e \
+    --hash=sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000 \
+    --hash=sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598 \
+    --hash=sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882 \
+    --hash=sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621 \
+    --hash=sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54 \
+    --hash=sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030 \
+    --hash=sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6 \
+    --hash=sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d \
+    --hash=sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3 \
+    --hash=sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6 \
+    --hash=sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248
+matplotlib==3.4.2; python_version >= "3.7" \
+    --hash=sha256:c541ee5a3287efe066bbe358320853cf4916bc14c00c38f8f3d8d75275a405a9 \
+    --hash=sha256:3a5c18dbd2c7c366da26a4ad1462fe3e03a577b39e3b503bbcf482b9cdac093c \
+    --hash=sha256:a9d8cb5329df13e0cdaa14b3b43f47b5e593ec637f13f14db75bb16e46178b05 \
+    --hash=sha256:7ad19f3fb6145b9eb41c08e7cbb9f8e10b91291396bee21e9ce761bb78df63ec \
+    --hash=sha256:7a58f3d8fe8fac3be522c79d921c9b86e090a59637cb88e3bc51298d7a2c862a \
+    --hash=sha256:6382bc6e2d7e481bcd977eb131c31dee96e0fb4f9177d15ec6fb976d3b9ace1a \
+    --hash=sha256:6a6a44f27aabe720ec4fd485061e8a35784c2b9ffa6363ad546316dfc9cea04e \
+    --hash=sha256:1c1779f7ab7d8bdb7d4c605e6ffaa0614b3e80f1e3c8ccf7b9269a22dbc5986b \
+    --hash=sha256:5826f56055b9b1c80fef82e326097e34dc4af8c7249226b7dd63095a686177d1 \
+    --hash=sha256:0bea5ec5c28d49020e5d7923c2725b837e60bc8be99d3164af410eb4b4c827da \
+    --hash=sha256:6475d0209024a77f869163ec3657c47fed35d9b6ed8bccba8aa0f0099fbbdaa8 \
+    --hash=sha256:21b31057bbc5e75b08e70a43cefc4c0b2c2f1b1a850f4a0f7af044eb4163086c \
+    --hash=sha256:b26535b9de85326e6958cdef720ecd10bcf74a3f4371bf9a7e5b2e659c17e153 \
+    --hash=sha256:32fa638cc10886885d1ca3d409d4473d6a22f7ceecd11322150961a70fab66dd \
+    --hash=sha256:956c8849b134b4a343598305a3ca1bdd3094f01f5efc8afccdebeffe6b315247 \
+    --hash=sha256:85f191bb03cb1a7b04b5c2cca4792bef94df06ef473bc49e2818105671766fee \
+    --hash=sha256:b1d5a2cedf5de05567c441b3a8c2651fbde56df08b82640e7f06c8cd91e201f6 \
+    --hash=sha256:df815378a754a7edd4559f8c51fc7064f779a74013644a7f5ac7a0c31f875866 \
+    --hash=sha256:d8d994cefdff9aaba45166eb3de4f5211adb4accac85cbf97137e98f26ea0219
+numpy==1.20.2; python_version >= "3.7" \
+    --hash=sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935 \
+    --hash=sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6 \
+    --hash=sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5 \
+    --hash=sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3 \
+    --hash=sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d \
+    --hash=sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6 \
+    --hash=sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677 \
+    --hash=sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737 \
+    --hash=sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935 \
+    --hash=sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f \
+    --hash=sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727 \
+    --hash=sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576 \
+    --hash=sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd \
+    --hash=sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931 \
+    --hash=sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2 \
+    --hash=sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7 \
+    --hash=sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98 \
+    --hash=sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4 \
+    --hash=sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6 \
+    --hash=sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9 \
+    --hash=sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff \
+    --hash=sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb \
+    --hash=sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042 \
+    --hash=sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee
+pandas==1.2.4; python_full_version >= "3.7.1" \
+    --hash=sha256:c601c6fdebc729df4438ec1f62275d6136a0dd14d332fc0e8ce3f7d2aadb4dd6 \
+    --hash=sha256:8d4c74177c26aadcfb4fd1de6c1c43c2bf822b3e0fc7a9b409eeaf84b3e92aaa \
+    --hash=sha256:b730add5267f873b3383c18cac4df2527ac4f0f0eed1c6cf37fcb437e25cf558 \
+    --hash=sha256:2cb7e8f4f152f27dc93f30b5c7a98f6c748601ea65da359af734dd0cf3fa733f \
+    --hash=sha256:2111c25e69fa9365ba80bbf4f959400054b2771ac5d041ed19415a8b488dc70a \
+    --hash=sha256:167693a80abc8eb28051fbd184c1b7afd13ce2c727a5af47b048f1ea3afefff4 \
+    --hash=sha256:612add929bf3ba9d27b436cc8853f5acc337242d6b584203f207e364bb46cb12 \
+    --hash=sha256:971e2a414fce20cc5331fe791153513d076814d30a60cd7348466943e6e909e4 \
+    --hash=sha256:68d7baa80c74aaacbed597265ca2308f017859123231542ff8a5266d489e1858 \
+    --hash=sha256:bd659c11a4578af740782288cac141a322057a2e36920016e0fc7b25c5a4b686 \
+    --hash=sha256:9db70ffa8b280bb4de83f9739d514cd0735825e79eef3a61d312420b9f16b758 \
+    --hash=sha256:298f0553fd3ba8e002c4070a723a59cdb28eda579f3e243bc2ee397773f5398b \
+    --hash=sha256:52d2472acbb8a56819a87aafdb8b5b6d2b3386e15c95bde56b281882529a7ded \
+    --hash=sha256:d0877407359811f7b853b548a614aacd7dea83b0c0c84620a9a643f180060950 \
+    --hash=sha256:2b063d41803b6a19703b845609c0b700913593de067b552a8b24dd8eeb8c9895 \
+    --hash=sha256:649ecab692fade3cbfcf967ff936496b0cfba0af00a55dfaacd82bdda5cb2279
+pillow==8.2.0; python_version >= "3.7" \
+    --hash=sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9 \
+    --hash=sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b \
+    --hash=sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b \
+    --hash=sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9 \
+    --hash=sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727 \
+    --hash=sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f \
+    --hash=sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d \
+    --hash=sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a \
+    --hash=sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9 \
+    --hash=sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388 \
+    --hash=sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5 \
+    --hash=sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2 \
+    --hash=sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4 \
+    --hash=sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812 \
+    --hash=sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178 \
+    --hash=sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb \
+    --hash=sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232 \
+    --hash=sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797 \
+    --hash=sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5 \
+    --hash=sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484 \
+    --hash=sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602 \
+    --hash=sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2 \
+    --hash=sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef \
+    --hash=sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713 \
+    --hash=sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c \
+    --hash=sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9 \
+    --hash=sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9 \
+    --hash=sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c \
+    --hash=sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b \
+    --hash=sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4 \
+    --hash=sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120 \
+    --hash=sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e \
+    --hash=sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1
+pyparsing==2.4.7; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7" \
+    --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \
+    --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1
+python-dateutil==2.8.1; python_full_version >= "3.7.1" and python_version >= "3.7" \
+    --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \
+    --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a
+pytz==2021.1; python_full_version >= "3.7.1" \
+    --hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \
+    --hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da
+rasterio==1.2.3; python_version >= "3.6" \
+    --hash=sha256:5f639ebee499cc9fb84210f184412d790cbc8bb13fe5a0c088beb28f98e5756c \
+    --hash=sha256:2a9bd9e4a1a78bc4bbeda1fab129c7ad0efb11419319142c479bc5d32fd518bf \
+    --hash=sha256:2176c4876548c02e4d24582922403d799f176f1224be89a341c7b8526544dbc4 \
+    --hash=sha256:8e852b45dd442c9d81b54950c3fef6a91edc8d108deb1b2b2bf36f1f52939286 \
+    --hash=sha256:45c548ab6c4a3ebbbf8f73c44abc75bdbc8eaa50c47267704fe1feef766f89bd \
+    --hash=sha256:d28f931ece82e609b52ac9e5addfc65d0cf125c1d70b246ea08e9f8c901c8c8c \
+    --hash=sha256:2032c9ad156a19d36ae00a4e060ac77331f3b6cc736757fd715fc30b7a5c67dd \
+    --hash=sha256:2ac2984e9c403fa11cefa16ecb7e9b9265dfac391e1b3326711c0d96a12d14e8 \
+    --hash=sha256:d8c345e01052b70ac3bbbe100c83def813c0ab19f7412c2c98e553d03720c1c5
+six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7" \
+    --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
+    --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
+snuggs==1.4.7; python_version >= "3.6" \
+    --hash=sha256:988dde5d4db88e9d71c99457404773dabcc7a1c45971bfbe81900999942d9f07 \
+    --hash=sha256:501cf113fe3892e14e2fee76da5cd0606b7e149c411c271898e6259ebde2617b
diff --git a/requirements_conda.txt b/requirements_conda.txt
deleted file mode 100644
index cda1a91..0000000
--- a/requirements_conda.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-gdal==2.2.3
-matplotlib==2.1.0
-numpy==1.13.3
-pandas==0.20.3
-pytest==3.2.3
-scipy==0.19.1
diff --git a/setup.py b/setup.py
index 1f85506..5e8c59e 100644
--- a/setup.py
+++ b/setup.py
@@ -4,20 +4,21 @@
 from setuptools import setup, find_packages
 from codecs import open
 from os import path
+
 here = path.abspath(path.dirname(__file__))
 
 
 def get_version():
-    """ Fetch version from version.py """
+    """Fetch version from version.py"""
     version = {}
-    with open(path.join(here, 'craterpy', 'version.py')) as f:
+    with open(path.join(here, "craterpy", "version.py")) as f:
         exec(f.read(), version)
-    return version['__version__']
+    return version["__version__"]
 
 
 def get_readme():
-    """ Fetch long winded description from /README.rst """
-    with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
+    """Fetch long winded description from /README.rst"""
+    with open(path.join(here, "README.rst"), encoding="utf-8") as f:
         return f.read()
 
 
@@ -25,54 +26,55 @@ def try_BuildDoc():
     """Try to import BuildDoc (incompatible with py2)"""
     try:
         from sphinx.setup_command import BuildDoc
-        return {'build_docs': BuildDoc}
+
+        return {"build_docs": BuildDoc}
     except ImportError:
         print("Warning: sphinx.setup_command unavailable. Docs not built")
         return {}
 
 
 # Set package metadata
-NAME = 'craterpy'
+NAME = "craterpy"
 VERSION = get_version()
-DESCRIPTION = 'A package for impact crater data science.'
+DESCRIPTION = "A package for impact crater data science."
 LONG_DESCRIPTION = get_readme()
-LONG_DESCRIPTION_CONTENT_TYPE = 'text/x-rst'
-AUTHOR = 'Christian Tai Udovicic'
-AUTHOR_EMAIL = 'cj.taiudovicic@gmail.com'
+LONG_DESCRIPTION_CONTENT_TYPE = "text/x-rst"
+AUTHOR = "Christian J. Tai Udovicic"
+AUTHOR_EMAIL = "cj.taiudovicic@gmail.com"
 MAINTAINER = AUTHOR
 MAINTAINER_EMAIL = AUTHOR_EMAIL
-URL = 'http://github.com/cjtu/craterpy'
-LICENSE = 'MIT'
-CLASSIFIERS = ['Development Status :: 3 - Alpha',
-               'Intended Audience :: Science/Research',
-               'License :: OSI Approved :: MIT License',
-               'Natural Language :: English',
-               'Operating System :: OS Independent',
-               'Topic :: Scientific/Engineering :: Astronomy',
-               'Programming Language :: Python :: 3',
-               'Programming Language :: Python :: 3.4',
-               'Programming Language :: Python :: 3.5']
-KEYWORDS = 'python crater data analysis planetary science'
-PLATFORMS = 'OS Independent'
-PACKAGES = find_packages(exclude=['tests'])
-PACKAGE_DATA = {'': ['*.csv', '*.tif']}
-INSTALL_REQUIRES = ['numpy',
-                    'scipy',
-                    'pandas',
-                    'matplotlib',
-                    'gdal']
+URL = "http://github.com/cjtu/craterpy"
+LICENSE = "MIT"
+CLASSIFIERS = [
+    "Development Status :: 3 - Alpha",
+    "Intended Audience :: Science/Research",
+    "License :: OSI Approved :: MIT License",
+    "Natural Language :: English",
+    "Operating System :: OS Independent",
+    "Topic :: Scientific/Engineering :: Astronomy",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.4",
+    "Programming Language :: Python :: 3.5",
+]
+KEYWORDS = "python crater data analysis planetary science"
+PLATFORMS = "OS Independent"
+PACKAGES = find_packages(exclude=["tests"])
+PACKAGE_DATA = {"": ["*.csv", "*.tif"]}
+INSTALL_REQUIRES = ["numpy", "pandas", "matplotlib" "rasterio"]
 # PYTHON_REQUIRES = '>=2.7, <=3.3'
 EXTRAS_REQUIRE = None  # {'test': ['pytest'], 'cite': ['duecredit'],}
 
 # Setup Sphinx integration to automatically build documentation
 CMDCLASS = try_BuildDoc()
 COMMAND_OPTIONS = {  # Overrides docs/conf.py settings
-        'build_docs': {
-            'project': ('setup.py', NAME),
-            'version': ('setup.py', VERSION),
-            'release': ('setup.py', VERSION),
-            'source_dir': ('setup.py', './docs/source'),
-            'build_dir': ('setup.py', './docs/build')}}
+    "build_docs": {
+        "project": ("setup.py", NAME),
+        "version": ("setup.py", VERSION),
+        "release": ("setup.py", VERSION),
+        "source_dir": ("setup.py", "./docs/source"),
+        "build_dir": ("setup.py", "./docs/build"),
+    }
+}
 
 # Run setup() function with metadata above
 setup(
@@ -96,5 +98,5 @@ def try_BuildDoc():
     extras_require=EXTRAS_REQUIRE,
     # python_requires=PYTHON_REQUIRES,
     cmdclass=CMDCLASS,
-    command_options=COMMAND_OPTIONS
+    command_options=COMMAND_OPTIONS,
 )


Test patch:
diff --git a/.github/workflows/test_pypi_publish.yml b/.github/workflows/test_pypi_publish.yml
new file mode 100644
index 0000000..c1f1250
--- /dev/null
+++ b/.github/workflows/test_pypi_publish.yml
@@ -0,0 +1,26 @@
+---
+name: Test PyPI publish
+
+on:
+  release:
+    types: [prereleased]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python 3.7
+        uses: actions/setup-python@v1
+        with:
+          python-version: 3.7
+      - name: Install poetry
+        run: pip install poetry
+      - name: Bump version number
+        run: poetry version ${{ github.event.release.tag_name }}
+      - name: Build package
+        run: poetry build
+      - name: Publish package
+        run: poetry publish -r testpypi -u __token__ -p ${{ secrets.TEST_PYPI_TOKEN }}
diff --git a/craterpy/tests/test_dataset.py b/craterpy/tests/test_dataset.py
index 5944d0c..8f2c4fc 100644
--- a/craterpy/tests/test_dataset.py
+++ b/craterpy/tests/test_dataset.py
@@ -1,71 +1,120 @@
-from __future__ import division, print_function, absolute_import
+"""Unittest dataset.py."""
 import os.path as p
 import unittest
 import numpy as np
-import gdal
+import rasterio as rio
+import craterpy
 from craterpy.dataset import CraterpyDataset
-from craterpy.exceptions import DataImportError
 
 
 class TestCraterpyDataset(unittest.TestCase):
     """Test CraterpyDataset object"""
+
     def setUp(self):
-        import craterpy
-        self.data_path = p.join(craterpy.__path__[0], 'data')
-        self.moon_tif = p.join(self.data_path, 'moon.tif')
+        self.data_path = p.join(craterpy.__path__[0], "data")
+        self.moon_tif = p.join(self.data_path, "moon.tif")
+        self.moon_tif_nogeoref = p.join(self.data_path, "moon_nogeoref.tif")
         self.cds = CraterpyDataset(self.moon_tif, radius=1737)
 
     def test_file_import(self):
         """Test import"""
         self.assertIsNotNone(CraterpyDataset(self.moon_tif))
 
-    def test_import_gdal_Dataset(self):
-        """Test importing from gdal.Dataset object"""
-        ds = gdal.Open(self.moon_tif)
-        self.assertIsNotNone(CraterpyDataset(ds))
-
     def test_import_error(self):
         """Test that importing invalid dataset fails"""
-        self.assertRaises(RuntimeError, CraterpyDataset, "Invalid_DS")
-        self.assertRaises(RuntimeError, CraterpyDataset, [1, 2])
+        self.assertRaises(rio.errors.RasterioIOError, CraterpyDataset, "?")
+
+    def test_geotiff(self):
+        """Test import geotiff with geotransform."""
+        ds = CraterpyDataset(self.moon_tif)
+        # Get geo transform
+        actual = list(ds.transform)
+        expected = [0.25, 0.0, -180.0, 0.0, -0.25, 90.0, 0.0, 0.0, 1.0]
+        self.assertListEqual(actual, expected)
+
+        # Get body radius
+        actual = ds.radius
+        expected = 1737.4
+        self.assertEqual(actual, expected)
 
-    def test_set_attrs(self):
-        """Test supplying optional attributes to Craterpydataset"""
-        pass  # TODO: implement
+    def test_not_georeferenced_error(self):
+        """Test importing dataset with no georef info and none supplied"""
+        with self.assertRaises(ImportError):
+            _ = CraterpyDataset(self.moon_tif_nogeoref)
 
-    def test_get_gdalDataset_attrs(self):
-        """Test that wrapped gdal Dataset attrs are accessible"""
-        self.assertIsNotNone(self.cds.GetRasterBand(1))
+    def test_not_georeferenced_supplied_args(self):
+        """Test importing dataset with no georef info and none supplied"""
+        args = [90, -90, 0, 360, 1737, 4, 4]
+        ds = CraterpyDataset(self.moon_tif_nogeoref, *args)
+        actual = [
+            ds.nlat,
+            ds.slat,
+            ds.wlon,
+            ds.elon,
+            ds.radius,
+            ds.xres,
+            ds.yres,
+        ]
+        self.assertListEqual(actual, args)
+
+    def test_not_georeferenced_infer_resolution(self):
+        """Test importing dataset with no georef and inferred xres, yres"""
+        args = [90, -90, 0, 360, 1737]
+        ds = CraterpyDataset(self.moon_tif_nogeoref, *args)
+        expected = args + [4, 4]
+        actual = [
+            ds.nlat,
+            ds.slat,
+            ds.wlon,
+            ds.elon,
+            ds.radius,
+            ds.xres,
+            ds.yres,
+        ]
+        self.assertListEqual(actual, expected)
+
+    # def test_set_attrs(self):
+    #     """Test supplying optional attributes to Craterpydataset"""
+    #     pass  # TODO: implement
+
+    def test_get_rasterioDataset_attrs(self):
+        """Test that wrapped rasterio Dataset attrs are accessible"""
+        self.assertIsNotNone(self.cds.read(1))
         with self.assertRaises(AttributeError):
-            self.cds.lat
+            _ = self.cds.lat
 
     def test_repr(self):
-        expected = 'CraterpyDataset with extent (90.0N, -90.0N), '
-        expected += '(-180.0E, 180.0E), radius 1737 km, and '
-        expected += 'resolution 4.0 ppd'
+        """Test Craterpy string __repr__."""
+        expected = "CraterpyDataset with extent (90.0N, -90.0N), "
+        expected += "(-180.0E, 180.0E), radius 1737 km, "
+        expected += "xres 4.0 ppd, and yres 4.0 ppd"
         actual = self.cds.__repr__()
         self.assertEqual(actual, expected)
 
     def test_get_geotiff_info(self):
         """Test _get_info() method for reading geotiff info"""
         actual = self.cds._get_geotiff_info()
-        expected = (90.0, -90.0, -180.0, 180.0, 6378.137, 4.0)
+        expected = (90.0, -90.0, -180.0, 180.0, 1737.4, 4.0, 4.0)
         self.assertEqual(actual, expected)
 
     def test_calc_mpp(self):
         """Test .calc_mpp method"""
         cds = self.cds
-        xpix = cds.RasterXSize  # [pix]
+        xpix = cds.width  # [pix]
         # Test at equator
-        expected = 1000*2*np.pi*cds.radius/xpix  # [m/pix] at lat=0
+        expected = 1000 * 2 * np.pi * cds.radius / xpix  # [m/pix] at lat=0
         self.assertAlmostEqual(cds.calc_mpp(), expected, 5)
         # Test at 50 degrees lat
         lat = 50
-        expected = 1000*2*np.pi*cds.radius*np.cos(lat*np.pi/180)/xpix
+        expected = (
+            1000 * 2 * np.pi * cds.radius * np.cos(lat * np.pi / 180) / xpix
+        )
         self.assertAlmostEqual(cds.calc_mpp(lat), expected, 5)
         # Test at -40 degrees lat
         lat = -40
-        expected = 1000*2*np.pi*cds.radius*np.cos(lat*np.pi/180)/xpix
+        expected = (
+            1000 * 2 * np.pi * cds.radius * np.cos(lat * np.pi / 180) / xpix
+        )
         self.assertAlmostEqual(cds.calc_mpp(lat), expected, 5)
         # Test at 90
         self.assertAlmostEqual(cds.calc_mpp(90), 0)
@@ -90,24 +139,18 @@ def test_inbounds(self):
 
     def test_is_global(self):
         """Test .is_global method"""
-        is_global = CraterpyDataset(self.moon_tif, wlon=0,
-                                    elon=360).is_global()
+        is_global = CraterpyDataset(
+            self.moon_tif, wlon=0, elon=360
+        ).is_global()
         self.assertTrue(is_global)
-        not_global = CraterpyDataset(self.moon_tif, wlon=0,
-                                     elon=180).is_global()
+        not_global = CraterpyDataset(
+            self.moon_tif, wlon=0, elon=180
+        ).is_global()
         self.assertFalse(not_global)
 
-    def test_wrap_roi_360(self):
-        """Test wrap_roi_360 method"""
-        pass  # TODO: implement
-
     def test_get_roi(self):
         """Test get_roi method"""
-        extent = (0, 10, 0, 91)
-        self.assertRaises(DataImportError, self.cds.get_roi, *extent)
-        extent = (170, 190, -10, 10)
-        actual = self.cds.get_roi(*extent)
-        # TODO: Finish this
-        extent = (-190, 170, -10, 10)
+        extent = (0, 0.5, 0, 0.5)
         actual = self.cds.get_roi(*extent)
-        # TODO: Finish this
+        expected = np.array([[41, 43], [41, 41]])
+        np.testing.assert_array_equal(actual, expected)
diff --git a/craterpy/tests/test_helper.py b/craterpy/tests/test_helper.py
index 4160684..09496c3 100644
--- a/craterpy/tests/test_helper.py
+++ b/craterpy/tests/test_helper.py
@@ -1,16 +1,62 @@
-"""
-Suite of unittests for functions found in /craterpy/acefunctions.py.
-"""
-from __future__ import division, print_function, absolute_import
+"""Unittest helper.py."""
 import unittest
 import numpy as np
 import pandas as pd
-import pandas.util.testing as pdt
+import pandas.testing as pdt
 from craterpy import helper as ch
 
 
 class Test_geo_helpers(unittest.TestCase):
     """Test Geospatial Helper Functions"""
+
+    def test_lon360(self):
+        """Test lon360 function"""
+        actual = ch.lon360(0)
+        expected = 0
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(360)
+        expected = 0
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(20)
+        expected = 20
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(185)
+        expected = 185
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(355)
+        expected = 355
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(-175)
+        expected = 185
+        self.assertEqual(actual, expected)
+        actual = ch.lon360(-5)
+        expected = 355
+        self.assertEqual(actual, expected)
+
+    def test_lon180(self):
+        """Test lon180 function"""
+        actual = ch.lon180(0)
+        expected = 0
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(360)
+        expected = 0
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(20)
+        expected = 20
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(185)
+        expected = -175
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(355)
+        expected = -5
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(-175)
+        expected = -175
+        self.assertEqual(actual, expected)
+        actual = ch.lon180(-5)
+        expected = -5
+        self.assertEqual(actual, expected)
+
     def test_deg2pix(self):
         """Test deg2pix function"""
         actual = ch.deg2pix(10, 20)
@@ -60,48 +106,61 @@ def test_inglobal(self):
         """Test inglobal"""
         self.assertTrue(ch.inglobal(0, 0))
         self.assertTrue(ch.inglobal(90.0, 180.0))
-        self.assertTrue(ch.inglobal(-5.5, 360, 'pos'))
+        self.assertTrue(ch.inglobal(-5.5, 360))
         self.assertFalse(ch.inglobal(91, 0))
-        self.assertFalse(ch.inglobal(0, 181))
-        self.assertFalse(ch.inglobal(0, -1, 'pos'))
+        self.assertTrue(ch.inglobal(0, 181))
+        self.assertTrue(ch.inglobal(0, -1))
 
 
 class Test_dataframe_helpers(unittest.TestCase):
     """Test pandas.DataFrame helper functions"""
+
     def setUp(self):
-        self.df = pd.DataFrame({'Lat': [10, -20., 80.0],
-                                'Lon': [14, -40.1, 317.2],
-                                'Diam': [2, 12., 23.7]})
+        self.df = pd.DataFrame(
+            {
+                "Lat": [10, -20.0, 80.0],
+                "Lon": [14, -40.1, 317.2],
+                "Diam": [2, 12.0, 23.7],
+            }
+        )
 
     def test_findcol(self):
         """Test findcol"""
-        expected = 'Lat'
-        actual = ch.findcol(self.df, 'Lat')
+        expected = "Lat"
+        actual = ch.findcol(self.df, "Lat")
         self.assertEqual(actual, expected)
-        actual = ch.findcol(self.df, ['lat'])
+        actual = ch.findcol(self.df, ["lat"])
         self.assertEqual(actual, expected)
-        actual = ch.findcol(self.df, ['Latitude', 'Lat'])
+        actual = ch.findcol(self.df, ["Latitude", "Lat"])
         self.assertEqual(actual, expected)
-        actual = ch.findcol(self.df, 'slat')
+        actual = ch.findcol(self.df, "slat")
         self.assertIsNone(actual)
 
     def test_diam2radius(self):
         """Test diam2radius"""
         # Find Diam col
         expected = pd.Series([1.0, 6.0, 11.85], name="Radius")
-        actual = ch.diam2radius(self.df)['Radius']
+        actual = ch.diam2radius(self.df)["Radius"]
         pdt.assert_series_equal(actual, expected)
         # Find Diameter col
         expected = pd.Series([1.0, 6.0, 11.85], name="Radius")
-        df = pd.DataFrame({'Lat': [10, -20., 80.0],
-                           'Lon': [14, -40.1, 317.2],
-                           'Diameter': [2, 12., 23.7]})
-        actual = ch.diam2radius(df)['Radius']
+        df = pd.DataFrame(
+            {
+                "Lat": [10, -20.0, 80.0],
+                "Lon": [14, -40.1, 317.2],
+                "Diameter": [2, 12.0, 23.7],
+            }
+        )
+        actual = ch.diam2radius(df)["Radius"]
         pdt.assert_series_equal(actual, expected)
         # Supply Lat col
-        df = pd.DataFrame({'Lat': [10, -20., 80.0],
-                           'Lon': [14, -40.1, 317.2],
-                           'Diam': [2, 12., 23.7]})
-        expected = pd.Series([5, -10., 40.0], name="Radius")
-        actual = ch.diam2radius(df, 'Lat')['Radius']
+        df = pd.DataFrame(
+            {
+                "Lat": [10, -20.0, 80.0],
+                "Lon": [14, -40.1, 317.2],
+                "Diam": [2, 12.0, 23.7],
+            }
+        )
+        expected = pd.Series([5, -10.0, 40.0], name="Radius")
+        actual = ch.diam2radius(df, "Lat")["Radius"]
         pdt.assert_series_equal(actual, expected)
diff --git a/craterpy/tests/test_masking.py b/craterpy/tests/test_masking.py
index de0a42b..6ddc1ed 100644
--- a/craterpy/tests/test_masking.py
+++ b/craterpy/tests/test_masking.py
@@ -1,182 +1,236 @@
-"""
-Suite of unittests for functions found in /craterpy/acefunctions.py.
-"""
-from __future__ import division, print_function, absolute_import
+"""Unittest masking.py."""
+import os.path as p
 import unittest
 import numpy as np
+import craterpy
 from craterpy import masking as cm
 from craterpy.roi import CraterRoi
+from craterpy.dataset import CraterpyDataset
 
 # Test ROI manipulation functions
 class Test_circle_mask(unittest.TestCase):
     """Test ring_mask function"""
+
     def test_radius0(self):
         """Test radius 0"""
         actual = cm.circle_mask((3, 3), 0)
-        expected = np.array([[False, False, False],
-                             [False, True,  False],
-                             [False, False, False]])
+        expected = np.array(
+            [
+                [False, False, False],
+                [False, True, False],
+                [False, False, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_radius1(self):
         """Test radius 1"""
         actual = cm.circle_mask((3, 3), 1)
-        expected = np.array([[False, True, False],
-                             [True,  True,  True],
-                             [False, True, False]])
+        expected = np.array(
+            [[False, True, False], [True, True, True], [False, True, False]]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_odd(self):
         """Test roi with odd side length"""
         actual = cm.circle_mask((5, 5), 2)
-        expected = np.array([[False, False, True,  False, False],
-                             [False, True,  True,  True,  False],
-                             [True,  True,  True,  True,  True],
-                             [False, True,  True,  True,  False],
-                             [False, False, True,  False, False]])
+        expected = np.array(
+            [
+                [False, False, True, False, False],
+                [False, True, True, True, False],
+                [True, True, True, True, True],
+                [False, True, True, True, False],
+                [False, False, True, False, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_even(self):
         """Test roi with even side length"""
         actual = cm.circle_mask((4, 4), 2)
-        expected = np.array([[False,  True,  True, False],
-                             [True,  True,  True,  True],
-                             [True,  True,  True,  True],
-                             [False,  True,  True, False]])
+        expected = np.array(
+            [
+                [False, True, True, False],
+                [True, True, True, True],
+                [True, True, True, True],
+                [False, True, True, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_offcenter(self):
         """Test specifying off center location"""
         actual = cm.circle_mask((5, 5), 2, center=(2, 3))
-        expected = np.array([[False, False, False, True,  False],
-                             [False, False, True,  True,  True],
-                             [False, True,  True,  True,  True],
-                             [False, False, True,  True,  True],
-                             [False, False, False, True,  False]])
+        expected = np.array(
+            [
+                [False, False, False, True, False],
+                [False, False, True, True, True],
+                [False, True, True, True, True],
+                [False, False, True, True, True],
+                [False, False, False, True, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
 
 class Test_ellipse_mask(unittest.TestCase):
     """Test ellipse mask function"""
+
     def test_radius_equal(self):
         """Test radius equal"""
         actual = cm.ellipse_mask((5, 5), 2, 2)
-        expected = np.array([[0, 0, 1, 0, 0],
-                             [0, 1, 1, 1, 0],
-                             [1, 1, 1, 1, 1],
-                             [0, 1, 1, 1, 0],
-                             [0, 0, 1, 0, 0]], dtype=bool)
+        expected = np.array(
+            [
+                [0, 0, 1, 0, 0],
+                [0, 1, 1, 1, 0],
+                [1, 1, 1, 1, 1],
+                [0, 1, 1, 1, 0],
+                [0, 0, 1, 0, 0],
+            ],
+            dtype=bool,
+        )
         np.testing.assert_equal(actual, expected)
 
     def test_y_gt_x(self):
         """Test ysize > xsize"""
         actual = cm.ellipse_mask((5, 5), 2, 1)
-        expected = np.array([[0, 0, 1, 0, 0],
-                             [0, 0, 1, 0, 0],
-                             [0, 1, 1, 1, 0],
-                             [0, 0, 1, 0, 0],
-                             [0, 0, 1, 0, 0]], dtype=bool)
+        expected = np.array(
+            [
+                [0, 0, 1, 0, 0],
+                [0, 0, 1, 0, 0],
+                [0, 1, 1, 1, 0],
+                [0, 0, 1, 0, 0],
+                [0, 0, 1, 0, 0],
+            ],
+            dtype=bool,
+        )
         np.testing.assert_equal(actual, expected)
-    
+
     def test_x_gt_y(self):
         """Test xsize > ysize"""
         actual = cm.ellipse_mask((5, 5), 1, 2)
-        expected = np.array([[0, 0, 0, 0, 0],
-                             [0, 0, 1, 0, 0],
-                             [1, 1, 1, 1, 1],
-                             [0, 0, 1, 0, 0],
-                             [0, 0, 0, 0, 0]], dtype=bool)
+        expected = np.array(
+            [
+                [0, 0, 0, 0, 0],
+                [0, 0, 1, 0, 0],
+                [1, 1, 1, 1, 1],
+                [0, 0, 1, 0, 0],
+                [0, 0, 0, 0, 0],
+            ],
+            dtype=bool,
+        )
         np.testing.assert_equal(actual, expected)
 
     def test_high_eccentricity(self):
         """Test ysize > xsize"""
         actual = cm.ellipse_mask((3, 9), 1, 4)
-        expected = np.array([[0, 0, 0, 0, 1, 0, 0, 0, 0],
-                             [1, 1, 1, 1, 1, 1, 1, 1, 1],
-                             [0, 0, 0, 0, 1, 0, 0, 0, 0]], dtype=bool)
+        expected = np.array(
+            [
+                [0, 0, 0, 0, 1, 0, 0, 0, 0],
+                [1, 1, 1, 1, 1, 1, 1, 1, 1],
+                [0, 0, 0, 0, 1, 0, 0, 0, 0],
+            ],
+            dtype=bool,
+        )
         print(actual)
         print(expected)
-        np.testing.assert_equal(actual, expected)     
+        np.testing.assert_equal(actual, expected)
+
 
 class Test_ring_mask(unittest.TestCase):
     """Test ring_mask function"""
+
     def test_trivial(self):
+        """Test fully masked."""
         actual = cm.ring_mask((3, 3), 0, 0)
-        expected = np.array([[False, False, False],
-                             [False, False, False],
-                             [False,  False, False]])
+        expected = np.array(
+            [
+                [False, False, False],
+                [False, False, False],
+                [False, False, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_odd(self):
         """Test roi with odd side length"""
         actual = cm.ring_mask((5, 5), 1, 2)
-        expected = np.array([[False, False, True,  False, False],
-                             [False, True,  False, True,  False],
-                             [True,  False, False, False, True],
-                             [False, True,  False, True,  False],
-                             [False, False, True,  False, False]])
+        expected = np.array(
+            [
+                [False, False, True, False, False],
+                [False, True, False, True, False],
+                [True, False, False, False, True],
+                [False, True, False, True, False],
+                [False, False, True, False, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_even(self):
         """Test roi with even side length"""
         actual = cm.ring_mask((4, 4), 1.5, 2)
-        expected = np.array([[False, True,  True,  False],
-                             [True,  False, False, True],
-                             [True,  False, False, True],
-                             [False, True,  True,  False]])
+        expected = np.array(
+            [
+                [False, True, True, False],
+                [True, False, False, True],
+                [True, False, False, True],
+                [False, True, True, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_offcenter(self):
         """Test specifying off center location"""
         actual = cm.ring_mask((5, 5), 1, 2, center=(2, 3))
-        expected = np.array([[False, False, False, True,  False],
-                             [False, False, True,  False, True],
-                             [False, True,  False, False, False],
-                             [False, False, True,  False, True],
-                             [False, False, False, True,  False]])
+        expected = np.array(
+            [
+                [False, False, False, True, False],
+                [False, False, True, False, True],
+                [False, True, False, False, False],
+                [False, False, True, False, True],
+                [False, False, False, True, False],
+            ]
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
 
 class Test_crater_floor_mask(unittest.TestCase):
     """Test crater floor mask function"""
-    def setUp(self):
-        import os.path as p
-        import craterpy
-        from craterpy.dataset import CraterpyDataset
 
-        self.data_path = p.join(craterpy.__path__[0], 'data')
-        self.moon_tif = p.join(self.data_path, 'moon.tif')
+    def setUp(self):
+        self.data_path = p.join(craterpy.__path__[0], "data")
+        self.moon_tif = p.join(self.data_path, "moon.tif")
         self.cds = CraterpyDataset(self.moon_tif, radius=1737)
-    
+
     def test_floor_mask_equator(self):
         """Test floor mask at equator"""
         lat, lon = (0, 0)
-        rad = 2 * self.cds.calc_mpp(lat) / 1000 # 2 pixel radius
+        rad = 2 * self.cds.calc_mpp(lat) / 1000  # 2 pixel radius
         croi = CraterRoi(self.cds, lat, lon, rad, 1)
         actual = cm.crater_floor_mask(croi, 1)
-        expected = np.array([[0, 1, 1, 0],
-                             [1, 1, 1, 1],
-                             [1, 1, 1, 1],
-                             [0, 1, 1, 0]], dtype=bool)
+        expected = np.array(
+            [[0, 1, 1, 0], [1, 1, 1, 1], [1, 1, 1, 1], [0, 1, 1, 0]],
+            dtype=bool,
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
     def test_floor_mask_mid_lat(self):
         """Test floor mask at mid latitude"""
         lat, lon = (45, 0)
-        rad = 2.5 * self.cds.calc_mpp(lat) / 1000 # 2.5 pixel radius
+        rad = 2.5 * self.cds.calc_mpp(lat) / 1000  # 2.5 pixel radius
         croi = CraterRoi(self.cds, lat, lon, rad, 1)
         actual = cm.crater_floor_mask(croi, 1)
-        expected = np.array([[0, 0, 1, 0, 0],
-                             [1, 1, 1, 1, 1],
-                             [0, 0, 1, 0, 0]], dtype=bool)
+        expected = np.array(
+            [[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0]], dtype=bool
+        )
         self.assertIsNone(np.testing.assert_array_equal(actual, expected))
 
 
-class Test_crater_ring_mask(unittest.TestCase):
-    """Test crater ring mask"""
-    pass  # TODO: implement
+# class Test_crater_ring_mask(unittest.TestCase):
+#     """Test crater ring mask"""
+#     pass  # TODO: implement
 
 
-class Test_polygon_mask(unittest.TestCase):
-    """Test polygon mask"""
-    pass  # TODO: implement
+# class Test_polygon_mask(unittest.TestCase):
+#     """Test polygon mask"""
+#     pass  # TODO: implement
diff --git a/craterpy/tests/test_quickstats.py b/craterpy/tests/test_quickstats.py
index b7947a6..4367875 100644
--- a/craterpy/tests/test_quickstats.py
+++ b/craterpy/tests/test_quickstats.py
@@ -1,7 +1,4 @@
-"""
-Suite of unittests for classes found in /acerim/acestats.py.
-"""
-from __future__ import division, print_function, absolute_import
+"""Unittest quickstats.py"""
 import unittest
 import numpy as np
 from craterpy import quickstats as qs
@@ -9,6 +6,7 @@
 
 class Test_quickstats(unittest.TestCase):
     """Test the basic stats found in quickstats.py"""
+
     def setUp(self):
         self.data_arr = np.array([4, 4, 8, 3, 4, -1, 0, -5, 1, -10, 3])
 
diff --git a/craterpy/tests/test_roi.py b/craterpy/tests/test_roi.py
index 0526f5c..51428bb 100644
--- a/craterpy/tests/test_roi.py
+++ b/craterpy/tests/test_roi.py
@@ -1,58 +1,107 @@
-from __future__ import division, print_function, absolute_import
+"""Unittest roi.py"""
 import os.path as p
 import unittest
 import numpy as np
+import craterpy
+import craterpy.roi as croi
 from craterpy.dataset import CraterpyDataset
-from craterpy.roi import CraterRoi
-from craterpy.masking import circle_mask
+
 
 class TestCraterRoi(unittest.TestCase):
     """Test CraterpyDataset object"""
+
     def setUp(self):
-        import craterpy
-        self.data_path = p.join(craterpy.__path__[0], 'data')
-        self.moon_tif = p.join(self.data_path, 'moon.tif')
-        self.cds = CraterpyDataset(self.moon_tif, radius=1737)
-        self.roi = CraterRoi(self.cds, 0, 0, 100)
+        self.data_path = p.join(craterpy.__path__[0], "data")
+        self.moon_tif = p.join(self.data_path, "moon.tif")
+        self.cds = CraterpyDataset(self.moon_tif)
+        self.roi = croi.CraterRoi(self.cds, 0, 0, 100)
 
     def test_roi_import(self):
         """Test import"""
-        roi = CraterRoi(self.cds, 0, 0, 100)
+        roi = croi.CraterRoi(self.cds, 0, 0, 100)
         self.assertIsNotNone(roi)
 
     def test_get_extent(self):
         """Test _get_extent"""
-        roi = CraterRoi(self.cds, 0, 0, 16)
-        actual = roi._get_extent()
-        expected = (-0.5278, 0.5278, -0.5278, 0.5278)
+        actual = croi.get_extent(self.cds, 0, 0, 40)
+        expected = np.array([-1.3191, 1.3191, -1.3191, 1.3191])
         np.testing.assert_almost_equal(actual, expected, 4)
-        roi = CraterRoi(self.cds, 20, 20, 16)
-        actual = roi.extent
-        expected = (19.4384, 20.5616, 19.4722, 20.5278)
+        actual = croi.get_extent(self.cds, 0, 0, 40, wsize=5)
+        expected = 5 * expected
         np.testing.assert_almost_equal(actual, expected, 4)
 
     def test_get_roi(self):
-        roi = CraterRoi(self.cds, 0, 0, 50)
-        arr = roi._get_roi()
-        self.assertIsNotNone(arr)
-        roi180 = CraterRoi(self.cds, 0, 180, 50)
-        arr180 = roi180.roi
-        self.assertIsNotNone(arr180)
-
-    # TODO: fix this test
-    # def test_filter(self):
-    #     roi = self.roi
-    #     roi.filter(0, 100)
-    #     self.assertTrue(np.nanmax(roi.roi) <= 100)
-    #     roi.filter(0, 50, strict=True)
-    #     self.assertTrue(np.nanmax(roi.roi) < 50)
-
-    # def test_mask(self):
-    #     shape, rad = self.roi.roi.shape, self.roi.rad
-    #     mask = circle_mask(shape, rad)
-    #     masked_roi = self.roi.mask(mask)
-    #     self.assertTrue(np.isnan(masked_roi[shape[0]//2, shape[1]//2]))
-    #     self.assertTrue(np.isnan(self.roi.roi[shape[0]//2, shape[1]//2]))
-
-    # def test_plot(self):
-    #     self.assertIsNotNone(self.roi.plot())
+        """Test get_roi"""
+        actual = croi.get_roi_latlon(self.cds, 0, 0.5, 0, 0.5)
+        expected = np.array([[41, 43], [41, 41]])
+        np.testing.assert_array_almost_equal(actual, expected)
+
+    def test_get_roi_wrap_360(self):
+        """Test roi that extends across dataset bounds"""
+        # Test wrap right
+        actual = croi.get_roi_latlon(self.cds, 179.5, 180.25, 0, 0.5)
+        expected = np.concatenate(
+            [
+                croi.get_roi_latlon(self.cds, 179.5, 180, 0, 0.5),
+                croi.get_roi_latlon(self.cds, -180, -179.75, 0, 0.5),
+            ],
+            axis=1,
+        )
+        np.testing.assert_equal(actual, expected)
+
+        # Test wrap left is the same
+        actual = croi.get_roi_latlon(self.cds, -180.5, -179.75, 0, 0.5)
+        np.testing.assert_equal(actual, expected)
+
+    def test_get_roi_oob(self):
+        """Test get_roi with extent out of bounds for cds"""
+        with self.assertRaises(ValueError):
+            _ = croi.get_roi_latlon(self.cds, 0, 0, 91, 0)
+
+    def test_string_repr(self):
+        """Test string representation of CraterRoi"""
+        actual = str(self.roi)
+        expected = "CraterRoi at (0N, 0E) with radius 100 km"
+        self.assertEqual(actual, expected)
+
+    def test_filter(self):
+        """Test filter method"""
+        roi = croi.CraterRoi(self.cds, 0, 0, 10)
+        # Filter non-inclusive
+        roi.filter(36, 39)
+        actual = roi.roi
+        expected = np.array(([36, np.nan], [36, 38]))
+        np.testing.assert_array_equal(actual, expected)
+
+        # Filter inclusive (strict)
+        roi.filter(36, 39, strict=True)
+        actual = roi.roi
+        expected = np.array(([np.nan, np.nan], [np.nan, 38]))
+        np.testing.assert_array_equal(actual, expected)
+
+    def test_mask_nan(self):
+        """Test mask"""
+        roi = croi.CraterRoi(self.cds, 0, 0, 10)
+        mask = np.array([[True, False], [True, True]])
+        roi.mask(mask)
+        actual = roi.roi
+        expected = np.array([[np.nan, 40], [np.nan, np.nan]])
+        np.testing.assert_array_equal(actual, expected)
+
+    def test_mask_outside(self):
+        """Test mask inverted"""
+        roi = croi.CraterRoi(self.cds, 0, 0, 10)
+        mask = np.array([[True, False], [True, True]])
+        roi.mask(mask, outside=True)
+        actual = roi.roi
+        expected = np.array([[36, np.nan], [36, 38]])
+        np.testing.assert_array_equal(actual, expected)
+
+    def test_mask_fill(self):
+        """Test mask with fillvalue"""
+        roi = croi.CraterRoi(self.cds, 0, 0, 10)
+        mask = np.array([[True, False], [True, True]])
+        roi.mask(mask, fillvalue=100)
+        actual = roi.roi
+        expected = np.array([[100, 40], [100, 100]])
+        np.testing.assert_array_equal(actual, expected)
diff --git a/craterpy/tests/test_stats.py b/craterpy/tests/test_stats.py
index e485b69..ca0d0c4 100644
--- a/craterpy/tests/test_stats.py
+++ b/craterpy/tests/test_stats.py
@@ -1,13 +1,7 @@
-"""
-Suite of unittests for functions found in /craterpy/acefunctions.py.
-"""
-from __future__ import division, print_function, absolute_import
-import os.path as p
+"""Unittest stats.py"""
 import unittest
 import numpy as np
-import pandas as pd
 from craterpy import stats as cs
-from craterpy.dataset import CraterpyDataset
 
 
 class Test_quickstats_helpers(unittest.TestCase):
@@ -19,7 +13,7 @@ def test_list_quickstats(self):
         actual = cs._list_quickstats()[:3]
         np.testing.assert_array_equal(actual, expected)
 
-    def test_get_quickstats_fuctions_single(self):
+    def test_get_quickstats_functions_single(self):
         """Test the _get_quickstats_functions function with one stat"""
         expected_name = "maximum"
         stat_func = cs._get_quickstats_functions("maximum")
@@ -28,7 +22,7 @@ def test_get_quickstats_fuctions_single(self):
         self.assertEqual(actual_name, expected_name)
         self.assertEqual(callable(actual_func), True)
 
-    def test_get_quickstats_fuctions_multi(self):
+    def test_get_quickstats_functions_multi(self):
         """Test the _get_quickstats_functions function with list of stats"""
         expected_names = ["maximum", "mean"]
         stats_funcs = cs._get_quickstats_functions(["maximum", "mean"])
@@ -37,36 +31,36 @@ def test_get_quickstats_fuctions_multi(self):
         self.assertEqual(actual_names, expected_names)
         self.assertEqual(funcs_callable, [True, True])
 
-    def test_get_quickstats_fuctions_undefined(self):
+    def test_get_quickstats_functions_undefined(self):
         """Test that undefined stat raises Exception"""
         self.assertRaises(ValueError, cs._get_quickstats_functions, "NotAStat")
 
 
-# Test Compute Stats
-class Test_compute_stats(unittest.TestCase):
-    """Test computeStats function"""
-    def setUp(self):
-        import craterpy
-        self.data_path = p.join(craterpy.__path__[0], 'data')
-        self.crater_csv = p.join(self.data_path, 'craters.csv')
-        self.moon_tif = p.join(self.data_path, 'moon.tif')
-        self.df = pd.read_csv(self.crater_csv)
-        self.cds = CraterpyDataset(self.moon_tif, radius=1737)
+# # Test Compute Stats
+# class Test_compute_stats(unittest.TestCase):
+#     """Test computeStats function"""
 
-    def test_one_crater_one_stat(self):
-        """Test mean on first crater in df"""
-        # af.compute_stats(self.df, self.cds, 'mean', self.cdf.index[0:5])
-        # TODO: implement
-        pass
+#     def setUp(self):
+#         self.data_path = p.join(craterpy.__path__[0], "data")
+#         self.crater_csv = p.join(self.data_path, "craters.csv")
+#         self.moon_tif = p.join(self.data_path, "moon.tif")
+#         self.df = pd.read_csv(self.crater_csv)
+#         self.cds = CraterpyDataset(self.moon_tif, radius=1737)
 
-    def test_one_crater_many_stats(self):
-        pass
+# def test_one_crater_one_stat(self):
+#     """Test mean on first crater in df"""
+#     # af.compute_stats(self.df, self.cds, 'mean', self.cdf.index[0:5])
+#     # TODO: implement
+#     pass
 
-    def test_many_craters_many_stats(self):
-        pass
+# def test_one_crater_many_stats(self):
+#     pass
 
-    def test_crater_stats(self):
-        pass
+# def test_many_craters_many_stats(self):
+#     pass
 
-    def test_ejecta_stats(self):
-        pass
+# def test_crater_stats(self):
+#     pass
+
+# def test_ejecta_stats(self):
+#     pass