Repository: lbl-anp/becquerel (language: python)

Issue:
Github actions
Travis is dropping unlimited support for OSS so lets move to github actions

- [ ] Add github actions workflow

Future work:

- [ ] Add `black` formatting
- [ ] Add CD to pypi


Discussion / hints:

Fix patch:
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 565065e0..00000000
--- a/.pylintrc
+++ /dev/null
@@ -1,407 +0,0 @@
-[MASTER]
-
-# Specify a configuration file.
-#rcfile=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS
-
-# Add files or directories matching the regex patterns to the blacklist. The
-# regex matches against base names, not paths.
-ignore-patterns=
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# List of plugins (as comma separated values of python modules names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-# Use multiple processes to speed up Pylint.
-jobs=1
-
-# Allow loading of arbitrary C extensions. Extensions are imported into the
-# active Python interpreter and may run arbitrary code.
-unsafe-load-any-extension=no
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code
-extension-pkg-whitelist=numpy
-
-# Allow optimization of some AST trees. This will activate a peephole AST
-# optimizer, which will apply various small optimizations. For instance, it can
-# be used to obtain the result of joining multiple strings with the addition
-# operator. Joining a lot of strings can lead to a maximum recursion error in
-# Pylint and this flag can prevent that. It has one side effect, the resulting
-# AST will be different than the one from reality. This option is deprecated
-# and it will be removed in Pylint 2.0.
-optimize-ast=no
-
-
-[MESSAGES CONTROL]
-
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
-confidence=
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-#enable=
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
-disable=invalid-name
-
-
-[REPORTS]
-
-# Set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html. You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-output-format=colorized
-
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]". This option is deprecated
-# and it will be removed in Pylint 2.0.
-files-output=no
-
-# Tells whether to display a full report or only the messages
-reports=yes
-
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details
-#msg-template=
-
-
-[BASIC]
-
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_,x,y,z,ax,fig
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
-
-# List of decorators that produce properties, such as abc.abstractproperty. Add
-# to this list to register other decorators that produce valid properties.
-property-classes=abc.abstractproperty
-
-# Regular expression matching correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression matching correct constant names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression matching correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for function names
-function-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for method names
-method-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for attribute names
-attr-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for argument names
-argument-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for variable names
-variable-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
-
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-
-[ELIF]
-
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
-
-
-[FORMAT]
-
-# Maximum number of characters on a single line.
-max-line-length=80
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,dict-separator
-
-# Maximum number of lines in a module
-max-module-lines=1000
-
-# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-# Number of spaces of indent required inside a hanging  or continued line.
-indent-after-paren=4
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
-
-
-[LOGGING]
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format
-logging-modules=logging
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
-
-
-[SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
-
-[SPELLING]
-
-# Spelling dictionary name. Available dictionaries: none. To make it working
-# install python-enchant package.
-spelling-dict=
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to indicated private dictionary in
-# --spelling-private-dict-file option instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis. It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# List of class names for which member attributes should not be checked (useful
-# for classes with dynamically set attributes). This supports the use of
-# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-# List of decorators that produce context managers, such as
-# contextlib.contextmanager. Add to this list to register other decorators that
-# produce valid context managers.
-contextmanager-decorators=contextlib.contextmanager
-
-
-[VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,_cb
-
-# List of qualified module names which can have objects that can redefine
-# builtins.
-redefining-builtins-modules=six.moves,future.builtins
-
-
-[CLASSES]
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
-
-
-[DESIGN]
-
-# Maximum number of arguments for function / method
-max-args=6
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
-
-# Maximum number of locals for function / method body
-max-locals=15
-
-# Maximum number of return / yield for function / method body
-max-returns=6
-
-# Maximum number of branch for function / method body
-max-branches=16
-
-# Maximum number of statements in function / method body
-max-statements=50
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=1
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-# Maximum number of boolean expressions in a if statement
-max-bool-expr=5
-
-
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=optparse
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-# Force import order to recognize a module as part of the standard
-# compatibility libraries.
-known-standard-library=
-
-# Force import order to recognize a module as part of a third party library.
-known-third-party=enchant
-
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9743f406..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-dist: bionic
-language: python
-python:
-    - "2.7"
-    - "3.6"
-    - "3.7"
-    - "3.8"
-install:
-    - pip install --upgrade pip setuptools wheel
-    - pip install --only-binary=numpy,scipy numpy scipy
-    - pip install -r requirements-dev.txt
-    - python setup.py install
-script: "python setup.py test"
-branches:
-    only:
-        - main
-notifications:
-  slack:
-    secure: fbsAu4oLvRWKGpPLzJcjobftOK1bOjmvjkrJbQ2+QcrpWqhsU0oSrBr4812zOQBbtU3LkyAgOYOMeKtIqkxaUaZed+73Lv69ZpdBpzx0tY9Oj5Qm/sUYd8g2NjYnK3GpbSp3rK0CVMIhVAopM14GYqWTAoCLCeyDw5jNnx9gLW0AQN35dozKH0kEdp/q2kMt3vsd8/hauJjtiYsBzlIh4s+92qZiYgKkHzqvX27KUfwWNXve18QxYKlzldApVtmstm0J2o+tV8Wzx3x9FBSWJK40pXjVT/KEdWEdCLnh85ZtzEdRc7sRThDBKxQBuGkGm78a4pD5fj7imM7eVR9mQK9+nl1JJ/BOVwIz1ij7kF9Kl8WnGZkfU0W5eRwaJMXHv9ohP3yXXccloAjCHPS+Dr9nGeFNQKuGSTj2Pd9NDPhKh350hB1qA4cIu9uerrcHZEaSab0Ethc5xHPELZQXPmnthuPPlRSoCTaO9G2RKp07KdiTaC6UGUw9rkzaJEmTQU9Xzcpog3AdO/cBVQ7EMsWyPSQqn8vk9QO8Sj47h009IE4UzBbkfnDW2QYzv0LOgC8IvAdfuL6yo2ljATqFz9ppbPGnNEQA+FpBiuJKO/yI8k2NPLtKKQ5G9xPVriXwD0OMeMGLQk+yRrXmF3bOZ40b8eNpH/8AkwV1QgYsKBU=
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5ab8ec3b..c2316994 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -47,19 +47,16 @@ Use [google standards](https://google.github.io/styleguide/pyguide.html)
 
 ### Linter
 
-* Use `flake8` in your IDE
-* Use `pylint` from command line (as in style guide)
+Use the linter of your choice.
+The code style will be checked with [`black`](https://black.readthedocs.io/en/stable/) in the testing.
+We like to use [`flake8`](https://flake8.pycqa.org/en/latest/) and/or [`pylance`](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance).
 
 ### Checklist for code contributions:
-  - [ ] Branch off of `main` and name the branch `feature-XX` or `issue-XX`
+  - [ ] Branch off of `main`
   - [ ] Develop the feature or fix
   - [ ] Write tests to cover all use cases
-  - [ ] Ensure all tests pass (`python setup.py test`)
+  - [ ] Ensure all tests pass (`pytest`)
   - [ ] Ensure test coverage is >95%
-  - [ ] Ensure there are no linter issues:
-    - `flake8 becquerel tests`
-    - `pydocstyle becquerel tests`
-    - `pylint becquerel tests`
+  - [ ] Autoformat (`black .`)
   - [ ] Spellcheck your code and docstrings
-  - [ ] Check style is [consistent with Google Python style guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md)
   - [ ] Push branch to GitHub and create a pull request to `main`
diff --git a/README.md b/README.md
index d1038b5d..794a8ee0 100644
--- a/README.md
+++ b/README.md
@@ -43,16 +43,13 @@ The dependencies `beautifulsoup4`, `lxml` and `html5lib` are necessary for
 [`pandas`][1].
 
 Developers require additional requirements which are listed in
-`requirements-dev.txt`. We use [`pytest`][2] for unit testing and encourage
-contributors to use [`pylint`][3], [`pycodestyle`][4], [`pydocstyle`][5] and
-[`yapf`][6] for proper code formatting.
+`requirements-dev.txt`. We use [`pytest`][2] for unit testing, [`black`][3] for
+code formatting and are converting to [`numpydoc`][4].
 
 [1]: https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies
-[2]: https://docs.pytest.org
-[3]: https://pylint.readthedocs.io
-[4]: http://pycodestyle.pycqa.org
-[5]: http://www.pydocstyle.org
-[6]: https://github.com/google/yapf
+[2]: https://docs.pytest.org/en/latest/
+[3]: https://black.readthedocs.io/en/stable/
+[4]: https://numpydoc.readthedocs.io/en/latest/format.html
 
 ## Copyright Notice
 
diff --git a/becquerel/__init__.py b/becquerel/__init__.py
index 56b34bca..454ace1a 100644
--- a/becquerel/__init__.py
+++ b/becquerel/__init__.py
@@ -12,8 +12,13 @@
 from .core.energycal import LinearEnergyCal, EnergyCalError, BadInput
 from .core.utils import UncertaintiesError
 from .core.plotting import SpectrumPlotter, PlottingError
-from .core.peakfinder import (PeakFilter, PeakFilterError,
-                              GaussianPeakFilter, PeakFinder, PeakFinderError)
+from .core.peakfinder import (
+    PeakFilter,
+    PeakFilterError,
+    GaussianPeakFilter,
+    PeakFinder,
+    PeakFinderError,
+)
 from .core.autocal import AutoCalibrator, AutoCalibratorError
 
 from .core import utils
@@ -28,15 +33,36 @@
 from .tools import materials
 
 import warnings
-warnings.simplefilter('default', DeprecationWarning)
 
-__all__ = ['core', 'parsers', 'tools',
-           'rebin', 'RebinError', 'RebinWarning',
-           'Spectrum', 'SpectrumError', 'SpectrumWarning', 'SpectrumPlotter',
-           'PlottingError', 'UncalibratedError', 'LinearEnergyCal',
-           'EnergyCalError', 'BadInput', 'UncertaintiesError',
-           'PeakFilter', 'PeakFilterError',
-           'GaussianPeakFilter', 'PeakFinder', 'PeakFinderError',
-           'AutoCalibrator', 'AutoCalibratorError',
-           '__description__', '__url__', '__version__', '__license__',
-           '__copyright__']
+warnings.simplefilter("default", DeprecationWarning)
+
+__all__ = [
+    "core",
+    "parsers",
+    "tools",
+    "rebin",
+    "RebinError",
+    "RebinWarning",
+    "Spectrum",
+    "SpectrumError",
+    "SpectrumWarning",
+    "SpectrumPlotter",
+    "PlottingError",
+    "UncalibratedError",
+    "LinearEnergyCal",
+    "EnergyCalError",
+    "BadInput",
+    "UncertaintiesError",
+    "PeakFilter",
+    "PeakFilterError",
+    "GaussianPeakFilter",
+    "PeakFinder",
+    "PeakFinderError",
+    "AutoCalibrator",
+    "AutoCalibratorError",
+    "__description__",
+    "__url__",
+    "__version__",
+    "__license__",
+    "__copyright__",
+]
diff --git a/becquerel/core/autocal.py b/becquerel/core/autocal.py
index 67c4a15e..29ace186 100644
--- a/becquerel/core/autocal.py
+++ b/becquerel/core/autocal.py
@@ -1,6 +1,5 @@
 """Automatic calibration by associating peaks with energies."""
 
-from __future__ import print_function
 from itertools import combinations
 import matplotlib.pyplot as plt
 import numpy as np
@@ -52,17 +51,18 @@ def fit_gain(channels, snrs, energies):
     """
     if len(channels) != len(energies):
         raise AutoCalibratorError(
-            'Number of channels ({}) must equal # of energies ({})'.format(
-                len(channels), len(energies)))
+            f"Number of channels ({len(channels)}) must equal # of energies "
+            f"({len(energies)})"
+        )
     if len(channels) != len(snrs):
         raise AutoCalibratorError(
-            'Number of channels ({}) must equal # of SNRs ({})'.format(
-                len(channels), len(snrs)))
+            f"Number of channels ({len(channels)}) must equal # of SNRs ({len(snrs)})"
+        )
     x = np.asarray(channels)
     s = np.asarray(snrs)
     y = np.asarray(energies)
-    S2X = (s**2 * x).sum()
-    S2Y = (s**2 * y).sum()
+    S2X = (s ** 2 * x).sum()
+    S2Y = (s ** 2 * y).sum()
     gain = S2Y / S2X
     return gain
 
@@ -93,41 +93,50 @@ def fom_gain(channels, snrs, energies):
     """
     if len(channels) != len(energies):
         raise AutoCalibratorError(
-            'Number of channels ({}) must equal # of energies ({})'.format(
-                len(channels), len(energies)))
+            f"Number of channels ({len(channels)}) must equal # of energies "
+            f"({len(energies)})"
+        )
     if len(channels) != len(snrs):
         raise AutoCalibratorError(
-            'Number of channels ({}) must equal # of SNRs ({})'.format(
-                len(channels), len(snrs)))
+            f"Number of channels ({len(channels)}) must equal # of SNRs ({len(snrs)})"
+        )
     x = np.asarray(channels)
     s = np.asarray(snrs)
     y = np.asarray(energies)
     N = len(channels)
-    S2X = (s**2 * x).sum()
-    S2Y = (s**2 * y).sum()
-    squared_errs = s**2 * (S2Y * x - S2X * y)**2 / (S2X - s**2 * x)**2 / x
+    S2X = (s ** 2 * x).sum()
+    S2Y = (s ** 2 * y).sum()
+    squared_errs = s ** 2 * (S2Y * x - S2X * y) ** 2 / (S2X - s ** 2 * x) ** 2 / x
     return squared_errs.sum() / N / (N - 1)
 
 
 def find_best_gain(
-        channels, snrs, required_energies, optional=(),
-        gain_range=(1e-3, 1e3), de_max=10., verbose=False):
+    channels,
+    snrs,
+    required_energies,
+    optional=(),
+    gain_range=(1e-3, 1e3),
+    de_max=10.0,
+    verbose=False,
+):
     """Find the gain that gives the best match of peaks to energies."""
     if len(channels) != len(snrs):
         raise AutoCalibratorError(
-            'Number of channels ({}) must equal # of SNRs ({})'.format(
-                len(channels), len(snrs)))
+            f"Number of channels ({len(channels)}) must equal # of SNRs ({len(snrs)})"
+        )
     if len(channels) < 2:
         raise AutoCalibratorError(
-            'Number of channels ({}) must be at least 2'.format(len(channels)))
+            f"Number of channels ({len(channels)}) must be at least 2"
+        )
     if len(required_energies) < 2:
         raise AutoCalibratorError(
-            'Number of required energies ({}) must be at least 2'.format(
-                len(required_energies)))
+            f"Number of required energies ({len(required_energies)}) must be at least 2"
+        )
     if len(channels) < len(required_energies):
         raise AutoCalibratorError(
-            'Number of channels ({}) must be >= required energies ({})'.format(
-                len(channels), len(required_energies)))
+            f"Number of channels ({len(channels)}) must be >= required energies "
+            f"({len(required_energies)})"
+        )
 
     channels = np.array(channels)
     snrs = np.array(snrs)
@@ -143,7 +152,7 @@ def find_best_gain(
     best_ergs = None
     while n_set >= n_req:
         if verbose:
-            print('Searching groups of {}'.format(n_set))
+            print(f"Searching groups of {n_set}")
         # cycle through energy combinations
         for comb_erg in combinations(optional, n_set - n_req):
             comb_erg = np.array(comb_erg)
@@ -154,8 +163,7 @@ def find_best_gain(
             max_chan = (comb_erg / gain_range[0]).max()
             # cycle through channel combinations
             chan_inds = np.arange(len(channels))
-            chan_inds = chan_inds[
-                (min_chan <= channels) & (channels <= max_chan)]
+            chan_inds = chan_inds[(min_chan <= channels) & (channels <= max_chan)]
             for chan_indices in combinations(chan_inds, n_set):
                 chan_indices = np.array(chan_indices, dtype=int)
                 comb_chan = np.array(channels)[chan_indices]
@@ -175,16 +183,17 @@ def find_best_gain(
                 # calculate figure of merit
                 fom = fom_gain(comb_chan, comb_snr, comb_erg)
                 if verbose:
-                    print('v')
-                    s0 = 'Valid calibration found:\n'
-                    s0 += 'FOM: {:15.9f}'.format(fom)
-                    s0 += '  gain: {:6.3f}'.format(gain)
-                    s0 += '  ergs: {:50s}'.format(str(comb_erg))
-                    s0 += '  de: {:50s}'.format(str(de))
-                    s0 += '  chans: {:40s}'.format(str(comb_chan))
-                    print(s0)
+                    print("v")
+                    print(
+                        "Valid calibration found:\n"
+                        f"FOM: {fom:15.9f}"
+                        f"  gain: {gain:6.3f}"
+                        f"  ergs: {str(comb_erg):50s}"
+                        f"  de: {str(de):50s}"
+                        f"  chans: {str(comb_chan):40s}"
+                    )
                 if best_fom is None:
-                    best_fom = fom + 1.
+                    best_fom = fom + 1.0
                 if fom < best_fom:
                     best_fom = fom
                     best_gain = gain
@@ -192,23 +201,24 @@ def find_best_gain(
                     best_snrs = comb_snr
                     best_ergs = comb_erg
                     if verbose:
-                        s0 = 'Best calibration so far:\n'
-                        s0 += 'FOM: {:15.9f}'.format(best_fom)
-                        s0 += '  gain: {:6.3f}'.format(best_gain)
-                        s0 += '  ergs: {:50s}'.format(str(best_ergs))
-                        s0 += '  de: {:50s}'.format(str(de))
-                        s0 += '  chans: {:40s}'.format(str(best_chans))
-                        print(s0)
+                        print(
+                            "Best calibration so far:\n"
+                            f"FOM: {best_fom:15.9f}"
+                            f"  gain: {best_gain:6.3f}"
+                            f"  ergs: {str(best_ergs):50s}"
+                            f"  de: {str(de):50s}"
+                            f"  chans: {str(best_chans):40s}"
+                        )
         n_set -= 1
     if best_gain is None:
         return None
     else:
-        print('found best gain: %f keV/channel' % best_gain)
+        print("found best gain: %f keV/channel" % best_gain)
         return {
-            'gain': best_gain,
-            'channels': best_chans,
-            'snrs': best_snrs,
-            'energies': best_ergs,
+            "gain": best_gain,
+            "channels": best_chans,
+            "snrs": best_snrs,
+            "energies": best_ergs,
         }
 
 
@@ -243,59 +253,68 @@ def set_peaks(self, peakfinder):
         """Use the peaks found by the PeakFinder."""
         if not isinstance(peakfinder, PeakFinder):
             raise AutoCalibratorError(
-                'Argument must be a PeakFinder, not {}'.format(
-                    type(peakfinder)))
+                f"Argument must be a PeakFinder, not {type(peakfinder)}"
+            )
         self.peakfinder = peakfinder
 
     def plot(self, **kwargs):
         """Plot the peaks found and the peaks used to fit."""
         self.peakfinder.plot(peaks=True, **kwargs)
         for chan, snr in zip(self.fit_channels, self.fit_snrs):
-            plt.plot([chan] * 2, [0, snr], 'g-', lw=2)
-            plt.plot(chan, snr, 'go')
+            plt.plot([chan] * 2, [0, snr], "g-", lw=2)
+            plt.plot(chan, snr, "go")
 
-    def fit(self, required_energies, optional=(),
-            gain_range=(1e-4, 1e3), de_max=10., verbose=False):
+    def fit(
+        self,
+        required_energies,
+        optional=(),
+        gain_range=(1e-4, 1e3),
+        de_max=10.0,
+        verbose=False,
+    ):
         """Find the gain that gives the best match of peaks to energies."""
-        if len(self.peakfinder.centroids) == 1 and \
-                len(required_energies) == 1:
+        if len(self.peakfinder.centroids) == 1 and len(required_energies) == 1:
             # special case: only one line identified
             self.fit_channels = list(self.peakfinder.centroids)
             self.fit_snrs = list(self.peakfinder.snrs)
             self.fit_energies = list(required_energies)
             gain = required_energies[0] / self.peakfinder.centroids[0]
             self.gain = gain
-            self.cal = LinearEnergyCal.from_coeffs(
-                {'offset': 0, 'slope': self.gain})
+            self.cal = LinearEnergyCal.from_coeffs({"offset": 0, "slope": self.gain})
             return
         # handle the usual case: multiple lines to match
         if len(self.peakfinder.centroids) < 2:
             raise AutoCalibratorError(
-                'Need more than {} peaks to fit'.format(
-                    len(self.peakfinder.centroids)))
+                f"Need more than {len(self.peakfinder.centroids)}"
+            )
         if len(required_energies) < 2:
             raise AutoCalibratorError(
-                'Need more than {} energies to fit'.format(
-                    len(required_energies)))
+                f"Need more than {len(required_energies)} energies to fit"
+            )
         if len(self.peakfinder.centroids) < len(required_energies):
             raise AutoCalibratorError(
-                'Require {} energies but only {} peaks are available'.format(
-                    len(required_energies), len(self.peakfinder.centroids)))
+                f"Require {len(required_energies)} energies but only "
+                f"{len(self.peakfinder.centroids)} peaks are available"
+            )
         fit = find_best_gain(
-            self.peakfinder.centroids, self.peakfinder.snrs,
-            required_energies, optional=optional, gain_range=gain_range,
-            de_max=de_max, verbose=verbose)
+            self.peakfinder.centroids,
+            self.peakfinder.snrs,
+            required_energies,
+            optional=optional,
+            gain_range=gain_range,
+            de_max=de_max,
+            verbose=verbose,
+        )
         if fit is None:
             self.fit_channels = []
             self.fit_snrs = []
             self.fit_energies = []
             self.gain = None
             self.cal = None
-            raise AutoCalibratorError('No valid fit was found')
+            raise AutoCalibratorError("No valid fit was found")
         else:
-            self.fit_channels = fit['channels']
-            self.fit_snrs = fit['snrs']
-            self.fit_energies = fit['energies']
-            self.gain = fit['gain']
-            self.cal = LinearEnergyCal.from_coeffs(
-                {'offset': 0, 'slope': self.gain})
+            self.fit_channels = fit["channels"]
+            self.fit_snrs = fit["snrs"]
+            self.fit_energies = fit["energies"]
+            self.gain = fit["gain"]
+            self.cal = LinearEnergyCal.from_coeffs({"offset": 0, "slope": self.gain})
diff --git a/becquerel/core/energycal.py b/becquerel/core/energycal.py
index cf5f08aa..ae84b029 100644
--- a/becquerel/core/energycal.py
+++ b/becquerel/core/energycal.py
@@ -5,6 +5,7 @@
 from future.utils import viewitems
 import numpy as np
 
+
 class EnergyCalError(Exception):
     """Base class for errors in energycal.py"""
 
@@ -63,14 +64,14 @@ def from_points(cls, chlist, kevlist, include_origin=False):
         """
 
         if chlist is None or kevlist is None:
-            raise BadInput('Channel list and energy list are required')
+            raise BadInput("Channel list and energy list are required")
 
         try:
             cond = len(chlist) != len(kevlist)
         except TypeError:
-            raise BadInput('Inputs must be one dimensional iterables')
+            raise BadInput("Inputs must be one dimensional iterables")
         if cond:
-            raise BadInput('Channels and energies must be same length')
+            raise BadInput("Channels and energies must be same length")
 
         cal = cls()
 
@@ -81,7 +82,7 @@ def from_points(cls, chlist, kevlist, include_origin=False):
             try:
                 cal.new_calpoint(ch, kev)
             except (ValueError, TypeError):
-                raise BadInput('Inputs must be one dimensional iterables')
+                raise BadInput("Inputs must be one dimensional iterables")
         cal.update_fit()
         return cal
 
@@ -166,7 +167,7 @@ def new_calpoint(self, ch, kev):
         """
 
         if kev in self._calpoints:
-            raise EnergyCalError('Calibration energy already exists')
+            raise EnergyCalError("Calibration energy already exists")
         self.add_calpoint(ch, kev)
 
     def rm_calpoint(self, kev):
@@ -268,7 +269,7 @@ def _set_coeff(self, name, val):
         if name in self.valid_coeffs:
             self._coeffs[name] = val
         else:
-            raise EnergyCalError('Invalid coefficient name: {}'.format(name))
+            raise EnergyCalError("Invalid coefficient name: {}".format(name))
 
     def update_fit(self):
         """Compute the calibration curve from the current points.
@@ -282,9 +283,9 @@ def update_fit(self):
         num_points = len(self._calpoints)
 
         if num_points == 0:
-            raise EnergyCalError('No calibration points; cannot calibrate')
+            raise EnergyCalError("No calibration points; cannot calibrate")
         elif num_points < num_coeffs:
-            raise EnergyCalError('Not enough calibration points to fit curve')
+            raise EnergyCalError("Not enough calibration points to fit curve")
         else:
             self._perform_fit()
 
@@ -319,15 +320,15 @@ def from_coeffs(cls, coeffs):
         """
 
         new_coeffs = {}
-        if 'p0' in coeffs and 'p1' in coeffs:
-            new_coeffs['b'] = coeffs['p1']
-            new_coeffs['c'] = coeffs['p0']
-        elif 'slope' in coeffs and 'offset' in coeffs:
-            new_coeffs['b'] = coeffs['slope']
-            new_coeffs['c'] = coeffs['offset']
-        elif 'm' in coeffs and 'b' in coeffs:
-            new_coeffs['b'] = coeffs['m']
-            new_coeffs['c'] = coeffs['b']
+        if "p0" in coeffs and "p1" in coeffs:
+            new_coeffs["b"] = coeffs["p1"]
+            new_coeffs["c"] = coeffs["p0"]
+        elif "slope" in coeffs and "offset" in coeffs:
+            new_coeffs["b"] = coeffs["slope"]
+            new_coeffs["c"] = coeffs["offset"]
+        elif "m" in coeffs and "b" in coeffs:
+            new_coeffs["b"] = coeffs["m"]
+            new_coeffs["c"] = coeffs["b"]
         else:
             new_coeffs = coeffs.copy()
         cal = super().from_coeffs(new_coeffs)
@@ -341,27 +342,25 @@ def valid_coeffs(self):
           a tuple of strings, the names of the coefficients for this curve
         """
 
-        return ('b', 'c')
+        return ("b", "c")
 
     @property
     def slope(self):
         """Return the slope coefficient value."""
 
         try:
-            return self._coeffs['b']
+            return self._coeffs["b"]
         except KeyError:
-            raise EnergyCalError(
-                'Slope coefficient not yet supplied or calculated.')
+            raise EnergyCalError("Slope coefficient not yet supplied or calculated.")
 
     @property
     def offset(self):
         """Return the offset coefficient value."""
 
         try:
-            return self._coeffs['c']
+            return self._coeffs["c"]
         except KeyError:
-            raise EnergyCalError(
-                'Offset coefficient not yet supplied or calculated.')
+            raise EnergyCalError("Offset coefficient not yet supplied or calculated.")
 
     def _ch2kev(self, ch):
         """Convert scalar OR np.array of channel(s) to energies.
@@ -394,5 +393,5 @@ def _perform_fit(self):
         """Do the actual curve fitting."""
 
         b, c = np.polyfit(self.channels, self.energies, 1)
-        self._set_coeff('b', b)
-        self._set_coeff('c', c)
+        self._set_coeff("b", b)
+        self._set_coeff("c", c)
diff --git a/becquerel/core/fitting.py b/becquerel/core/fitting.py
index 0ad18af9..0a3dce7c 100644
--- a/becquerel/core/fitting.py
+++ b/becquerel/core/fitting.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
 import warnings
 import inspect
 import numpy as np
@@ -8,13 +7,14 @@
 import matplotlib.pyplot as plt
 from matplotlib.gridspec import GridSpec
 from matplotlib.font_manager import FontProperties
-from .utils import isstring, bin_centers_from_edges
 
-FWHM_SIG_RATIO = np.sqrt(8*np.log(2))  # 2.35482
-SQRT_TWO = np.sqrt(2) #1.414213562
+FWHM_SIG_RATIO = np.sqrt(8 * np.log(2))  # 2.35482
+SQRT_TWO = np.sqrt(2)  # 1.414213562
+
 
 class FittingError(Exception):
     """Exception raised by Fitters."""
+
     pass
 
 
@@ -37,12 +37,16 @@ def line(x, m, b):
 
 
 def gauss(x, amp, mu, sigma):
-    return amp / sigma / np.sqrt(2. * np.pi) * \
-        np.exp(-(x - mu) ** 2. / (2. * sigma ** 2.))
+    return (
+        amp
+        / sigma
+        / np.sqrt(2.0 * np.pi)
+        * np.exp(-((x - mu) ** 2.0) / (2.0 * sigma ** 2.0))
+    )
 
 
 def erf(x, amp, mu, sigma):
-    return amp * 0.5 * (1. - scipy.special.erf((x - mu) / (SQRT_TWO * sigma)))
+    return amp * 0.5 * (1.0 - scipy.special.erf((x - mu) / (SQRT_TWO * sigma)))
 
 
 def exp(x, amp, lam):
@@ -54,10 +58,11 @@ def gausserf(x, ampgauss, amperf, mu, sigma):
 
 
 def expgauss(x, amp=1, mu=0, sigma=1.0, gamma=1.0):
-    gss = gamma*sigma*sigma
-    arg1 = gamma*(mu + gss/2.0 - x)
-    arg2 = (mu + gss - x)/(SQRT_TWO*sigma)
-    return amp*(gamma/2) * np.exp(arg1) * scipy.special.erfc(arg2)
+    gss = gamma * sigma * sigma
+    arg1 = gamma * (mu + gss / 2.0 - x)
+    arg2 = (mu + gss - x) / (SQRT_TWO * sigma)
+    return amp * (gamma / 2) * np.exp(arg1) * scipy.special.erfc(arg2)
+
 
 # -----------------------------------------------------------------------------
 # Fitting models
@@ -84,7 +89,7 @@ def _xy_right(y, x=None, num=4):
     """
     if x is not None:
         return np.mean(x[-num:]), np.mean(y[-num:])
-    return len(y)*0.5, np.mean(y[-num:])
+    return len(y) * 0.5, np.mean(y[-num:])
 
 
 def _xy_left(y, x=None, num=4):
@@ -106,54 +111,54 @@ def _xy_left(y, x=None, num=4):
     """
     if x is not None:
         return np.mean(x[:num]), np.mean(y[:num])
-    return len(y)*0.5, np.mean(y[:num])
+    return len(y) * 0.5, np.mean(y[:num])
 
 
 class ConstantModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(ConstantModel, self).__init__(constant, *args, **kwargs)
         # TODO: remove this min setting?
-        self.set_param_hint('{}c'.format(self.prefix), min=0.)
+        self.set_param_hint("{}c".format(self.prefix), min=0.0)
 
     def guess(self, y, x=None, dx=None, num=2):
         if dx is None:
             dx = np.ones_like(x)
-        c = (y[-1]/dx[-1] + y[0]/dx[0]) * 0.5
+        c = (y[-1] / dx[-1] + y[0] / dx[0]) * 0.5
         return [
-            ('{}c'.format(self.prefix), 'value', c),
+            ("{}c".format(self.prefix), "value", c),
         ]
 
 
 class LineModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(LineModel, self).__init__(line, *args, **kwargs)
 
     def guess(self, y, x=None, dx=None, num=2):
         if dx is None:
             dx = np.ones_like(x)
-        m = (y[-1]/dx[-1] - y[0]/dx[0])/(x[-1] - x[0])
-        b = ((y[-1]/dx[-1] + y[0]/dx[0]) - m*(x[1] + x[0])) * 0.5
+        m = (y[-1] / dx[-1] - y[0] / dx[0]) / (x[-1] - x[0])
+        b = ((y[-1] / dx[-1] + y[0] / dx[0]) - m * (x[1] + x[0])) * 0.5
         return [
-            ('{}m'.format(self.prefix), 'value', m),
-            ('{}b'.format(self.prefix), 'value', b),
+            ("{}m".format(self.prefix), "value", m),
+            ("{}b".format(self.prefix), "value", b),
         ]
 
 
 class GaussModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(GaussModel, self).__init__(gauss, *args, **kwargs)
         self.set_param_hint(
-            '{}fwhm'.format(self.prefix),
-            expr='{} * {}sigma'.format(FWHM_SIG_RATIO, self.prefix))
+            "{}fwhm".format(self.prefix),
+            expr="{} * {}sigma".format(FWHM_SIG_RATIO, self.prefix),
+        )
 
     def guess(self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5):
-        assert center_ratio < 1, \
-            'Center mask ratio cannot exceed 1: {}'.format(center_ratio)
-        assert width_ratio < 1, \
-            'Width mask ratio cannot exceed 1: {}'.format(width_ratio)
+        assert center_ratio < 1, "Center mask ratio cannot exceed 1: {}".format(
+            center_ratio
+        )
+        assert width_ratio < 1, "Width mask ratio cannot exceed 1: {}".format(
+            width_ratio
+        )
 
         if x is None:
             x = np.arange(0, len(y))
@@ -162,25 +167,23 @@ def guess(self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5):
 
         xspan = x[-1] - x[0]
         mu = x[0] + xspan * center_ratio
-        msk = ((x >= (mu - xspan * width_ratio)) &
-               (x <= mu + xspan * width_ratio))
+        msk = (x >= (mu - xspan * width_ratio)) & (x <= mu + xspan * width_ratio)
 
         # TODO: update this, minimizer creates NaN's if default sigma used (0)
-        sigma = xspan * width_ratio / 10.
-        amp = np.max(y[msk]) * np.sqrt(2*np.pi) * sigma  # new amplitude guess
+        sigma = xspan * width_ratio / 10.0
+        amp = np.max(y[msk]) * np.sqrt(2 * np.pi) * sigma  # new amplitude guess
         return [
-            ('{}amp'.format(self.prefix), 'value', amp),
-            ('{}amp'.format(self.prefix), 'min', 0.0),
-            ('{}mu'.format(self.prefix), 'value', mu),
-            ('{}mu'.format(self.prefix), 'min', x[0]),
-            ('{}mu'.format(self.prefix), 'max', x[-1]),
-            ('{}sigma'.format(self.prefix), 'value', sigma),
-            ('{}sigma'.format(self.prefix), 'min', 0.0),
+            ("{}amp".format(self.prefix), "value", amp),
+            ("{}amp".format(self.prefix), "min", 0.0),
+            ("{}mu".format(self.prefix), "value", mu),
+            ("{}mu".format(self.prefix), "min", x[0]),
+            ("{}mu".format(self.prefix), "max", x[-1]),
+            ("{}sigma".format(self.prefix), "value", sigma),
+            ("{}sigma".format(self.prefix), "min", 0.0),
         ]
 
 
 class ErfModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(ErfModel, self).__init__(erf, *args, **kwargs)
 
@@ -188,58 +191,59 @@ def guess(self, y, x=None, dx=None, center_ratio=0):
         xspan = x[-1] - x[0]
         mu = x[0] + xspan * center_ratio
         return [
-            ('{}amp'.format(self.prefix), 'value', y[0] - y[-1]),
-            ('{}mu'.format(self.prefix), 'value', mu),
-            ('{}sigma'.format(self.prefix), 'expr', 'gauss_sigma'),
+            ("{}amp".format(self.prefix), "value", y[0] - y[-1]),
+            ("{}mu".format(self.prefix), "value", mu),
+            ("{}sigma".format(self.prefix), "expr", "gauss_sigma"),
         ]
 
 
 class GaussErfModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(GaussErfModel, self).__init__(gausserf, *args, **kwargs)
         self.set_param_hint(
-            '{}fwhm'.format(self.prefix),
-            expr='{} * {}sigma'.format(FWHM_SIG_RATIO, self.prefix))
-
-    def guess(self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5,
-              amp_ratio=0.9):
-        assert center_ratio < 1, \
-            'Center mask ratio cannot exceed 1: {}'.format(center_ratio)
-        assert width_ratio < 1, \
-            'Width mask ratio cannot exceed 1: {}'.format(width_ratio)
+            "{}fwhm".format(self.prefix),
+            expr="{} * {}sigma".format(FWHM_SIG_RATIO, self.prefix),
+        )
+
+    def guess(
+        self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5, amp_ratio=0.9
+    ):
+        assert center_ratio < 1, "Center mask ratio cannot exceed 1: {}".format(
+            center_ratio
+        )
+        assert width_ratio < 1, "Width mask ratio cannot exceed 1: {}".format(
+            width_ratio
+        )
         if x is None:
             x = np.arange(0, len(y))
         if dx is None:
             dx = np.ones_like(x)
         xspan = x[-1] - x[0]
         mu = x[0] + xspan * center_ratio
-        msk = ((x >= (mu - xspan * width_ratio)) &
-               (x <= mu + xspan * width_ratio))
+        msk = (x >= (mu - xspan * width_ratio)) & (x <= mu + xspan * width_ratio)
 
-        sigma = xspan * width_ratio / 10.
-        amp = np.max(y[msk]) * np.sqrt(2*np.pi) * sigma
+        sigma = xspan * width_ratio / 10.0
+        amp = np.max(y[msk]) * np.sqrt(2 * np.pi) * sigma
         amp_gauss = amp * amp_ratio
-        amp_erf = amp * (1.0 - amp_ratio)/dx[0]/(np.sqrt(2*np.pi) * sigma)
+        amp_erf = amp * (1.0 - amp_ratio) / dx[0] / (np.sqrt(2 * np.pi) * sigma)
         return [
-            ('{}ampgauss'.format(self.prefix), 'value', amp_gauss),
-            ('{}ampgauss'.format(self.prefix), 'min', 0.0),
-            ('{}amperf'.format(self.prefix), 'value', amp_erf),
-            ('{}amperf'.format(self.prefix), 'min', 0.0),
-            ('{}mu'.format(self.prefix), 'value', mu),
-            ('{}mu'.format(self.prefix), 'min', x[0]),
-            ('{}mu'.format(self.prefix), 'max', x[-1]),
-            ('{}sigma'.format(self.prefix), 'value', sigma),
-            ('{}sigma'.format(self.prefix), 'min', 0.0),
+            ("{}ampgauss".format(self.prefix), "value", amp_gauss),
+            ("{}ampgauss".format(self.prefix), "min", 0.0),
+            ("{}amperf".format(self.prefix), "value", amp_erf),
+            ("{}amperf".format(self.prefix), "min", 0.0),
+            ("{}mu".format(self.prefix), "value", mu),
+            ("{}mu".format(self.prefix), "min", x[0]),
+            ("{}mu".format(self.prefix), "max", x[-1]),
+            ("{}sigma".format(self.prefix), "value", sigma),
+            ("{}sigma".format(self.prefix), "min", 0.0),
         ]
 
 
 class ExpModel(Model):
-
     def __init__(self, *args, **kwargs):
         super(ExpModel, self).__init__(exp, *args, **kwargs)
-        self.set_param_hint('{}amp'.format(self.prefix), min=0.)
-        self.set_param_hint('{}lam'.format(self.prefix), max=0.)
+        self.set_param_hint("{}amp".format(self.prefix), min=0.0)
+        self.set_param_hint("{}lam".format(self.prefix), max=0.0)
 
     def guess(self, y, x=None, dx=None, num=1):
         xl, yl = _xy_left(y, x=x, num=num)
@@ -248,10 +252,10 @@ def guess(self, y, x=None, dx=None, num=1):
         lam = (xl - xr) / np.log(yl / (yr + 0.0001))
         amp = yl / np.exp(xl / lam)
         return [
-            ('{}lam'.format(self.prefix), 'value', lam),
-            ('{}lam'.format(self.prefix), 'max', -1e-3),
-            ('{}amp'.format(self.prefix), 'value', amp),
-            ('{}amp'.format(self.prefix), 'min', 0.0),
+            ("{}lam".format(self.prefix), "value", lam),
+            ("{}lam".format(self.prefix), "max", -1e-3),
+            ("{}amp".format(self.prefix), "value", amp),
+            ("{}amp".format(self.prefix), "min", 0.0),
         ]
 
 
@@ -262,52 +266,54 @@ class ExpGaussModel(Model):
 
     def __init__(self, *args, **kwargs):
         super(ExpGaussModel, self).__init__(expgauss, **kwargs)
-        self.set_param_hint('{}sigma'.format(self.prefix), min=0)
-        self.set_param_hint('{}gamma'.format(self.prefix), min=0, max=1)
+        self.set_param_hint("{}sigma".format(self.prefix), min=0)
+        self.set_param_hint("{}gamma".format(self.prefix), min=0, max=1)
         # TODO: This is obviously wrong
         self.set_param_hint(
-            '{}fwhm'.format(self.prefix),
-            expr='{} * {}sigma'.format(FWHM_SIG_RATIO, self.prefix))
+            "{}fwhm".format(self.prefix),
+            expr="{} * {}sigma".format(FWHM_SIG_RATIO, self.prefix),
+        )
 
     def guess(self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5):
-        assert center_ratio < 1, \
-            'Center mask ratio cannot exceed 1: {}'.format(center_ratio)
-        assert width_ratio < 1, \
-            'Width mask ratio cannot exceed 1: {}'.format(width_ratio)
+        assert center_ratio < 1, "Center mask ratio cannot exceed 1: {}".format(
+            center_ratio
+        )
+        assert width_ratio < 1, "Width mask ratio cannot exceed 1: {}".format(
+            width_ratio
+        )
         if x is None:
             x = np.arange(0, len(y))
         if dx is None:
             dx = np.ones_like(x)
         xspan = x[-1] - x[0]
         mu = x[0] + xspan * center_ratio
-        msk = ((x >= (mu - xspan * width_ratio)) &
-               (x <= mu + xspan * width_ratio))
+        msk = (x >= (mu - xspan * width_ratio)) & (x <= mu + xspan * width_ratio)
 
         # TODO: update this, minimizer creates NaN's if default sigma used (0)
-        sigma = xspan * width_ratio / 10.
-        amp = np.max(y[msk]) * np.sqrt(2*np.pi) * sigma
+        sigma = xspan * width_ratio / 10.0
+        amp = np.max(y[msk]) * np.sqrt(2 * np.pi) * sigma
         # TODO: We miss gamma here
         return [
-            ('{}amp'.format(self.prefix), 'value', amp),
-            ('{}amp'.format(self.prefix), 'min', 0.0),
-            ('{}mu'.format(self.prefix), 'value', mu),
-            ('{}mu'.format(self.prefix), 'min', x[0]),
-            ('{}mu'.format(self.prefix), 'max', x[-1]),
-            ('{}sigma'.format(self.prefix), 'value', sigma),
-            ('{}sigma'.format(self.prefix), 'min', 0.0),
-            ('{}gamma'.format(self.prefix), 'value', 0.95),
-            ('{}gamma'.format(self.prefix), 'min', 0.0),
+            ("{}amp".format(self.prefix), "value", amp),
+            ("{}amp".format(self.prefix), "min", 0.0),
+            ("{}mu".format(self.prefix), "value", mu),
+            ("{}mu".format(self.prefix), "min", x[0]),
+            ("{}mu".format(self.prefix), "max", x[-1]),
+            ("{}sigma".format(self.prefix), "value", sigma),
+            ("{}sigma".format(self.prefix), "min", 0.0),
+            ("{}gamma".format(self.prefix), "value", 0.95),
+            ("{}gamma".format(self.prefix), "min", 0.0),
         ]
 
 
 MODEL_STR_TO_CLS = {
-    'constant': ConstantModel,
-    'line': LineModel,
-    'gauss': GaussModel,
-    'gausserf': GaussErfModel,
-    'erf': ErfModel,
-    'exp': ExpModel,
-    'expgauss': ExpGaussModel
+    "constant": ConstantModel,
+    "line": LineModel,
+    "gauss": GaussModel,
+    "gausserf": GaussErfModel,
+    "erf": ErfModel,
+    "exp": ExpModel,
+    "expgauss": ExpGaussModel,
 }
 
 
@@ -323,7 +329,7 @@ def guess(self, y, x=None, dx=None, center_ratio=0.5, width_ratio=0.5):
 
 
 class Fitter(object):
-    '''Base class for more specialized fit objects.
+    """Base class for more specialized fit objects.
 
     A note on interpreting fit results: ascribing meaning to histogram fit
     parameters is notoriously tricky, since the y-scale has units of counts
@@ -331,7 +337,7 @@ class Fitter(object):
     histogram bin width for area- or height-like parameters if the histogram is
     not already normalized by bin width. See, e.g., p. 171 of Bevington and
     Robinson, "Data reduction and error analysis for the physical sciences".
-    '''
+    """
 
     def __init__(self, model, x=None, y=None, y_unc=None, dx=None, roi=None):
         # Initialize
@@ -350,21 +356,20 @@ def __init__(self, model, x=None, y=None, y_unc=None, dx=None, roi=None):
         self._make_model(model)
         self.params = self.model.make_params()
         # Set data
-        self.set_data(x=x, y=y, y_unc=y_unc, dx=dx, roi=roi,
-                      update_defaults=True)
+        self.set_data(x=x, y=y, y_unc=y_unc, dx=dx, roi=roi, update_defaults=True)
 
     def __str__(self):
         return (
-            'bq.Fitter instance\n' +
-            '     name: {}\n'.format(self.name) +
-            '    model: {}\n'.format(self.model) +
-            '        x: {}\n'.format(self.x) +
-            '        y: {}\n'.format(self.y) +
-            '    y_unc: {}\n'.format(self.y_unc) +
-            '    xmode: {}\n'.format(self.xmode) +
-            '    ymode: {}\n'.format(self.ymode) +
-            '       dx: {}\n'.format(self.dx) +
-            '      roi: {}'.format(self.roi)
+            "bq.Fitter instance\n"
+            + "     name: {}\n".format(self.name)
+            + "    model: {}\n".format(self.model)
+            + "        x: {}\n".format(self.x)
+            + "        y: {}\n".format(self.y)
+            + "    y_unc: {}\n".format(self.y_unc)
+            + "    xmode: {}\n".format(self.xmode)
+            + "    ymode: {}\n".format(self.ymode)
+            + "       dx: {}\n".format(self.dx)
+            + "      roi: {}".format(self.roi)
         )
 
     __repr__ = __str__
@@ -389,25 +394,29 @@ def y(self):
     def y_unc(self):
         if self._y_unc is None:
             warnings.warn(
-                'No y uncertainties (y_unc) provided. The fit will not be ' +
-                'weighted causing in poor results at low counting statistics.',
-                FittingWarning)
+                "No y uncertainties (y_unc) provided. The fit will not be "
+                + "weighted causing in poor results at low counting statistics.",
+                FittingWarning,
+            )
         return self._y_unc
 
     @y_unc.setter
     def y_unc(self, y_unc):
         if y_unc is not None:
             self._y_unc = np.asarray(y_unc, dtype=np.float)
-            assert len(self.x) == len(self._y_unc), \
-                'Fitting x (len {}) does not match y_unc (len {})'.format(
-                    len(self.x), len(self._y_unc))
-            if np.any(self._y_unc <= 0.):
-                min_v = np.min(self._y_unc[self._y_unc > 0.])
+            assert len(self.x) == len(
+                self._y_unc
+            ), "Fitting x (len {}) does not match y_unc (len {})".format(
+                len(self.x), len(self._y_unc)
+            )
+            if np.any(self._y_unc <= 0.0):
+                min_v = np.min(self._y_unc[self._y_unc > 0.0])
                 warnings.warn(
-                    'Negative or zero uncertainty not supported. Changing ' +
-                    'them to {}. If you have Poisson data, '.format(min_v) +
-                    'this should be 1.')
-                self._y_unc[self._y_unc <= 0.] = min_v
+                    "Negative or zero uncertainty not supported. Changing "
+                    + "them to {}. If you have Poisson data, ".format(min_v)
+                    + "this should be 1."
+                )
+                self._y_unc[self._y_unc <= 0.0] = min_v
         else:
             self._y_unc = None
 
@@ -454,8 +463,7 @@ def ymode(self):
     def param_names(self):
         return list(self.params.keys())
 
-    def set_data(self, y, x=None, y_unc=None, dx=None, roi=None,
-                 update_defaults=True):
+    def set_data(self, y, x=None, y_unc=None, dx=None, roi=None, update_defaults=True):
         # Set y data (skip if y is None)
         if y is None:
             return
@@ -465,9 +473,11 @@ def set_data(self, y, x=None, y_unc=None, dx=None, roi=None,
             self._x = np.arange(len(self.y))
         else:
             self._x = np.asarray(x)
-            assert len(self.x) == len(self.y), \
-                'Fitting x (len {}) does not match y (len {})'.format(
-                    len(self.x), len(self.y))
+            assert len(self.x) == len(
+                self.y
+            ), "Fitting x (len {}) does not match y (len {})".format(
+                len(self.x), len(self.y)
+            )
         # Handle y uncertainties
         self.y_unc = y_unc
         # set deltax (bin width)
@@ -492,8 +502,7 @@ def set_roi(self, low, high, update_defaults=True):
         """
 
         self._roi = (float(low), float(high))
-        self._roi_msk = ((self.x >= self.roi[0]) &
-                         (self.x <= self.roi[1]))
+        self._roi_msk = (self.x >= self.roi[0]) & (self.x <= self.roi[1])
         if update_defaults:
             self.guess_param_defaults(update=True)
 
@@ -504,22 +513,23 @@ def _translate_model(self, m):
         if inspect.isclass(m):
             if not issubclass(m, Model):
                 raise FittingError(
-                    'Input model is not a subclass of Model: {}'.format(m))
+                    "Input model is not a subclass of Model: {}".format(m)
+                )
             self._model_cls_cnt[m] = self._model_cls_cnt.get(m, 0) + 1
             return m
         elif isinstance(m, Model):
             cls = m.__class__
             self._model_cls_cnt[cls] = self._model_cls_cnt.get(cls, 0) + 1
             return m
-        elif isstring(m):
+        elif isinstance(m, str):
             cls = MODEL_STR_TO_CLS.get(m.lower(), None)
             if cls is not None:
                 self._model_cls_cnt[cls] = self._model_cls_cnt.get(cls, 0) + 1
                 return cls
-        raise FittingError('Unknown model type: {}'.format(m))
+        raise FittingError("Unknown model type: {}".format(m))
 
     def _make_model(self, model):
-        if isstring(model) or isinstance(model, Model):
+        if isinstance(model, str) or isinstance(model, Model):
             model = [model]
         # Convert the model(s) to a list of Model classes / Model instancess
         self._model_cls_cnt = {}
@@ -527,17 +537,17 @@ def _make_model(self, model):
         # Build complete model with appropriate prefixes
         model_prefixes = set()
         models = []
-        name = ''
+        name = ""
         for m in model_translated:
             if inspect.isclass(m):
                 prefix_base = m.__name__.lower()
-                if prefix_base.endswith('model'):
+                if prefix_base.endswith("model"):
                     prefix_base = prefix_base[:-5]
                 if self._model_cls_cnt[m] == 1:
-                    prefix = '{}_'.format(prefix_base)
+                    prefix = "{}_".format(prefix_base)
                 else:
                     for i in range(self._model_cls_cnt[m]):
-                        prefix = '{}{}_'.format(prefix_base, i)
+                        prefix = "{}{}_".format(prefix_base, i)
                         if prefix not in model_prefixes:
                             break
                 m_instance = m(prefix=prefix)
@@ -545,9 +555,9 @@ def _make_model(self, model):
                 m_instance = m
             if m_instance.prefix in model_prefixes:
                 raise FittingError(
-                    'A model prefix is not unique: ' +
-                    '{} '.format(m_instance.prefix) +
-                    'All models: {}'.format(model_translated)
+                    "A model prefix is not unique: "
+                    + "{} ".format(m_instance.prefix)
+                    + "All models: {}".format(model_translated)
                 )
             model_prefixes.add(m_instance.prefix)
             models.append(m_instance)
@@ -573,7 +583,7 @@ def guess_param_defaults(self, update=False, **kwargs):
                     self.set_param(*dp)
         return defaults
 
-    def fit(self, backend='lmfit'):
+    def fit(self, backend="lmfit"):
         """Perform the weighted fit to data.
 
         Parameters
@@ -589,31 +599,33 @@ def fit(self, backend='lmfit'):
             If self.y is None.
         """
 
-        assert self.y is not None, \
-            'No data initialized, did you call set_data?'
+        assert self.y is not None, "No data initialized, did you call set_data?"
         self.result = None
         y_roi_norm = self.y_roi
         if self.dx is not None:
             y_roi_norm = self.y_roi / self.dx_roi
-        if backend.lower().strip() == 'lmfit':
+        if backend.lower().strip() == "lmfit":
             # Perform the fit, weighted by 1/uncertainties.
             weights = self.y_unc_roi ** -1.0
             self.result = self.model.fit(
                 y_roi_norm, self.params, x=self.x_roi, weights=weights
             )
-        elif backend.lower().strip() == 'lmfit-pml':
+        elif backend.lower().strip() == "lmfit-pml":
             self._set_likelihood_residual()
             # Perform the fit. PML automatically applies 1/sqrt(y) weights, so
             # additional weights here just convert back to counts.
             self.result = self.model.fit(
-                self.y_roi, self.params, #self.result.params,
-                x=self.x_roi, weights=self.dx_roi,
-                fit_kws={'reduce_fcn': lambda r: np.sum(r)},
-                method='Nelder-Mead', calc_covar=False
+                self.y_roi,
+                self.params,  # self.result.params,
+                x=self.x_roi,
+                weights=self.dx_roi,
+                fit_kws={"reduce_fcn": lambda r: np.sum(r)},
+                method="Nelder-Mead",
+                calc_covar=False,
             )  # no, bounds, default would be L-BFGS-B'
             # TODO: Calculate errors breaks minimization right now
         else:
-            raise FittingError('Unknown fitting backend: {}'.format(backend))
+            raise FittingError("Unknown fitting backend: {}".format(backend))
 
     def _set_likelihood_residual(self):
         def _likelihood_residual(self, params, data, weights, **kwargs):
@@ -621,12 +633,14 @@ def _likelihood_residual(self, params, data, weights, **kwargs):
             model = self.eval(params, **kwargs)
             if weights is not None:
                 model *= weights
-            if self.nan_policy == 'raise' and not np.all(np.isfinite(model)):
-                msg = ('The model function generated NaN values and the fit '
-                       'aborted! Please check your model function and/or set '
-                       'boundaries on parameters where applicable. In cases '
-                       'like this, using "nan_policy=\'omit\'" will probably '
-                       'not work.')
+            if self.nan_policy == "raise" and not np.all(np.isfinite(model)):
+                msg = (
+                    "The model function generated NaN values and the fit "
+                    "aborted! Please check your model function and/or set "
+                    "boundaries on parameters where applicable. In cases "
+                    "like this, using \"nan_policy='omit'\" will probably "
+                    "not work."
+                )
                 raise ValueError(msg)
             mask = model <= 0  # This should not be necessary
             diff = model - scipy.special.xlogy(data, model)
@@ -635,6 +649,7 @@ def _likelihood_residual(self, params, data, weights, **kwargs):
                 # data/model are complex
                 diff = diff.ravel().view(np.float)
             return np.asarray(diff).ravel()  # for compatibility with pandas.Series
+
         # This overwrites the  model residual method, is an ugly hack to make
         # poisson fitting possible. This is not undone for now.
         self.model._residual = _likelihood_residual.__get__(self.model, Model)
@@ -653,7 +668,7 @@ def param_val(self, param):
         elif param in self.fit.best_values:
             return self.result.best_values[param]
         else:
-            raise FittingError('Unknown param: {}', param)
+            raise FittingError("Unknown param: {}", param)
 
     def param_unc(self, param):
         """
@@ -667,7 +682,7 @@ def param_unc(self, param):
             # This is the case for the `erf_form` key
             return np.nan
         else:
-            raise FittingError('Unknown param: {}', param)
+            raise FittingError("Unknown param: {}", param)
 
     def param_dataframe(self, sort_by_model=False):
         """
@@ -675,19 +690,20 @@ def param_dataframe(self, sort_by_model=False):
         """
         if self.result is None:
             return None
-        df = pd.DataFrame(columns=['val', 'unc'], dtype=np.float)
+        df = pd.DataFrame(columns=["val", "unc"], dtype=np.float)
         for k in self.param_names:
-            df.loc[k, 'val'] = self.param_val(k)
-            df.loc[k, 'unc'] = self.param_unc(k)
+            df.loc[k, "val"] = self.param_val(k)
+            df.loc[k, "unc"] = self.param_unc(k)
         if sort_by_model:
             df.set_index(
                 pd.MultiIndex.from_tuples(
-                    [tuple(p.split('_')) for p in df.index],
-                    names=['model', 'param']),
-                inplace=True)
+                    [tuple(p.split("_")) for p in df.index], names=["model", "param"]
+                ),
+                inplace=True,
+            )
         return df
 
-    def compute_residuals(self, residual_type='abs'):
+    def compute_residuals(self, residual_type="abs"):
         """Compute residuals between the data and the fit.
 
         Parameters
@@ -707,19 +723,17 @@ def compute_residuals(self, residual_type='abs'):
         y_eval = self.eval(self.x_roi, **self.result.best_values) * dx_roi
         y_res = self.y_roi - y_eval
 
-        if residual_type == 'rel':
+        if residual_type == "rel":
             # Residuals relative to the model evaluation
             return y_res / np.abs(y_eval)
-        elif residual_type == 'sigma':
+        elif residual_type == "sigma":
             # Residuals relative to the data uncertainty
             return y_res / self.y_unc_roi
-        elif residual_type == 'abs':
+        elif residual_type == "abs":
             # Absolute residuals
             return y_res
         else:
-            raise ValueError(
-                'Unknown residuals type: {0:s}'.format(residual_type)
-            )
+            raise ValueError("Unknown residuals type: {0:s}".format(residual_type))
 
     def plot(self, npts=1000, **kwargs):
         """Plot the fit result on the current axis.
@@ -741,8 +755,15 @@ def plot(self, npts=1000, **kwargs):
         y = self.eval(x_plot, **self.result.best_values)
         plt.plot(x_plot, y, **kwargs)
 
-    def custom_plot(self, title=None, savefname=None, title_fontsize=24,
-                    title_fontweight='bold', residual_type='abs', **kwargs):
+    def custom_plot(
+        self,
+        title=None,
+        savefname=None,
+        title_fontsize=24,
+        title_fontweight="bold",
+        residual_type="abs",
+        **kwargs,
+    ):
         """Three-panel figure showing fit results.
 
         Top-left panel shows the data and the fit. Bottom-left shows the fit
@@ -780,16 +801,18 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
         if dx_roi is None:
             dx_roi = np.ones_like(self.x_roi)
         gs = GridSpec(2, 2, height_ratios=(4, 1))
-        gs.update(left=0.05, right=0.99, wspace=0.03, top=0.94, bottom=0.06,
-                  hspace=0.06)
+        gs.update(
+            left=0.05, right=0.99, wspace=0.03, top=0.94, bottom=0.06, hspace=0.06
+        )
         fig = plt.figure(figsize=(18, 9))
         fit_ax = fig.add_subplot(gs[0, 0])
         res_ax = fig.add_subplot(gs[1, 0], sharex=fit_ax)
         txt_ax = fig.add_subplot(gs[:, 1])
         # Set fig title
         if title is not None:
-            fig.suptitle(str(title), fontweight=title_fontweight,
-                         fontsize=title_fontsize)
+            fig.suptitle(
+                str(title), fontweight=title_fontweight, fontsize=title_fontsize
+            )
 
         # ---------------------------------------
         # Fit plot (keep track of min/max in roi)
@@ -797,29 +820,37 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
         # Smooth roi x values
         x_plot = np.linspace(self.x_roi[0], self.x_roi[-1], 1000)
         # All data (not only roi)
-        fit_ax.errorbar(self.x, self.y/dx, yerr=self.y_unc,
-                        c='k', fmt='o', markersize=5, alpha=0.1, label='data')
+        fit_ax.errorbar(
+            self.x,
+            self.y / dx,
+            yerr=self.y_unc,
+            c="k",
+            fmt="o",
+            markersize=5,
+            alpha=0.1,
+            label="data",
+        )
         # Init fit
         y = self.eval(x_plot, **self.result.init_values)
         ymin, ymax = min(y.min(), ymin), max(y.max(), ymax)
-        fit_ax.plot(x_plot, y, 'k--', label='init')
+        fit_ax.plot(x_plot, y, "k--", label="init")
         # Best fit
         y = self.eval(x_plot, **self.result.best_values)
         ymin, ymax = min(y.min(), ymin), max(y.max(), ymax)
-        fit_ax.plot(x_plot, y, color='#e31a1c', label='best fit', zorder=10)
+        fit_ax.plot(x_plot, y, color="#e31a1c", label="best fit", zorder=10)
         if self.result.success:
             yunc = self.result.eval_uncertainty(x=x_plot, sigma=1)
             fit_ax.fill_between(
                 x_plot,
-                y-yunc,
-                y+yunc,
-                label=r'$\pm 1\, \sigma$',
-                color='#e31a1c',
+                y - yunc,
+                y + yunc,
+                label=r"$\pm 1\, \sigma$",
+                color="#e31a1c",
                 alpha=0.2,
-                zorder=9
+                zorder=9,
             )
         # Components (currently will work for <=3 component)
-        colors = ['#1f78b4', '#33a02c', '#6a3d9a']
+        colors = ["#1f78b4", "#33a02c", "#6a3d9a"]
         for i, m in enumerate(self.result.model.components):
             y = m.eval(x=x_plot, **self.result.best_values)
             if isinstance(y, float):
@@ -827,21 +858,27 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
             ymin, ymax = min(y.min(), ymin), max(y.max(), ymax)
             fit_ax.plot(x_plot, y, label=m.prefix, color=colors[i])
         # Plot Peak center and FWHM
-        peak_centers = [self.param_val(p) for p in self.param_names if
-                        (p.startswith('gauss') and p.endswith('mu'))]
-        peak_fwhms = [self.param_val(p) * FWHM_SIG_RATIO
-                      for p in self.param_names if
-                      (p.startswith('gauss') and p.endswith('sigma'))]
+        peak_centers = [
+            self.param_val(p)
+            for p in self.param_names
+            if (p.startswith("gauss") and p.endswith("mu"))
+        ]
+        peak_fwhms = [
+            self.param_val(p) * FWHM_SIG_RATIO
+            for p in self.param_names
+            if (p.startswith("gauss") and p.endswith("sigma"))
+        ]
         for i, (c, f) in enumerate(zip(peak_centers, peak_fwhms)):
             if i == 0:
-                label = 'Centroid and FWHM'
+                label = "Centroid and FWHM"
             else:
                 label = None
-            fit_ax.axvline(c, color='#ff7f00')
-            fit_ax.axvspan(c - f / 2.0, c + f / 2.0, color='#ff7f00',
-                           alpha=0.2, label=label)
+            fit_ax.axvline(c, color="#ff7f00")
+            fit_ax.axvspan(
+                c - f / 2.0, c + f / 2.0, color="#ff7f00", alpha=0.2, label=label
+            )
         # Misc
-        fit_ax.legend(loc='upper right')
+        fit_ax.legend(loc="upper right")
         # Set viewing window to only include the roi (not entire spectrum)
         xpad = (self.x_roi[-1] - self.x_roi[0]) * 0.05
         ypad = (ymax - ymin) * 0.05
@@ -853,27 +890,25 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
         # Residuals
         # ---------
         y_eval = self.eval(self.x_roi, **self.result.best_values) * dx_roi
-        res_kwargs = dict(fmt='o', color='k', markersize=5, label='residuals')
+        res_kwargs = dict(fmt="o", color="k", markersize=5, label="residuals")
 
         # Y-values of the residual plot, depending on residual_type
         y_plot = self.compute_residuals(residual_type)
 
         # Error bars and ylabel of the residual plot
-        if residual_type == 'rel':
+        if residual_type == "rel":
             yerr_plot = self.y_unc_roi / np.abs(y_eval)
-            ylabel = 'Relative residuals'
-        elif residual_type == 'sigma':
+            ylabel = "Relative residuals"
+        elif residual_type == "sigma":
             yerr_plot = np.zeros_like(y_plot)
-            ylabel = r'Residuals $(\sigma)$'
-        elif residual_type == 'abs':
+            ylabel = r"Residuals $(\sigma)$"
+        elif residual_type == "abs":
             yerr_plot = self.y_unc_roi
-            ylabel = 'Residuals'
+            ylabel = "Residuals"
         else:
-            raise ValueError(
-                'Unknown residuals option: {0:s}'.format(residual_type)
-            )
+            raise ValueError("Unknown residuals option: {0:s}".format(residual_type))
         res_ax.errorbar(x=self.x_roi, y=y_plot, yerr=yerr_plot, **res_kwargs)
-        res_ax.axhline(0.0, linestyle='dashed', c='k', linewidth=1.0)
+        res_ax.axhline(0.0, linestyle="dashed", c="k", linewidth=1.0)
         res_ax.set_ylabel(ylabel)
         res_ax.set_xlabel(self.xmode)
 
@@ -882,7 +917,7 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
         # -------------------
         txt_ax.get_xaxis().set_visible(False)
         txt_ax.get_yaxis().set_visible(False)
-        best_fit_values = ''
+        best_fit_values = ""
         op = self.result.params
         for p in self.result.params:
             if op[p].stderr is None:
@@ -892,36 +927,42 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
                 #     "Package numdifftools is required to have "
                 #     "stderr calculated.", FittingWarning)
             else:
-                best_fit_values += '{:15} {: .6e} +/- {:.5e} ({:6.1%})\n'.format(
-                   p, op[p].value, op[p].stderr, abs(op[p].stderr / op[p].value))
-        best_fit_values += '{:15} {: .6e}\n'.format('Chi Squared:',
-                                                    self.result.chisqr)
-        best_fit_values += '{:15} {: .6e}'.format('Reduced Chi Sq:',
-                                                  self.result.redchi)
-        props = dict(boxstyle='round', facecolor='white', edgecolor='black',
-                     alpha=1)
-        props = dict(facecolor='white', edgecolor='none', alpha=0)
-        fp = FontProperties(family='monospace', size=8)
+                best_fit_values += "{:15} {: .6e} +/- {:.5e} ({:6.1%})\n".format(
+                    p, op[p].value, op[p].stderr, abs(op[p].stderr / op[p].value)
+                )
+        best_fit_values += "{:15} {: .6e}\n".format("Chi Squared:", self.result.chisqr)
+        best_fit_values += "{:15} {: .6e}".format("Reduced Chi Sq:", self.result.redchi)
+        props = dict(boxstyle="round", facecolor="white", edgecolor="black", alpha=1)
+        props = dict(facecolor="white", edgecolor="none", alpha=0)
+        fp = FontProperties(family="monospace", size=8)
         # Remove first 2 lines of fit report (long model description)
-        s = '\n'.join(self.result.fit_report().split('\n')[2:])
+        s = "\n".join(self.result.fit_report().split("\n")[2:])
         # Add some more parameter details
-        s += '\n'
+        s += "\n"
         param_df = self.param_dataframe(sort_by_model=True)
-        for model_name, sdf in param_df.groupby(level='model'):
-            s += model_name + '\n'
+        for model_name, sdf in param_df.groupby(level="model"):
+            s += model_name + "\n"
             for (_, param_name), param_data in sdf.iterrows():
-                v = param_data['val']
-                e = param_data['unc']
-                s += '    {:24}: {: .6e} +/- {:.5e} ({:6.1%})\n'.format(
-                    param_name, v, e, np.abs(e / v))
+                v = param_data["val"]
+                e = param_data["unc"]
+                s += "    {:24}: {: .6e} +/- {:.5e} ({:6.1%})\n".format(
+                    param_name, v, e, np.abs(e / v)
+                )
         # Add info about the ROI and units
-        s += 'ROI: [{0:.3f}, {1:.3f}]\n'.format(*self.roi)
-        s += 'X units: {:s}\n'.format(self.xmode if self.xmode else 'None')
-        s += 'Y units: {:s}\n'.format(self.ymode if self.ymode else 'None')
+        s += "ROI: [{0:.3f}, {1:.3f}]\n".format(*self.roi)
+        s += "X units: {:s}\n".format(self.xmode if self.xmode else "None")
+        s += "Y units: {:s}\n".format(self.ymode if self.ymode else "None")
         # Add to empty axis
-        txt_ax.text(x=0.01, y=0.99, s=s, fontproperties=fp,
-                    ha='left', va='top', transform=txt_ax.transAxes,
-                    bbox=props)
+        txt_ax.text(
+            x=0.01,
+            y=0.99,
+            s=s,
+            fontproperties=fp,
+            ha="left",
+            va="top",
+            transform=txt_ax.transAxes,
+            bbox=props,
+        )
         if savefname is not None:
             fig.savefig(savefname)
             plt.close(fig)
@@ -930,63 +971,90 @@ def custom_plot(self, title=None, savefname=None, title_fontsize=24,
 
 
 class FitterGaussGauss(Fitter):
-
     def make_model(self):
-        self.model = \
-            GaussModel(prefix='gauss0_') + \
-            GaussModel(prefix='gauss1_')
+        self.model = GaussModel(prefix="gauss0_") + GaussModel(prefix="gauss1_")
 
     def _guess_param_defaults(self):
         params = []
         for comp in self.model.components:
-            if comp.prefix == 'gauss0_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=self.dx_roi,
-                                     center_ratio=0.33, width_ratio=0.5)
-            elif comp.prefix == 'gauss1_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=self.dx_roi,
-                                     center_ratio=0.66, width_ratio=0.5)
+            if comp.prefix == "gauss0_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=self.dx_roi,
+                    center_ratio=0.33,
+                    width_ratio=0.5,
+                )
+            elif comp.prefix == "gauss1_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=self.dx_roi,
+                    center_ratio=0.66,
+                    width_ratio=0.5,
+                )
         return params
 
 
 class FitterGaussGaussLine(Fitter):
-
     def make_model(self):
-        self.model = \
-            GaussModel(prefix='gauss0_') + \
-            GaussModel(prefix='gauss1_') + \
-            LineModel(prefix='line_')
+        self.model = (
+            GaussModel(prefix="gauss0_")
+            + GaussModel(prefix="gauss1_")
+            + LineModel(prefix="line_")
+        )
 
     def _guess_param_defaults(self):
         params = []
         for comp in self.model.components:
-            if comp.prefix == 'gauss0_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=self.dx_roi,
-                                     center_ratio=0.33, width_ratio=0.5)
-            elif comp.prefix == 'gauss1_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=self.dx_roi,
-                                     center_ratio=0.66, width_ratio=0.5)
+            if comp.prefix == "gauss0_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=self.dx_roi,
+                    center_ratio=0.33,
+                    width_ratio=0.5,
+                )
+            elif comp.prefix == "gauss1_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=self.dx_roi,
+                    center_ratio=0.66,
+                    width_ratio=0.5,
+                )
             else:
                 params += comp.guess(self.y_roi, x=self.x_roi, dx=self.dx_roi)
         return params
 
 
 class FitterGaussGaussExp(Fitter):
-
     def make_model(self):
-        self.model = \
-            GaussModel(prefix='gauss0_') + \
-            GaussModel(prefix='gauss1_') + \
-            ExpModel(prefix='exp_')
+        self.model = (
+            GaussModel(prefix="gauss0_")
+            + GaussModel(prefix="gauss1_")
+            + ExpModel(prefix="exp_")
+        )
 
     def _guess_param_defaults(self):
         params = []
         for comp in self.model.components:
-            if comp.prefix == 'gauss0_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=dx_roi,
-                                     center_ratio=0.33, width_ratio=0.5)
-            elif comp.prefix == 'gauss1_':
-                params += comp.guess(self.y_roi, x=self.x_roi, dx=dx_roi,
-                                     center_ratio=0.66, width_ratio=0.5)
+            if comp.prefix == "gauss0_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=dx_roi,
+                    center_ratio=0.33,
+                    width_ratio=0.5,
+                )
+            elif comp.prefix == "gauss1_":
+                params += comp.guess(
+                    self.y_roi,
+                    x=self.x_roi,
+                    dx=dx_roi,
+                    center_ratio=0.66,
+                    width_ratio=0.5,
+                )
             else:
                 params += comp.guess(self.y_roi, x=self.x_roi, dx=dx_roi)
         return params
diff --git a/becquerel/core/peakfinder.py b/becquerel/core/peakfinder.py
index 1987e4a5..2c11f214 100644
--- a/becquerel/core/peakfinder.py
+++ b/becquerel/core/peakfinder.py
@@ -2,7 +2,6 @@
 
 """Spectral peak search using convolutions."""
 
-from __future__ import print_function
 import matplotlib.pyplot as plt
 import numpy as np
 import warnings
@@ -37,14 +36,14 @@ class PeakFilter(object):
 
     """
 
-    def __init__(self, ref_x, ref_fwhm, fwhm_at_0=1.):
+    def __init__(self, ref_x, ref_fwhm, fwhm_at_0=1.0):
         """Initialize with a reference line position and FWHM in x-values."""
         if ref_x <= 0:
-            raise PeakFilterError('Reference x must be positive')
+            raise PeakFilterError("Reference x must be positive")
         if ref_fwhm <= 0:
-            raise PeakFilterError('Reference FWHM must be positive')
+            raise PeakFilterError("Reference FWHM must be positive")
         if fwhm_at_0 < 0:
-            raise PeakFilterError('FWHM at 0 must be non-negative')
+            raise PeakFilterError("FWHM at 0 must be non-negative")
         self.ref_x = float(ref_x)
         self.ref_fwhm = float(ref_fwhm)
         self.fwhm_at_0 = float(fwhm_at_0)
@@ -58,7 +57,7 @@ def fwhm(self, x):
         f0 = self.fwhm_at_0
         f1 = self.ref_fwhm
         x1 = self.ref_x
-        fwhm_sqr = f0**2 + (f1**2 - f0**2) * (x / x1)**2
+        fwhm_sqr = f0 ** 2 + (f1 ** 2 - f0 ** 2) * (x / x1) ** 2
         return np.sqrt(fwhm_sqr)
 
     def kernel(self, x, edges):
@@ -87,13 +86,16 @@ def plot_matrix(self, edges):
         kern_max = max(kern_max, -1 * kern_min)
 
         plt.imshow(
-            kern_mat.T[::-1, :], cmap=plt.get_cmap('bwr'),
-            vmin=kern_min, vmax=kern_max,
-            extent=[n_channels, 0, 0, n_channels])
+            kern_mat.T[::-1, :],
+            cmap=plt.get_cmap("bwr"),
+            vmin=kern_min,
+            vmax=kern_max,
+            extent=[n_channels, 0, 0, n_channels],
+        )
         plt.colorbar()
-        plt.xlabel('Input x')
-        plt.ylabel('Output x')
-        plt.gca().set_aspect('equal')
+        plt.xlabel("Input x")
+        plt.ylabel("Output x")
+        plt.gca().set_aspect("equal")
 
     def convolve(self, edges, data):
         """Convolve this kernel with the data."""
@@ -103,7 +105,7 @@ def convolve(self, edges, data):
         peak_plus_bkg = np.dot(kern_mat_pos, data)
         bkg = np.dot(kern_mat_neg, data)
         signal = np.dot(kern_mat, data)
-        noise = np.sqrt(np.dot(kern_mat**2, data))
+        noise = np.sqrt(np.dot(kern_mat ** 2, data))
         snr = np.zeros_like(signal)
         snr[noise > 0] = signal[noise > 0] / noise[noise > 0]
         return peak_plus_bkg, bkg, signal, noise, snr
@@ -112,12 +114,12 @@ def convolve(self, edges, data):
 def _gaussian0(x, mean, sigma):
     """Gaussian function."""
     z = (x - mean) / sigma
-    return np.exp(-z**2 / 2.)
+    return np.exp(-(z ** 2) / 2.0)
 
 
 def _gaussian1(x, mean, sigma):
     """First derivative of a gaussian."""
-    z = (x - mean)
+    z = x - mean
     return -1 * z * _gaussian0(x, mean, sigma)
 
 
@@ -157,8 +159,7 @@ class PeakFinder(object):
     def __init__(self, spectrum, kernel, min_sep=5, fwhm_tol=(0.5, 1.5)):
         """Initialize with a spectrum and kernel."""
         if min_sep <= 0:
-            raise PeakFinderError(
-                'Minimum x separation must be positive')
+            raise PeakFinderError("Minimum x separation must be positive")
         self.min_sep = min_sep
         self.fwhm_tol = tuple(fwhm_tol)
         self.spectrum = None
@@ -177,8 +178,11 @@ def __init__(self, spectrum, kernel, min_sep=5, fwhm_tol=(0.5, 1.5)):
 
     @property
     def channels(self):
-        warnings.warn('channels is deprecated and will be removed in a future '
-                      'release. Use centroids instead.', DeprecationWarning)
+        warnings.warn(
+            "channels is deprecated and will be removed in a future "
+            "release. Use centroids instead.",
+            DeprecationWarning,
+        )
         return self.centroids
 
     def reset(self):
@@ -193,8 +197,10 @@ def sort_by(self, arr):
         """Sort peaks by the provided array."""
         if len(arr) != len(self.centroids):
             raise PeakFinderError(
-                'Sorting array has length {} but must have length {}'.format(
-                    len(arr), len(self.centroids)))
+                "Sorting array has length {} but must have length {}".format(
+                    len(arr), len(self.centroids)
+                )
+            )
         self.centroids = np.array(self.centroids)
         self.snrs = np.array(self.snrs)
         self.fwhms = np.array(self.fwhms)
@@ -211,10 +217,12 @@ def calculate(self, spectrum, kernel):
         """Calculate the convolution of the spectrum with the kernel."""
         if not isinstance(spectrum, Spectrum):
             raise PeakFinderError(
-                'Argument must be a Spectrum, not {}'.format(type(spectrum)))
+                "Argument must be a Spectrum, not {}".format(type(spectrum))
+            )
         if not isinstance(kernel, PeakFilter):
             raise PeakFinderError(
-                'Argument must be a PeakFilter, not {}'.format(type(kernel)))
+                "Argument must be a PeakFilter, not {}".format(type(kernel))
+            )
         self.spectrum = spectrum
         self.kernel = kernel
         self.snr = np.zeros(len(self.spectrum))
@@ -222,8 +230,9 @@ def calculate(self, spectrum, kernel):
         bin_edges = self.spectrum.bin_edges_raw
 
         # calculate the convolution
-        peak_plus_bkg, bkg, signal, noise, snr = \
-            self.kernel.convolve(bin_edges, self.spectrum.counts_vals)
+        peak_plus_bkg, bkg, signal, noise, snr = self.kernel.convolve(
+            bin_edges, self.spectrum.counts_vals
+        )
         self._peak_plus_bkg = peak_plus_bkg
         self._bkg = bkg
         self._signal = signal
@@ -240,8 +249,8 @@ def add_peak(self, xpeak):
 
         if xpeak < xmin or xpeak > xmax:
             raise PeakFinderError(
-                'Peak x {} is outside of range {}-{}'.format(
-                    xpeak, xmin, xmax))
+                "Peak x {} is outside of range {}-{}".format(xpeak, xmin, xmax)
+            )
         is_new_x = True
         for cent in self.centroids:
             if abs(xpeak - cent) <= self.min_sep:
@@ -256,12 +265,13 @@ def add_peak(self, xpeak):
             fwhm0 = self.kernel.fwhm(xpeak)
             bw = self.spectrum.bin_widths_raw[0]
             h = int(max(1, 0.2 * fwhm0 / bw))
-            d2 = (1 * self.snr[xbin - h]
-                  - 2 * self.snr[xbin]
-                  + 1 * self.snr[xbin + h]) / h**2 / bw**2
+            d2 = (
+                (1 * self.snr[xbin - h] - 2 * self.snr[xbin] + 1 * self.snr[xbin + h])
+                / h ** 2
+                / bw ** 2
+            )
             if d2 >= 0:
-                raise PeakFinderError(
-                    'Second derivative must be negative at peak')
+                raise PeakFinderError("Second derivative must be negative at peak")
             d2 *= -1
             fwhm = 2 * np.sqrt(self.snr[xbin] / d2)
             self.fwhms.append(fwhm)
@@ -275,27 +285,25 @@ def add_peak(self, xpeak):
         # sort the peaks by centroid
         self.sort_by(self.centroids)
 
-    def plot(self, facecolor='red', linecolor='red', alpha=0.5, peaks=True):
+    def plot(self, facecolor="red", linecolor="red", alpha=0.5, peaks=True):
         """Plot the peak signal-to-noise ratios calculated using the kernel."""
         bin_edges = self.spectrum.bin_edges_raw
 
         if facecolor is not None:
-            plt.fill_between(
-                bin_edges[:-1], self.snr, 0,
-                color=facecolor, alpha=alpha)
+            plt.fill_between(bin_edges[:-1], self.snr, 0, color=facecolor, alpha=alpha)
         if linecolor is not None:
-            plt.plot(bin_edges[:-1], self.snr, '-', color=linecolor)
+            plt.plot(bin_edges[:-1], self.snr, "-", color=linecolor)
         if peaks:
             for cent, snr, fwhm in zip(self.centroids, self.snrs, self.fwhms):
-                plt.plot([cent] * 2, [0, snr], 'b-', lw=1.5)
-                plt.plot(cent, snr, 'bo')
+                plt.plot([cent] * 2, [0, snr], "b-", lw=1.5)
+                plt.plot(cent, snr, "bo")
                 plt.plot(
-                    [cent - fwhm / 2, cent + fwhm / 2], [snr / 2] * 2,
-                    'b-', lw=1.5)
+                    [cent - fwhm / 2, cent + fwhm / 2], [snr / 2] * 2, "b-", lw=1.5
+                )
         plt.xlim(0, bin_edges.max())
         plt.ylim(0)
-        plt.xlabel('x')
-        plt.ylabel('SNR')
+        plt.xlabel("x")
+        plt.ylabel("SNR")
 
     def find_peak(self, xpeak, frac_range=(0.8, 1.2), min_snr=2):
         """Find the highest SNR peak within f0*xpeak and f1*xpeak."""
@@ -306,27 +314,33 @@ def find_peak(self, xpeak, frac_range=(0.8, 1.2), min_snr=2):
 
         if xpeak < xmin or xpeak > xmax:
             raise PeakFinderError(
-                'Guess xpeak {} is outside of range {}-{}'.format(
-                    xpeak, xmin, xmax))
-        if frac_range[0] < 0 or frac_range[0] > 1 or frac_range[1] < 1 or \
-                frac_range[0] > frac_range[1]:
+                "Guess xpeak {} is outside of range {}-{}".format(xpeak, xmin, xmax)
+            )
+        if (
+            frac_range[0] < 0
+            or frac_range[0] > 1
+            or frac_range[1] < 1
+            or frac_range[0] > frac_range[1]
+        ):
             raise PeakFinderError(
-                'Fractional range {}-{} is invalid'.format(*frac_range))
+                "Fractional range {}-{} is invalid".format(*frac_range)
+            )
         if min_snr < 0:
-            raise PeakFinderError(
-                'Minimum SNR {:.3f} must be > 0'.format(min_snr))
+            raise PeakFinderError("Minimum SNR {:.3f} must be > 0".format(min_snr))
         if self.snr.max() < min_snr:
             raise PeakFinderError(
-                'SNR threshold is {:.3f} but maximum SNR is {:.3f}'.format(
-                    min_snr, self.snr.max()))
+                "SNR threshold is {:.3f} but maximum SNR is {:.3f}".format(
+                    min_snr, self.snr.max()
+                )
+            )
         x0 = frac_range[0] * xpeak
         x1 = frac_range[1] * xpeak
         x_range = (x0 <= bin_edges[:-1]) & (bin_edges[:-1] <= x1)
         peak_snr = self.snr[x_range].max()
         if peak_snr < min_snr:
             raise PeakFinderError(
-                'No peak found in range {}-{} with SNR > {}'.format(
-                    x0, x1, min_snr))
+                "No peak found in range {}-{} with SNR > {}".format(x0, x1, min_snr)
+            )
 
         peak_index = np.where((self.snr == peak_snr) & x_range)[0][0]
         peak_x = bin_centers[peak_index]
@@ -342,24 +356,25 @@ def find_peaks(self, xmin=None, xmax=None, min_snr=2, max_num=40):
             xmin = bin_edges.min()
         if xmax is None:
             xmax = bin_edges.max()
-        if xmin < bin_edges.min() or \
-                xmin > bin_edges.max() or \
-                xmax > bin_edges.max() or \
-                xmax < bin_edges.min() or \
-                xmin > xmax:
-            raise PeakFinderError(
-                'x-axis range {}-{} is invalid'.format(xmin, xmax))
+        if (
+            xmin < bin_edges.min()
+            or xmin > bin_edges.max()
+            or xmax > bin_edges.max()
+            or xmax < bin_edges.min()
+            or xmin > xmax
+        ):
+            raise PeakFinderError("x-axis range {}-{} is invalid".format(xmin, xmax))
         if min_snr < 0:
-            raise PeakFinderError(
-                'Minimum SNR {:.3f} must be > 0'.format(min_snr))
+            raise PeakFinderError("Minimum SNR {:.3f} must be > 0".format(min_snr))
         if self.snr.max() < min_snr:
             raise PeakFinderError(
-                'SNR threshold is {:.3f} but maximum SNR is {:.3f}'.format(
-                    min_snr, self.snr.max()))
+                "SNR threshold is {:.3f} but maximum SNR is {:.3f}".format(
+                    min_snr, self.snr.max()
+                )
+            )
         max_num = int(max_num)
         if max_num < 1:
-            raise PeakFinderError(
-                'Must keep at least 1 peak, not {}'.format(max_num))
+            raise PeakFinderError("Must keep at least 1 peak, not {}".format(max_num))
         # calculate the first derivative and second derivatives of the SNR
         d1 = (self.snr[2:] - self.snr[:-2]) / 2
         d1 = np.append(0, d1)
@@ -372,9 +387,9 @@ def find_peaks(self, xmin=None, xmax=None, min_snr=2, max_num=40):
         peak = np.append(False, peak)
         peak = np.append(peak, False)
         # select peaks using SNR and centroid criteria
-        peak &= (min_snr <= self.snr)
-        peak &= (xmin <= bin_edges[:-1])
-        peak &= (bin_edges[:-1] <= xmax)
+        peak &= min_snr <= self.snr
+        peak &= xmin <= bin_edges[:-1]
+        peak &= bin_edges[:-1] <= xmax
         for x in bin_centers[peak]:
             self.add_peak(x)
         # reduce number of centroids to a maximum number max_n of highest SNR
diff --git a/becquerel/core/plotting.py b/becquerel/core/plotting.py
index 59a51c6f..3200def7 100644
--- a/becquerel/core/plotting.py
+++ b/becquerel/core/plotting.py
@@ -1,6 +1,5 @@
 """Tools for plotting spectra."""
 
-from __future__ import print_function
 import matplotlib.pyplot as plt
 import numpy as np
 from uncertainties import unumpy
@@ -55,33 +54,33 @@ def __init__(self, spec, *fmt, **kwargs):
         xlabel = None
         ylabel = None
 
-        if 'xmode' in kwargs:
-            xmode = kwargs.pop('xmode')
-        if 'ymode' in kwargs:
-            ymode = kwargs.pop('ymode')
-        if 'xlim' in kwargs:
-            xlim = kwargs.pop('xlim')
-        if 'ylim' in kwargs:
-            ylim = kwargs.pop('ylim')
-        if 'ax' in kwargs:
-            ax = kwargs.pop('ax')
-        if 'yscale' in kwargs:
-            yscale = kwargs.pop('yscale')
-        if 'title' in kwargs:
-            title = kwargs.pop('title')
-        if 'xlabel' in kwargs:
-            xlabel = kwargs.pop('xlabel')
-        if 'ylabel' in kwargs:
-            ylabel = kwargs.pop('ylabel')
+        if "xmode" in kwargs:
+            xmode = kwargs.pop("xmode")
+        if "ymode" in kwargs:
+            ymode = kwargs.pop("ymode")
+        if "xlim" in kwargs:
+            xlim = kwargs.pop("xlim")
+        if "ylim" in kwargs:
+            ylim = kwargs.pop("ylim")
+        if "ax" in kwargs:
+            ax = kwargs.pop("ax")
+        if "yscale" in kwargs:
+            yscale = kwargs.pop("yscale")
+        if "title" in kwargs:
+            title = kwargs.pop("title")
+        if "xlabel" in kwargs:
+            xlabel = kwargs.pop("xlabel")
+        if "ylabel" in kwargs:
+            ylabel = kwargs.pop("ylabel")
 
         self.spec = spec
 
-        if hasattr(fmt, '__len__') and len(fmt) in [0, 1]:
+        if hasattr(fmt, "__len__") and len(fmt) in [0, 1]:
             self.fmt = fmt
         else:
             raise PlottingError("Wrong number of positional arguments")
 
-        if 'linthreshy' in kwargs:
+        if "linthreshy" in kwargs:
             self._linthreshy = kwargs.pop("linthreshy")
 
         self.xlim = xlim
@@ -98,7 +97,6 @@ def __init__(self, spec, *fmt, **kwargs):
         self.xlabel = xlabel
         self.ylabel = ylabel
 
-
     @property
     def xmode(self):
         """
@@ -107,7 +105,6 @@ def xmode(self):
 
         return self._xmode
 
-
     @xmode.setter
     def xmode(self, mode):
         """
@@ -121,29 +118,30 @@ def xmode(self, mode):
         # First, set the _xmode
         if mode is None:
             if self.spec.is_calibrated:
-                self._xmode = 'energy'
+                self._xmode = "energy"
             else:
-                self._xmode = 'channel'
+                self._xmode = "channel"
         else:
-            if mode.lower() in ('kev', 'energy'):
+            if mode.lower() in ("kev", "energy"):
                 if not self.spec.is_calibrated:
-                    raise PlottingError('Spectrum is not calibrated, however'
-                                        ' x axis was requested as energy')
-                self._xmode = 'energy'
-            elif mode.lower() in ('channel', 'channels', 'chn', 'chns'):
-                self._xmode = 'channel'
+                    raise PlottingError(
+                        "Spectrum is not calibrated, however"
+                        " x axis was requested as energy"
+                    )
+                self._xmode = "energy"
+            elif mode.lower() in ("channel", "channels", "chn", "chns"):
+                self._xmode = "channel"
             else:
-                raise PlottingError('Unknown x data mode: {}'.format(mode))
+                raise PlottingError("Unknown x data mode: {}".format(mode))
 
         # Then, set the _xedges and _xlabel based on the _xmode
         xedges, xlabel = self.spec.parse_xmode(self._xmode)
         self._xedges = xedges
-        possible_labels = ['Energy [keV]', 'Channel']
+        possible_labels = ["Energy [keV]", "Channel"]
         if self._xlabel in possible_labels or self._xlabel is None:
             # Only reset _xlabel if it's an old result from parse_xmode or None
             self._xlabel = xlabel
 
-
     @property
     def ymode(self):
         """
@@ -151,7 +149,6 @@ def ymode(self):
         """
         return self._ymode
 
-
     @ymode.setter
     def ymode(self, mode):
         """
@@ -166,29 +163,28 @@ def ymode(self, mode):
         # First, set the _ymode
         if mode is None:
             if self.spec._counts is not None:
-                self._ymode = 'counts'
+                self._ymode = "counts"
             else:
-                self._ymode = 'cps'
-        elif mode.lower() in ('count', 'counts', 'cnt', 'cnts'):
+                self._ymode = "cps"
+        elif mode.lower() in ("count", "counts", "cnt", "cnts"):
             if self.spec._counts is None:
-                raise PlottingError('Spectrum has counts not defined')
-            self._ymode = 'counts'
-        elif mode.lower() == 'cps':
-            self._ymode = 'cps'
-        elif mode.lower() == 'cpskev':
-            self._ymode = 'cpskev'
+                raise PlottingError("Spectrum has counts not defined")
+            self._ymode = "counts"
+        elif mode.lower() == "cps":
+            self._ymode = "cps"
+        elif mode.lower() == "cpskev":
+            self._ymode = "cpskev"
         else:
-            raise PlottingError('Unknown y data mode: {}'.format(mode))
+            raise PlottingError("Unknown y data mode: {}".format(mode))
 
         # Then, set the _ydata and _ylabel based on the _ymode
         ydata, _, ylabel = self.spec.parse_ymode(self._ymode)
         self._ydata = ydata
-        possible_labels = ['Counts', 'Countrate [1/s]', 'Countrate [1/s/keV]']
+        possible_labels = ["Counts", "Countrate [1/s]", "Countrate [1/s/keV]"]
         if self._ylabel in possible_labels or self._ylabel is None:
             # Only reset _ylabel if it's an old result from parse_ymode or None
             self._ylabel = ylabel
 
-
     @property
     def ax(self):
         """
@@ -200,7 +196,6 @@ def ax(self):
             _, self._ax = plt.subplots()
         return self._ax
 
-
     @ax.setter
     def ax(self, ax):
         """
@@ -219,7 +214,6 @@ def xlabel(self):
         """
         return self._xlabel
 
-
     @xlabel.setter
     def xlabel(self, label):
         """
@@ -229,7 +223,6 @@ def xlabel(self, label):
         if label is not None:
             self._xlabel = label
 
-
     @property
     def ylabel(self):
         """
@@ -238,7 +231,6 @@ def ylabel(self):
 
         return self._ylabel
 
-
     @ylabel.setter
     def ylabel(self, label):
         """
@@ -255,14 +247,13 @@ def yerror(self):
         FIXME: now redundant?
         """
 
-        if self._ymode == 'counts':
+        if self._ymode == "counts":
             return self.spec.counts_uncs
-        elif self._ymode == 'cps':
+        elif self._ymode == "cps":
             return self.spec.cps_uncs
-        elif self._ymode == 'cpskev':
+        elif self._ymode == "cpskev":
             return self.spec.cpskev_uncs
 
-
     def get_corners(self):
         """
         Creates a stepped version of the current spectrum data.
@@ -273,8 +264,8 @@ def get_corners(self):
         """
 
         return self.bin_edges_and_heights_to_steps(
-            self._xedges, unumpy.nominal_values(self._ydata))
-
+            self._xedges, unumpy.nominal_values(self._ydata)
+        )
 
     def _prepare_plot(self, **kwargs):
         """Prepare for the plotting."""
@@ -294,11 +285,10 @@ def _prepare_plot(self, **kwargs):
             self.ax.set_xlim(self.xlim)
         if self._ylim is not None:
             self.ax.set_ylim(self.ylim)
-            if self.yscale == 'symlog' and self._ylim == 'default':
+            if self.yscale == "symlog" and self._ylim == "default":
                 self.ax.set_yscale(self.yscale, linthreshy=self.linthreshy)
         return self.get_corners()
 
-
     def plot(self, *fmt, **kwargs):
         """
         Create actual plot with matplotlib's plot method.
@@ -309,17 +299,16 @@ def plot(self, *fmt, **kwargs):
                   previously defined keywords
         """
 
-        if hasattr(fmt, '__len__') and len(fmt) > 0:
+        if hasattr(fmt, "__len__") and len(fmt) > 0:
             self.fmt = fmt
 
-        if not hasattr(self.fmt, '__len__') or not len(self.fmt) in [0, 1]:
+        if not hasattr(self.fmt, "__len__") or not len(self.fmt) in [0, 1]:
             raise PlottingError("Wrong number of positional argument")
 
         xcorners, ycorners = self._prepare_plot(**kwargs)
         self.ax.plot(xcorners, ycorners, *self.fmt, **self.kwargs)
         return self.ax
 
-
     def fill_between(self, **kwargs):
         """
         Create actual plot with matplotlib's fill_between method.
@@ -333,7 +322,6 @@ def fill_between(self, **kwargs):
         self.ax.fill_between(xcorners, ycorners, **self.kwargs)
         return self.ax
 
-
     def errorbar(self, **kwargs):
         """
         Create errorbars with matplotlib's plot errorbar method.
@@ -344,19 +332,20 @@ def errorbar(self, **kwargs):
         """
 
         self._prepare_plot(**kwargs)
-        xdata = (self._xedges[0:-1]+self._xedges[1:])*0.5
+        xdata = (self._xedges[0:-1] + self._xedges[1:]) * 0.5
 
-        if 'fmt' in self.kwargs:
-            self.fmt = (self.kwargs.pop('fmt'))
+        if "fmt" in self.kwargs:
+            self.fmt = self.kwargs.pop("fmt")
 
-        if hasattr(self.fmt, '__len__') and len(self.fmt) == 0:
-            self.fmt = (',',)
+        if hasattr(self.fmt, "__len__") and len(self.fmt) == 0:
+            self.fmt = (",",)
 
-        if not hasattr(self.fmt, '__len__') or len(self.fmt) != 1:
+        if not hasattr(self.fmt, "__len__") or len(self.fmt) != 1:
             raise PlottingError("Wrong number of argument for fmt")
 
-        self.ax.errorbar(xdata, self._ydata, yerr=self.yerror, fmt=self.fmt[0], **self.kwargs)
-
+        self.ax.errorbar(
+            xdata, self._ydata, yerr=self.yerror, fmt=self.fmt[0], **self.kwargs
+        )
 
     def errorband(self, **kwargs):
         """
@@ -370,17 +359,18 @@ def errorband(self, **kwargs):
         self._prepare_plot(**kwargs)
 
         alpha = 0.5
-        if 'alpha' in self.kwargs:
+        if "alpha" in self.kwargs:
             alpha = self.kwargs.pop("alpha")
 
         xcorners, ycorlow = self.bin_edges_and_heights_to_steps(
-            self._xedges, unumpy.nominal_values(self._ydata-self.yerror))
+            self._xedges, unumpy.nominal_values(self._ydata - self.yerror)
+        )
         _, ycorhig = self.bin_edges_and_heights_to_steps(
-            self._xedges, unumpy.nominal_values(self._ydata+self.yerror))
+            self._xedges, unumpy.nominal_values(self._ydata + self.yerror)
+        )
         self.ax.fill_between(xcorners, ycorlow, ycorhig, alpha=alpha, **self.kwargs)
         return self.ax
 
-
     @staticmethod
     def bin_edges_and_heights_to_steps(bin_edges, heights):
         """A robust alternative to matplotlib's drawstyle='steps-*'"""
@@ -394,7 +384,6 @@ def bin_edges_and_heights_to_steps(bin_edges, heights):
         y[2:-1:2] = heights.astype(float)
         return x, y
 
-
     @staticmethod
     def dynamic_min(data_min, min_delta_y):
         """Get an axes lower limit (for y) based on data value.
@@ -407,7 +396,7 @@ def dynamic_min(data_min, min_delta_y):
         """
 
         if data_min > 0:
-            ceil10 = 10**(np.ceil(np.log10(data_min)))
+            ceil10 = 10 ** (np.ceil(np.log10(data_min)))
             sig_fig = np.floor(10 * data_min / ceil10)
             if sig_fig <= 3:
                 ymin = ceil10 / 10
@@ -417,7 +406,7 @@ def dynamic_min(data_min, min_delta_y):
             ymin = min_delta_y / 10.0
         else:
             # negative
-            floor10 = 10**(np.floor(np.log10(-data_min)))
+            floor10 = 10 ** (np.floor(np.log10(-data_min)))
             sig_fig = np.floor(-data_min / floor10)
             if sig_fig < 3:
                 ymin = -floor10 * 3
@@ -426,7 +415,6 @@ def dynamic_min(data_min, min_delta_y):
 
         return ymin
 
-
     @staticmethod
     def dynamic_max(data_max, yscale):
         """Get an axes upper limit (for y) based on data value.
@@ -438,9 +426,9 @@ def dynamic_max(data_max, yscale):
           data_max: the maximum of the data (could be integers or floats)
         """
 
-        floor10 = 10**(np.floor(np.log10(data_max)))
+        floor10 = 10 ** (np.floor(np.log10(data_max)))
         sig_fig = np.ceil(data_max / floor10)
-        if yscale == 'linear':
+        if yscale == "linear":
             sig_fig = np.floor(data_max / floor10)
             ymax = floor10 * (sig_fig + 1)
         elif sig_fig < 3:
@@ -450,12 +438,11 @@ def dynamic_max(data_max, yscale):
 
         return np.maximum(ymax, 0)
 
-
     @property
     def xlim(self):
         """Returns the xlim, requires xedges."""
 
-        if self._xlim is None or self._xlim == 'default':
+        if self._xlim is None or self._xlim == "default":
             return np.min(self._xedges), np.max(self._xedges)
 
         return self._xlim
@@ -464,17 +451,19 @@ def xlim(self):
     def xlim(self, limits):
         """Sets xlim."""
 
-        if limits is not None and limits != 'default' and \
-        (not hasattr(limits, '__len__') or len(limits) != 2):
-            raise PlottingError('xlim should be length 2: {}'.format(limits))
+        if (
+            limits is not None
+            and limits != "default"
+            and (not hasattr(limits, "__len__") or len(limits) != 2)
+        ):
+            raise PlottingError("xlim should be length 2: {}".format(limits))
         self._xlim = limits
 
-
     @property
     def ylim(self):
         """Returns ylim, requires yscale, ydata."""
 
-        if self._ylim is None or self._ylim == 'default':
+        if self._ylim is None or self._ylim == "default":
             yscale = self.yscale
             if yscale is None:
                 yscale = self.ax.get_yscale()
@@ -484,12 +473,13 @@ def ylim(self):
             min_delta_y = np.min(delta_y[delta_y > 0])
 
             data_min = np.min(self._ydata)
-            if yscale == 'linear':
+            if yscale == "linear":
                 ymin = 0
-            elif yscale == 'log' and data_min < 0:
-                raise PlottingError('Cannot plot negative values on a log ' +
-                                    'scale; use symlog scale')
-            elif yscale == 'symlog' and data_min >= 0:
+            elif yscale == "log" and data_min < 0:
+                raise PlottingError(
+                    "Cannot plot negative values on a log scale; use symlog scale"
+                )
+            elif yscale == "symlog" and data_min >= 0:
                 ymin = 0
             else:
                 ymin = self.dynamic_min(data_min, min_delta_y)
@@ -500,17 +490,18 @@ def ylim(self):
 
         return self._ylim
 
-
     @ylim.setter
     def ylim(self, limits):
         """Sets ylim."""
 
-        if limits is not None and limits != 'default' and \
-        (not hasattr(limits, '__len__') or len(limits) != 2):
-            raise PlottingError('ylim should be length 2: {}'.format(limits))
+        if (
+            limits is not None
+            and limits != "default"
+            and (not hasattr(limits, "__len__") or len(limits) != 2)
+        ):
+            raise PlottingError("ylim should be length 2: {}".format(limits))
         self._ylim = limits
 
-
     @property
     def linthreshy(self):
         """Returns linthreshy, requires ydata."""
diff --git a/becquerel/core/rebin.py b/becquerel/core/rebin.py
index e0116475..17c1e585 100644
--- a/becquerel/core/rebin.py
+++ b/becquerel/core/rebin.py
@@ -16,7 +16,7 @@ class RebinWarning(UserWarning):
     pass
 
 
-def _check_monotonic_increasing(arr, arr_name='array'):
+def _check_monotonic_increasing(arr, arr_name="array"):
     """Check that a numpy array is monotonically increasing.
 
     Args:
@@ -30,8 +30,7 @@ def _check_monotonic_increasing(arr, arr_name='array'):
     # neighboring elements are equal
     tmp = np.diff(arr)
     if not ((tmp > 0) | np.isclose(tmp, 0)).all():
-        raise RebinError('{} is not monotonically increasing: {}'.format(
-            arr_name, arr))
+        raise RebinError("{} is not monotonically increasing: {}".format(arr_name, arr))
 
 
 def _check_partial_overlap(in_edges, out_edges):
@@ -55,14 +54,16 @@ def _check_partial_overlap(in_edges, out_edges):
     """
     if (in_edges[..., 0] > out_edges[..., 0]).any():
         warnings.warn(
-            'The first input edge is larger than the first output edge, ' +
-            'zeros will padded on the left side of the new spectrum',
-            RebinWarning)
+            "The first input edge is larger than the first output edge, "
+            "zeros will padded on the left side of the new spectrum",
+            RebinWarning,
+        )
     if (in_edges[..., -1] < out_edges[..., -1]).any():
         warnings.warn(
-            'The last input edge is smaller than the last output edge, ' +
-            'zeros will padded on the right side of the new spectrum',
-            RebinWarning)
+            "The last input edge is smaller than the last output edge, "
+            "zeros will padded on the right side of the new spectrum",
+            RebinWarning,
+        )
 
 
 def _check_any_overlap(in_edges, out_edges):
@@ -84,9 +85,9 @@ def _check_any_overlap(in_edges, out_edges):
             new:   └┴┴┴┘
     """
     if (in_edges[..., -1] <= out_edges[..., 0]).any():
-        raise RebinError('Input edges are all smaller than output edges')
+        raise RebinError("Input edges are all smaller than output edges")
     if (in_edges[..., 0] >= out_edges[..., -1]).any():
-        raise RebinError('Input edges are all larger than output edges')
+        raise RebinError("Input edges are all larger than output edges")
 
 
 @nb.vectorize([nb.f8(nb.f8, nb.f8, nb.f8, nb.f8)], nopython=True)
@@ -107,7 +108,7 @@ def _linear_offset(slope, cts, low, high):
     if np.abs(slope) < 1e-6:
         offset = cts / (high - low)
     else:
-        offset = (cts - slope / 2. * (high**2 - low**2)) / (high - low)
+        offset = (cts - slope / 2.0 * (high ** 2 - low ** 2)) / (high - low)
     return offset
 
 
@@ -124,7 +125,7 @@ def _slope_integral(x, m, b):
       The indefinite integral of y = mx + b, with an x value substituted in.
       (m x^2 / 2 + b x)
     """
-    return m * x**2 / 2 + b * x
+    return m * x ** 2 / 2 + b * x
 
 
 @nb.vectorize([nb.f8(nb.f8, nb.f8, nb.f8, nb.f8)], nopython=True)
@@ -148,10 +149,12 @@ def _counts(m, b, x_low, x_high):
     return _slope_integral(x_high, m, b) - _slope_integral(x_low, m, b)
 
 
-@nb.guvectorize([(nb.f8[:], nb.f8[:], nb.f8[:], nb.f8[:], nb.f8[:])],
-                "(n),(N),(m),(n)->(m)")
+@nb.guvectorize(
+    [(nb.f8[:], nb.f8[:], nb.f8[:], nb.f8[:], nb.f8[:])], "(n),(N),(m),(n)->(m)"
+)
 def _rebin_interpolation(
-        in_spectrum, in_edges, out_edges_no_rightmost, slopes, out_spectrum):
+    in_spectrum, in_edges, out_edges_no_rightmost, slopes, out_spectrum
+):
     """Rebins a spectrum using linear interpolation.
 
     Keeps a running counter of two loop indices: in_idx & out_idx
@@ -220,10 +223,8 @@ def _rebin_interpolation(
             out_spectrum[out_idx] += _counts(slope, offset, low, high)
 
 
-@nb.guvectorize([(nb.i8[:], nb.f8[:], nb.f8[:], nb.i8[:])],
-                "(n),(N),(m)->(m)")
-def _rebin_listmode(
-        in_spectrum, in_edges, out_edges_no_rightmost, out_spectrum):
+@nb.guvectorize([(nb.i8[:], nb.f8[:], nb.f8[:], nb.i8[:])], "(n),(N),(m)->(m)")
+def _rebin_listmode(in_spectrum, in_edges, out_edges_no_rightmost, out_spectrum):
     """Stochastic rebinning method: spectrum-histogram to listmode then back.
 
     rightmost edge in the parameter out_edges chopped off, in order for
@@ -251,7 +252,8 @@ def _rebin_listmode(
     # knock out leftmost bin edge too, because we put all overflows into
     # first and last bins anyways
     out_edges = np.concatenate(
-        (np.array([-np.inf]), out_edges_no_rightmost[1:], np.array([np.inf])))
+        (np.array([-np.inf]), out_edges_no_rightmost[1:], np.array([np.inf]))
+    )
     energies = np.zeros(np.sum(in_spectrum))
     energy_idx_start = 0
     # loop through input bins:
@@ -262,15 +264,22 @@ def _rebin_listmode(
         in_left_edge = in_edges[in_idx]
         in_right_edge = in_edges[in_idx + 1]
         energies[energy_idx_start:energy_idx_stop] = in_left_edge + (
-            in_right_edge - in_left_edge) * np.random.rand(bin_counts)
+            in_right_edge - in_left_edge
+        ) * np.random.rand(bin_counts)
         energy_idx_start = energy_idx_stop
     # bin the energies (drops the energies outside of the out-binning-range)
     # N.B. use [:] to assign values to full array output with guvectorize:
     out_spectrum[:] = np.histogram(energies, bins=out_edges)[0]
 
 
-def rebin(in_spectra, in_edges, out_edges, method="interpolation",
-          slopes=None, zero_pad_warnings=True):
+def rebin(
+    in_spectra,
+    in_edges,
+    out_edges,
+    method="interpolation",
+    slopes=None,
+    zero_pad_warnings=True,
+):
     """
     Spectra rebinning via deterministic or stochastic methods.
 
@@ -315,11 +324,14 @@ def rebin(in_spectra, in_edges, out_edges, method="interpolation",
     # Cast data types and check listmode input
     if method == "listmode":
         if (in_spectra < 0).any():
-            raise RebinError('Cannot rebin spectra with negative values with '
-                             'listmode method')
+            raise RebinError(
+                "Cannot rebin spectra with negative values with listmode method"
+            )
         if (in_spectra < 1).all():
-            raise RebinError('Cannot rebin spectra with all values less than '
-                             'one with listmode method')
+            raise RebinError(
+                "Cannot rebin spectra with all values less than "
+                "one with listmode method"
+            )
         if np.issubdtype(in_spectra.dtype.type, np.floating):
             # np.rint is a ufunc: allows using this with tools such as xarray
             in_spectra_rint = np.rint(in_spectra).astype(int)
@@ -328,10 +340,11 @@ def rebin(in_spectra, in_edges, out_edges, method="interpolation",
                 pass
             else:
                 warnings.warn(
-                    'Argument in_spectra contains float value(s) which ' +
-                    'will have decimal precision loss when converting to ' +
-                    'integers for rebin method listmode.',
-                    RebinWarning)
+                    "Argument in_spectra contains float value(s) which "
+                    + "will have decimal precision loss when converting to "
+                    + "integers for rebin method listmode.",
+                    RebinWarning,
+                )
             in_spectra = in_spectra_rint
 
     if slopes is not None:
@@ -344,8 +357,8 @@ def rebin(in_spectra, in_edges, out_edges, method="interpolation",
     # issue should raise an error in guvectorize
     assert in_spectra.shape[-1] == in_edges.shape[-1] - 1
     # Check for increasing bin structure
-    _check_monotonic_increasing(in_edges, 'in_edges')
-    _check_monotonic_increasing(out_edges, 'out_edges')
+    _check_monotonic_increasing(in_edges, "in_edges")
+    _check_monotonic_increasing(out_edges, "out_edges")
     # Check for bin structure overlap
     _check_any_overlap(in_edges, out_edges)
     if zero_pad_warnings:
@@ -355,11 +368,7 @@ def rebin(in_spectra, in_edges, out_edges, method="interpolation",
 
     # Specific calls to (wrapped) nb.guvectorize'd rebinning methods
     if method == "interpolation":
-        return _rebin_interpolation(
-            in_spectra, in_edges, out_edges, slopes
-        )
+        return _rebin_interpolation(in_spectra, in_edges, out_edges, slopes)
     elif method == "listmode":
         return _rebin_listmode(in_spectra, in_edges, out_edges)
-    raise ValueError(
-        "{} is not a valid rebinning method".format(method)
-    )
+    raise ValueError("{} is not a valid rebinning method".format(method))
diff --git a/becquerel/core/spectrum.py b/becquerel/core/spectrum.py
index 947eab0a..2bec41ce 100644
--- a/becquerel/core/spectrum.py
+++ b/becquerel/core/spectrum.py
@@ -1,6 +1,5 @@
 """Base class for spectrum file parsers."""
 
-from __future__ import print_function
 import os
 from copy import deepcopy
 import datetime
@@ -91,9 +90,19 @@ class Spectrum(object):
       copy: return a deep copy of this Spectrum object
     """
 
-    def __init__(self, counts=None, cps=None, uncs=None, bin_edges_kev=None,
-                 bin_edges_raw=None, input_file_object=None, livetime=None,
-                 realtime=None, start_time=None, stop_time=None):
+    def __init__(
+        self,
+        counts=None,
+        cps=None,
+        uncs=None,
+        bin_edges_kev=None,
+        bin_edges_raw=None,
+        input_file_object=None,
+        livetime=None,
+        realtime=None,
+        start_time=None,
+        stop_time=None,
+    ):
         """Initialize the spectrum.
 
         Either counts or cps must be specified. Other args are optional.
@@ -134,7 +143,7 @@ def __init__(self, counts=None, cps=None, uncs=None, bin_edges_kev=None,
         """
 
         if not (counts is None) ^ (cps is None):
-            raise SpectrumError('Must specify one of counts or CPS')
+            raise SpectrumError("Must specify one of counts or CPS")
 
         self._counts = None
         self._cps = None
@@ -146,21 +155,23 @@ def __init__(self, counts=None, cps=None, uncs=None, bin_edges_kev=None,
 
         if counts is not None:
             if len(counts) == 0:
-                raise SpectrumError('Empty spectrum counts')
+                raise SpectrumError("Empty spectrum counts")
             if uncs is None and np.any(np.asarray(counts) < 0):
                 raise SpectrumError(
-                    'Negative values encountered in counts. Uncertainties ' +
-                    'are most likely not Poisson-distributed. Provide uncs ' +
-                    'to force initialization.')
+                    "Negative values encountered in counts. Uncertainties "
+                    "are most likely not Poisson-distributed. Provide uncs "
+                    "to force initialization."
+                )
             self._counts = handle_uncs(
-                counts, uncs, lambda x: np.maximum(np.sqrt(x), 1))
+                counts, uncs, lambda x: np.maximum(np.sqrt(x), 1)
+            )
         else:
             if len(cps) == 0:
-                raise SpectrumError('Empty spectrum counts')
+                raise SpectrumError("Empty spectrum counts")
             self._cps = handle_uncs(cps, uncs, lambda x: np.nan)
 
         if bin_edges_raw is None and not (counts is None and cps is None):
-            bin_edges_raw = np.arange(len(self)+1)
+            bin_edges_raw = np.arange(len(self) + 1)
         self.bin_edges_raw = bin_edges_raw
         self.bin_edges_kev = bin_edges_kev
 
@@ -172,32 +183,35 @@ def __init__(self, counts=None, cps=None, uncs=None, bin_edges_kev=None,
             if self.livetime is not None:
                 if self.livetime > self.realtime:
                     raise ValueError(
-                        'Livetime ({}) cannot exceed realtime ({})'.format(
-                            self.livetime, self.realtime))
-
-        self.start_time = handle_datetime(
-            start_time, 'start_time', allow_none=True)
-        self.stop_time = handle_datetime(
-            stop_time, 'stop_time', allow_none=True)
-
-        if (self.realtime is not None and
-                self.stop_time is not None and
-                self.start_time is not None):
+                        "Livetime ({}) cannot exceed realtime ({})".format(
+                            self.livetime, self.realtime
+                        )
+                    )
+
+        self.start_time = handle_datetime(start_time, "start_time", allow_none=True)
+        self.stop_time = handle_datetime(stop_time, "stop_time", allow_none=True)
+
+        if (
+            self.realtime is not None
+            and self.stop_time is not None
+            and self.start_time is not None
+        ):
             raise SpectrumError(
-                'Specify no more than 2 out of 3 args: ' +
-                'realtime, stop_time, start_time')
+                "Specify no more than 2 out of 3 args: "
+                + "realtime, stop_time, start_time"
+            )
         elif self.start_time is not None and self.stop_time is not None:
             if self.start_time > self.stop_time:
                 raise ValueError(
-                    'Stop time ({}) must be after start time ({})'.format(
-                        self.start_time, self.stop_time))
+                    "Stop time ({}) must be after start time ({})".format(
+                        self.start_time, self.stop_time
+                    )
+                )
             self.realtime = (self.stop_time - self.start_time).total_seconds()
         elif self.start_time is not None and self.realtime is not None:
-            self.stop_time = self.start_time + datetime.timedelta(
-                seconds=self.realtime)
+            self.stop_time = self.start_time + datetime.timedelta(seconds=self.realtime)
         elif self.realtime is not None and self.stop_time is not None:
-            self.start_time = self.stop_time - datetime.timedelta(
-                seconds=self.realtime)
+            self.start_time = self.stop_time - datetime.timedelta(seconds=self.realtime)
 
         self._infileobject = input_file_object
         if input_file_object is not None:
@@ -218,29 +232,26 @@ def __init__(self, counts=None, cps=None, uncs=None, bin_edges_kev=None,
         self.__array_priority__ = 1
 
     def __str__(self):
-        lines = ['becquerel.Spectrum']
+        lines = ["becquerel.Spectrum"]
         ltups = []
-        for k in ['start_time', 'stop_time', 'realtime', 'livetime',
-                  'is_calibrated']:
+        for k in ["start_time", "stop_time", "realtime", "livetime", "is_calibrated"]:
             ltups.append((k, getattr(self, k)))
-        ltups.append(('num_bins', len(self.bin_indices)))
+        ltups.append(("num_bins", len(self.bin_indices)))
         if self._counts is None:
-            ltups.append(('gross_counts', None))
+            ltups.append(("gross_counts", None))
         else:
-            ltups.append(('gross_counts', self.counts.sum()))
+            ltups.append(("gross_counts", self.counts.sum()))
         try:
-            ltups.append(('gross_cps', self.cps.sum()))
+            ltups.append(("gross_cps", self.cps.sum()))
         except SpectrumError:
-            ltups.append(('gross_cps', None))
-        if hasattr(self, 'infilename'):
-            ltups.append(('filename', self.infilename))
+            ltups.append(("gross_cps", None))
+        if hasattr(self, "infilename"):
+            ltups.append(("filename", self.infilename))
         else:
-            ltups.append(('filename', None))
+            ltups.append(("filename", None))
         for lt in ltups:
-            lines.append('    {:15} {}'.format(
-                '{}:'.format(lt[0]),
-                lt[1]))
-        return '\n'.join(lines)
+            lines.append("    {:15} {}".format("{}:".format(lt[0]), lt[1]))
+        return "\n".join(lines)
 
     __repr__ = __str__
 
@@ -265,7 +276,8 @@ def counts(self):
                 return self.cps * self.livetime
             except TypeError:
                 raise SpectrumError(
-                    'Unknown livetime; cannot calculate counts from CPS')
+                    "Unknown livetime; cannot calculate counts from CPS"
+                )
 
     @property
     def counts_vals(self):
@@ -308,7 +320,8 @@ def cps(self):
                 return self.counts / self.livetime
             except TypeError:
                 raise SpectrumError(
-                    'Unknown livetime; cannot calculate CPS from counts')
+                    "Unknown livetime; cannot calculate CPS from counts"
+                )
 
     @property
     def cps_vals(self):
@@ -383,9 +396,11 @@ def channels(self):
           np.array of int's from 0 to (len(self.counts) - 1)
         """
 
-        warnings.warn('channels is deprecated terminology and will be removed '
-                      'in a future release. Use bin_indices instead.',
-                      DeprecationWarning)
+        warnings.warn(
+            "channels is deprecated terminology and will be removed "
+            "in a future release. Use bin_indices instead.",
+            DeprecationWarning,
+        )
         return np.arange(len(self), dtype=int)
 
     @property
@@ -420,7 +435,7 @@ def bin_centers_kev(self):
         """
 
         if not self.is_calibrated:
-            raise UncalibratedError('Spectrum is not calibrated')
+            raise UncalibratedError("Spectrum is not calibrated")
         else:
             return bin_centers_from_edges(self.bin_edges_kev)
 
@@ -437,12 +452,14 @@ def energies_kev(self):
             bin_centers_kev in a future release
         """
 
-        warnings.warn('energies_kev is deprecated and will be removed in a '
-                      'future release. Use bin_centers_kev instead.',
-                      DeprecationWarning)
+        warnings.warn(
+            "energies_kev is deprecated and will be removed in a "
+            "future release. Use bin_centers_kev instead.",
+            DeprecationWarning,
+        )
 
         if not self.is_calibrated:
-            raise UncalibratedError('Spectrum is not calibrated')
+            raise UncalibratedError("Spectrum is not calibrated")
         else:
             return bin_centers_from_edges(self.bin_edges_kev)
 
@@ -458,7 +475,7 @@ def bin_widths_kev(self):
         """
 
         if not self.is_calibrated:
-            raise UncalibratedError('Spectrum is not calibrated')
+            raise UncalibratedError("Spectrum is not calibrated")
         else:
             return np.diff(self.bin_edges_kev)
 
@@ -475,12 +492,15 @@ def bin_widths(self):
             bin_widths_kev in a future release
         """
 
-        warnings.warn('bin_widths is deprecated and will be removed in a '
-                      'future release. Use bin_widths_kev (or bin_widths_raw) '
-                      'instead.', DeprecationWarning)
+        warnings.warn(
+            "bin_widths is deprecated and will be removed in a "
+            "future release. Use bin_widths_kev (or bin_widths_raw) "
+            "instead.",
+            DeprecationWarning,
+        )
 
         if not self.is_calibrated:
-            raise UncalibratedError('Spectrum is not calibrated')
+            raise UncalibratedError("Spectrum is not calibrated")
         else:
             return np.diff(self.bin_edges_kev)
 
@@ -518,10 +538,9 @@ def bin_edges_kev(self, bin_edges_kev):
         if bin_edges_kev is None:
             self._bin_edges_kev = None
         elif len(bin_edges_kev) != len(self) + 1:
-            raise SpectrumError('Bad length of bin edges vector')
+            raise SpectrumError("Bad length of bin edges vector")
         elif np.any(np.diff(bin_edges_kev) <= 0):
-            raise ValueError(
-                'Bin edge energies must be strictly increasing')
+            raise ValueError("Bin edge energies must be strictly increasing")
         else:
             self._bin_edges_kev = np.array(bin_edges_kev, dtype=float)
 
@@ -549,10 +568,9 @@ def bin_edges_raw(self, bin_edges_raw):
         if bin_edges_raw is None:
             self._bin_edges_raw = None
         elif len(bin_edges_raw) != len(self) + 1:
-            raise SpectrumError('Bad length of bin edges vector')
+            raise SpectrumError("Bad length of bin edges vector")
         elif np.any(np.diff(bin_edges_raw) <= 0):
-            raise ValueError(
-                'Raw bin edges must be strictly increasing')
+            raise ValueError("Raw bin edges must be strictly increasing")
         else:
             self._bin_edges_raw = np.array(bin_edges_raw, dtype=float)
 
@@ -572,17 +590,18 @@ def from_file(cls, infilename):
 
         spect_file_obj = _get_file_object(infilename)
 
-        kwargs = {'counts': spect_file_obj.data,
-                  'input_file_object': spect_file_obj,
-                  'bin_edges_kev': spect_file_obj.bin_edges_kev}
+        kwargs = {
+            "counts": spect_file_obj.data,
+            "input_file_object": spect_file_obj,
+            "bin_edges_kev": spect_file_obj.bin_edges_kev,
+        }
 
         # TODO Get more attributes from self.infileobj
 
         return cls(**kwargs)
 
     @classmethod
-    def from_listmode(cls, listmode_data, bins=None, xmin=None, xmax=None,
-                      **kwargs):
+    def from_listmode(cls, listmode_data, bins=None, xmin=None, xmax=None, **kwargs):
         """Construct a Spectrum object (specifically the `bin_edges_raw` and
         `counts` of a histogram) from an array of listmode data. It is left to
         the user to set kwargs realtime, livetime, etc, rather than trying to
@@ -618,12 +637,12 @@ def from_listmode(cls, listmode_data, bins=None, xmin=None, xmax=None,
         else:
             assert len(bins) > 1
 
-        bin_counts, bin_edges = np.histogram(listmode_data,
-                                             bins=bins,
-                                             range=(xmin, xmax))
+        bin_counts, bin_edges = np.histogram(
+            listmode_data, bins=bins, range=(xmin, xmax)
+        )
 
-        kwargs['counts'] = bin_counts
-        kwargs['bin_edges_raw'] = bin_edges
+        kwargs["counts"] = bin_counts
+        kwargs["bin_edges_raw"] = bin_edges
 
         return cls(**kwargs)
 
@@ -674,19 +693,23 @@ def __add__(self, other):
         self._add_sub_error_checking(other)
         if (self._counts is None) ^ (other._counts is None):
             raise SpectrumError(
-                'Addition of counts-based and CPS-based spectra is ' +
-                'ambiguous, use Spectrum(counts=specA.counts+specB.counts) ' +
-                'or Spectrum(cps=specA.cps+specB.cps) instead.')
+                "Addition of counts-based and CPS-based spectra is "
+                + "ambiguous, use Spectrum(counts=specA.counts+specB.counts) "
+                + "or Spectrum(cps=specA.cps+specB.cps) instead."
+            )
 
         if self._counts is not None and other._counts is not None:
-            kwargs = {'counts': self.counts + other.counts}
+            kwargs = {"counts": self.counts + other.counts}
             if self.livetime and other.livetime:
-                kwargs['livetime'] = self.livetime + other.livetime
+                kwargs["livetime"] = self.livetime + other.livetime
             else:
-                warnings.warn('Addition of counts with missing livetimes, ' +
-                              'livetime was set to None.', SpectrumWarning)
+                warnings.warn(
+                    "Addition of counts with missing livetimes, "
+                    + "livetime was set to None.",
+                    SpectrumWarning,
+                )
         else:
-            kwargs = {'cps': self.cps + other.cps}
+            kwargs = {"cps": self.cps + other.cps}
 
         if self.is_calibrated and other.is_calibrated:
             spect_obj = Spectrum(bin_edges_kev=self.bin_edges_kev, **kwargs)
@@ -722,20 +745,27 @@ def __sub__(self, other):
 
         self._add_sub_error_checking(other)
         try:
-            kwargs = {'cps': self.cps - other.cps}
+            kwargs = {"cps": self.cps - other.cps}
             if (self._cps is None) or (other._cps is None):
-                warnings.warn('Subtraction of counts-based specta, spectra ' +
-                              'have been converted to CPS', SpectrumWarning)
+                warnings.warn(
+                    "Subtraction of counts-based specta, spectra "
+                    + "have been converted to CPS",
+                    SpectrumWarning,
+                )
         except SpectrumError:
             try:
-                kwargs = {'counts': self.counts_vals - other.counts_vals}
-                kwargs['uncs'] = [np.nan]*len(self)
-                warnings.warn('Subtraction of counts-based spectra, ' +
-                              'livetimes have been ignored.', SpectrumWarning)
+                kwargs = {"counts": self.counts_vals - other.counts_vals}
+                kwargs["uncs"] = [np.nan] * len(self)
+                warnings.warn(
+                    "Subtraction of counts-based spectra, "
+                    + "livetimes have been ignored.",
+                    SpectrumWarning,
+                )
             except SpectrumError:
                 raise SpectrumError(
-                    'Subtraction of counts and CPS-based spectra without' +
-                    'livetimes not possible')
+                    "Subtraction of counts and CPS-based spectra without"
+                    + "livetimes not possible"
+                )
 
         if self.is_calibrated and other.is_calibrated:
             spect_obj = Spectrum(bin_edges_kev=self.bin_edges_kev, **kwargs)
@@ -758,27 +788,30 @@ def _add_sub_error_checking(self, other):
 
         if not isinstance(other, Spectrum):
             raise TypeError(
-                'Spectrum addition/subtraction must involve a Spectrum object')
+                "Spectrum addition/subtraction must involve a Spectrum object"
+            )
         if len(self) != len(other):
-            raise SpectrumError(
-                'Cannot add/subtract spectra of different lengths')
+            raise SpectrumError("Cannot add/subtract spectra of different lengths")
         if self.is_calibrated ^ other.is_calibrated:
             raise SpectrumError(
-                'Cannot add/subtract uncalibrated spectrum to/from a ' +
-                'calibrated spectrum. If both have the same calibration, ' +
-                'please use the "calibrate_like" method')
+                "Cannot add/subtract uncalibrated spectrum to/from a "
+                + "calibrated spectrum. If both have the same calibration, "
+                + 'please use the "calibrate_like" method'
+            )
         if self.is_calibrated and other.is_calibrated:
             if not np.all(self.bin_edges_kev == other.bin_edges_kev):
                 raise NotImplementedError(
-                    'Addition/subtraction for arbitrary calibrated spectra ' +
-                    'not implemented')
+                    "Addition/subtraction for arbitrary calibrated spectra "
+                    + "not implemented"
+                )
                 # TODO: if both spectra are calibrated but with different
                 #   calibrations, should one be rebinned to match?
         if not self.is_calibrated and not other.is_calibrated:
             if not np.all(self.bin_edges_raw == other.bin_edges_raw):
                 raise NotImplementedError(
-                    'Addition/subtraction for arbitrary uncalibrated ' +
-                    'spectra not implemented')
+                    "Addition/subtraction for arbitrary uncalibrated "
+                    + "spectra not implemented"
+                )
 
     def __mul__(self, other):
         """Return a new Spectrum object with counts (or CPS) scaled up.
@@ -833,28 +866,29 @@ def _mul_div(self, scaling_factor, div=False):
             try:
                 scaling_factor = float(scaling_factor)
             except (TypeError, ValueError):
-                raise TypeError(
-                    'Spectrum must be multiplied/divided by a scalar')
-            if (scaling_factor == 0 or
-                    np.isinf(scaling_factor) or
-                    np.isnan(scaling_factor)):
-                raise ValueError(
-                    'Scaling factor must be nonzero and finite')
+                raise TypeError("Spectrum must be multiplied/divided by a scalar")
+            if (
+                scaling_factor == 0
+                or np.isinf(scaling_factor)
+                or np.isnan(scaling_factor)
+            ):
+                raise ValueError("Scaling factor must be nonzero and finite")
         else:
-            if (scaling_factor.nominal_value == 0 or
-                    np.isinf(scaling_factor.nominal_value) or
-                    np.isnan(scaling_factor.nominal_value)):
-                raise ValueError(
-                    'Scaling factor must be nonzero and finite')
+            if (
+                scaling_factor.nominal_value == 0
+                or np.isinf(scaling_factor.nominal_value)
+                or np.isnan(scaling_factor.nominal_value)
+            ):
+                raise ValueError("Scaling factor must be nonzero and finite")
         if div:
             multiplier = 1 / scaling_factor
         else:
             multiplier = scaling_factor
 
         if self._counts is not None:
-            data_arg = {'counts': self.counts * multiplier}
+            data_arg = {"counts": self.counts * multiplier}
         else:
-            data_arg = {'cps': self.cps * multiplier}
+            data_arg = {"cps": self.cps * multiplier}
 
         if self.is_calibrated:
             spect_obj = Spectrum(bin_edges_kev=self.bin_edges_kev, **data_arg)
@@ -890,32 +924,37 @@ def downsample(self, f, handle_livetime=None):
         """
 
         if self._counts is None:
-            raise SpectrumError('Cannot downsample from CPS')
+            raise SpectrumError("Cannot downsample from CPS")
         if f < 1:
-            raise ValueError('Cannot upsample a spectrum; f must be > 1')
+            raise ValueError("Cannot upsample a spectrum; f must be > 1")
 
         if handle_livetime is None:
             new_livetime = None
-        elif handle_livetime.lower() == 'preserve':
+        elif handle_livetime.lower() == "preserve":
             new_livetime = self.livetime
-        elif handle_livetime.lower() == 'reduce':
+        elif handle_livetime.lower() == "reduce":
             new_livetime = self.livetime / f
         else:
-            raise ValueError('Illegal value for handle_livetime: {}'.format(
-                handle_livetime))
+            raise ValueError(
+                "Illegal value for handle_livetime: {}".format(handle_livetime)
+            )
 
         # TODO handle uncertainty?
         old_counts = self.counts_vals.astype(int)
-        new_counts = np.random.binomial(old_counts, 1. / f)
+        new_counts = np.random.binomial(old_counts, 1.0 / f)
 
         if self.is_calibrated:
-            return Spectrum(counts=new_counts,
-                            bin_edges_kev=self.bin_edges_kev,
-                            livetime=new_livetime)
+            return Spectrum(
+                counts=new_counts,
+                bin_edges_kev=self.bin_edges_kev,
+                livetime=new_livetime,
+            )
         else:
-            return Spectrum(counts=new_counts,
-                            bin_edges_raw=self.bin_edges_raw,
-                            livetime=new_livetime)
+            return Spectrum(
+                counts=new_counts,
+                bin_edges_raw=self.bin_edges_raw,
+                livetime=new_livetime,
+            )
 
     def has_uniform_bins(self, use_kev=None, rtol=None):
         """Test whether the Spectrum has uniform binning.
@@ -941,14 +980,15 @@ def has_uniform_bins(self, use_kev=None, rtol=None):
         if rtol is None:
             rtol = 100 * EPS
         if rtol < EPS:
-            raise ValueError('Relative tolerance rtol cannot be < system EPS')
+            raise ValueError("Relative tolerance rtol cannot be < system EPS")
 
         if use_kev is None:
             use_kev = self.is_calibrated
 
         if use_kev and not self.is_calibrated:
-            raise UncalibratedError('Cannot access energy bins with an ' +
-                                    'uncalibrated Spectrum.')
+            raise UncalibratedError(
+                "Cannot access energy bins with an uncalibrated Spectrum."
+            )
 
         bin_widths = self.bin_widths_kev if use_kev else self.bin_widths_raw
 
@@ -960,7 +1000,7 @@ def has_uniform_bins(self, use_kev=None, rtol=None):
         iterator = iter(bin_widths)
         x0 = next(iterator, None)
         for x in iterator:
-            if abs(x/x0 - 1.0) > rtol:
+            if abs(x / x0 - 1.0) > rtol:
                 return False
         return True
 
@@ -991,18 +1031,19 @@ def find_bin_index(self, x, use_kev=None):
             use_kev = self.is_calibrated
 
         if use_kev and not self.is_calibrated:
-            raise UncalibratedError('Cannot access energy bins with an ' +
-                                    'uncalibrated Spectrum.')
+            raise UncalibratedError(
+                "Cannot access energy bins with an uncalibrated Spectrum."
+            )
 
         bin_edges, bin_widths, _ = self.get_bin_properties(use_kev)
         x = np.asarray(x)
 
         if np.any(x < bin_edges[0]):
-            raise SpectrumError('requested x is < lowest bin edge')
+            raise SpectrumError("requested x is < lowest bin edge")
         if np.any(x >= bin_edges[-1]):
-            raise SpectrumError('requested x is >= highest bin edge')
+            raise SpectrumError("requested x is >= highest bin edge")
 
-        return np.searchsorted(bin_edges, x, 'right') - 1
+        return np.searchsorted(bin_edges, x, "right") - 1
 
     def get_bin_properties(self, use_kev=None):
         """Convenience function to get bin properties: edges, widths, centers.
@@ -1021,8 +1062,9 @@ def get_bin_properties(self, use_kev=None):
 
         if use_kev:
             if not self.is_calibrated:
-                raise UncalibratedError('Cannot access energy bins with an ' +
-                                        'uncalibrated Spectrum.')
+                raise UncalibratedError(
+                    "Cannot access energy bins with an uncalibrated Spectrum."
+                )
             return self.bin_edges_kev, self.bin_widths_kev, self.bin_centers_kev
         else:
             return self.bin_edges_raw, self.bin_widths_raw, self.bin_centers_raw
@@ -1054,7 +1096,7 @@ def calibrate_like(self, other):
             self.bin_edges_kev = other.bin_edges_kev.copy()
             self.energy_cal = other.energy_cal
         else:
-            raise UncalibratedError('Other spectrum is not calibrated')
+            raise UncalibratedError("Other spectrum is not calibrated")
 
     def rm_calibration(self):
         """Remove the calibration (if it exists) from this spectrum."""
@@ -1080,9 +1122,9 @@ def combine_bins(self, f):
 
         f = int(f)
         if self._counts is None:
-            key = 'cps'
+            key = "cps"
         else:
-            key = 'counts'
+            key = "counts"
         data = getattr(self, key)
         if len(self) % f == 0:
             padded_counts = np.copy(data)
@@ -1096,19 +1138,23 @@ def combine_bins(self, f):
             combined_bin_edges = self.bin_edges_kev[::f]
             if combined_bin_edges[-1] != self.bin_edges_kev[-1]:
                 combined_bin_edges = np.append(
-                    combined_bin_edges, self.bin_edges_kev[-1])
+                    combined_bin_edges, self.bin_edges_kev[-1]
+                )
         else:  # TODO: should be able to combine bins
             combined_bin_edges = None
 
-        kwargs = {key: combined_counts,
-                  'bin_edges_kev': combined_bin_edges,
-                  'input_file_object': self._infileobject,
-                  'livetime': self.livetime}
+        kwargs = {
+            key: combined_counts,
+            "bin_edges_kev": combined_bin_edges,
+            "input_file_object": self._infileobject,
+            "livetime": self.livetime,
+        }
         obj = Spectrum(**kwargs)
         return obj
 
-    def rebin(self, out_edges, method="interpolation", slopes=None,
-              zero_pad_warnings=True):
+    def rebin(
+        self, out_edges, method="interpolation", slopes=None, zero_pad_warnings=True
+    ):
         """
         Spectra rebinning via deterministic or stochastic methods.
 
@@ -1134,27 +1180,37 @@ def rebin(self, out_edges, method="interpolation", slopes=None,
             A new Spectrum object with the rebinned data.
         """
         if self.bin_edges_kev is None:
-            raise SpectrumError('Cannot rebin spectrum without energy '
-                                'calibration')  # TODO: why not?
+            raise SpectrumError(
+                "Cannot rebin spectrum without energy calibration"
+            )  # TODO: why not?
         in_spec = self.counts_vals
-        if method.lower() == 'listmode':
+        if method.lower() == "listmode":
             if (self._counts is None) and (self.livetime is not None):
                 warnings.warn(
-                    'Rebinning by listmode method without explicit counts ' +
-                    'provided in Spectrum object',
-                    SpectrumWarning)
-        out_spec = rebin(in_spec, self.bin_edges_kev, out_edges,
-                         method=method, slopes=slopes,
-                         zero_pad_warnings=zero_pad_warnings)
-        return Spectrum(counts=out_spec,
-                        uncs=np.sqrt(out_spec),
-                        bin_edges_kev=out_edges,
-                        input_file_object=self._infileobject,
-                        livetime=self.livetime)
+                    "Rebinning by listmode method without explicit counts "
+                    + "provided in Spectrum object",
+                    SpectrumWarning,
+                )
+        out_spec = rebin(
+            in_spec,
+            self.bin_edges_kev,
+            out_edges,
+            method=method,
+            slopes=slopes,
+            zero_pad_warnings=zero_pad_warnings,
+        )
+        return Spectrum(
+            counts=out_spec,
+            uncs=np.sqrt(out_spec),
+            bin_edges_kev=out_edges,
+            input_file_object=self._infileobject,
+            livetime=self.livetime,
+        )
 
     def rebin_like(self, other, zero_pad_warnings=False, **kwargs):
-        return self.rebin(other.bin_edges_kev,
-                          zero_pad_warnings=zero_pad_warnings, **kwargs)
+        return self.rebin(
+            other.bin_edges_kev, zero_pad_warnings=zero_pad_warnings, **kwargs
+        )
         # TODO: raw here too?
 
     def parse_xmode(self, xmode):
@@ -1175,14 +1231,14 @@ def parse_xmode(self, xmode):
         ValueError
             If the xmode parameter is unsupported
         """
-        if xmode == 'energy':
+        if xmode == "energy":
             xedges = self.bin_edges_kev
-            xlabel = 'Energy [keV]'
-        elif xmode == 'channel':
+            xlabel = "Energy [keV]"
+        elif xmode == "channel":
             xedges = self.bin_edges_raw
-            xlabel = 'Channel'
+            xlabel = "Channel"
         else:
-            raise ValueError('Unsupported xmode: {0:s}'.format(xmode))
+            raise ValueError("Unsupported xmode: {0:s}".format(xmode))
         return xedges, xlabel
 
     def parse_ymode(self, ymode):
@@ -1203,20 +1259,20 @@ def parse_ymode(self, ymode):
         ValueError
             If the ymode parameter is unsupported
         """
-        if ymode == 'counts':
+        if ymode == "counts":
             ydata = self.counts_vals
             yuncs = self.counts_uncs
-            ylabel = 'Counts'
-        elif ymode == 'cps':
+            ylabel = "Counts"
+        elif ymode == "cps":
             ydata = self.cps_vals
             yuncs = self.cps_uncs
-            ylabel = 'Countrate [1/s]'
-        elif ymode == 'cpskev':
+            ylabel = "Countrate [1/s]"
+        elif ymode == "cpskev":
             ydata = self.cpskev_vals
             yuncs = self.cpskev_uncs
-            ylabel = 'Countrate [1/s/keV]'
+            ylabel = "Countrate [1/s/keV]"
         else:
-            raise ValueError('Unsupported ymode: {0:s}'.format(ymode))
+            raise ValueError("Unsupported ymode: {0:s}".format(ymode))
         return ydata, yuncs, ylabel
 
     def plot(self, *fmt, **kwargs):
@@ -1248,23 +1304,24 @@ def plot(self, *fmt, **kwargs):
           matplotlib axes object
         """
 
-        emode = 'none'
+        emode = "none"
         alpha = 1
-        if 'emode' in kwargs:
-            emode = kwargs.pop('emode')
-        if 'alpha' in kwargs:
-            alpha = kwargs['alpha']
+        if "emode" in kwargs:
+            emode = kwargs.pop("emode")
+        if "alpha" in kwargs:
+            alpha = kwargs["alpha"]
 
         plotter = plotting.SpectrumPlotter(self, *fmt, **kwargs)
         ax = plotter.plot()
         color = ax.get_lines()[-1].get_color()
-        if emode == 'band':
-            plotter.errorband(color=color, alpha=alpha*0.5, label='_nolegend_')
-        elif emode == 'bars' or emode == 'bar':
-            plotter.errorbar(color=color, label='_nolegend_')
-        elif emode != 'none':
-            raise SpectrumError("Unknown error mode '{}', use 'bars' "
-                                "or 'band'".format(emode))
+        if emode == "band":
+            plotter.errorband(color=color, alpha=alpha * 0.5, label="_nolegend_")
+        elif emode == "bars" or emode == "bar":
+            plotter.errorbar(color=color, label="_nolegend_")
+        elif emode != "none":
+            raise SpectrumError(
+                "Unknown error mode '{}', use 'bars' " "or 'band'".format(emode)
+            )
         return ax
 
     def fill_between(self, **kwargs):
@@ -1319,9 +1376,7 @@ def fit(self, model, xmode, ymode, roi=None, perform_fit=True):
         ydata, yuncs, ylabel = self.parse_ymode(ymode)
 
         xcenters = bin_centers_from_edges(xedges)
-        fitter = fitting.Fitter(
-            model, x=xcenters, y=ydata, y_unc=yuncs, roi=roi
-        )
+        fitter = fitting.Fitter(model, x=xcenters, y=ydata, y_unc=yuncs, roi=roi)
         fitter._xmode = xmode
         fitter._ymode = ymode
         if perform_fit:
@@ -1346,12 +1401,11 @@ def _get_file_object(infilename):
     """
 
     _, extension = os.path.splitext(infilename)
-    if extension.lower() == '.spe':
+    if extension.lower() == ".spe":
         return parsers.SpeFile(infilename)
-    elif extension.lower() == '.spc':
+    elif extension.lower() == ".spc":
         return parsers.SpcFile(infilename)
-    elif extension.lower() == '.cnf':
+    elif extension.lower() == ".cnf":
         return parsers.CnfFile(infilename)
     else:
-        raise NotImplementedError(
-            'File type {} can not be read'.format(extension))
+        raise NotImplementedError("File type {} can not be read".format(extension))
diff --git a/becquerel/core/utils.py b/becquerel/core/utils.py
index e175407b..69d072f3 100644
--- a/becquerel/core/utils.py
+++ b/becquerel/core/utils.py
@@ -1,44 +1,16 @@
 """General utility functions to be shared among core modules."""
 
-from __future__ import print_function
 import sys
 import datetime
 from dateutil.parser import parse as dateutil_parse
 from uncertainties import UFloat, unumpy
 import warnings
 import numpy as np
-try:
-    # Python 3.x
-    from collections.abc import Iterable
-except ImportError:
-    # Python 2.x
-    from collections import Iterable
 
 EPS = np.finfo(float).eps
 
 VECTOR_TYPES = (list, tuple, np.ndarray)
 
-if sys.version_info < (3, 2):
-
-    class ResourceWarning(Warning):
-        pass
-
-else:
-
-    ResourceWarning = ResourceWarning
-
-if sys.version_info < (3,):
-
-    def isstring(s):
-        """Test for strings in python2"""
-        return isinstance(s, basestring)
-
-else:
-
-    def isstring(s):
-        """Test for strings in python3"""
-        return isinstance(s, str)
-
 
 class UncertaintiesError(Exception):
     """Raised when uncertainties are badly specified in an input."""
@@ -68,8 +40,7 @@ def all_ufloats(x):
         if all(are_ufloats):
             return True
         elif any(are_ufloats):
-            raise UncertaintiesError(
-                'Input should be all UFloats or no UFloats')
+            raise UncertaintiesError("Input should be all UFloats or no UFloats")
         else:
             return False
 
@@ -97,15 +68,17 @@ def handle_uncs(x_array, x_uncs, default_unc_func):
     if ufloats and x_uncs is None:
         return np.asarray(x_array)
     elif ufloats:
-        raise UncertaintiesError('Specify uncertainties with UFloats or ' +
-                                 'by separate argument, but not both')
+        raise UncertaintiesError(
+            "Specify uncertainties with UFloats or "
+            + "by separate argument, but not both"
+        )
     elif x_uncs is not None:
         return unumpy.uarray(x_array, x_uncs)
     else:
         return unumpy.uarray(x_array, default_unc_func(x_array))
 
 
-def handle_datetime(input_time, error_name='datetime arg', allow_none=False):
+def handle_datetime(input_time, error_name="datetime arg", allow_none=False):
     """Parse an argument as a date, datetime, date+time string, or None.
 
     Args:
@@ -126,18 +99,15 @@ def handle_datetime(input_time, error_name='datetime arg', allow_none=False):
         return input_time
     elif isinstance(input_time, datetime.date):
         warnings.warn(
-            'datetime.date passed in with no time; defaulting to 0:00 on date'
-        )
-        return datetime.datetime(
-            input_time.year, input_time.month, input_time.day
+            "datetime.date passed in with no time; defaulting to 0:00 on date"
         )
-    elif isstring(input_time):
+        return datetime.datetime(input_time.year, input_time.month, input_time.day)
+    elif isinstance(input_time, str):
         return dateutil_parse(input_time)
     elif input_time is None and allow_none:
         return None
     else:
-        raise TypeError(
-            'Unknown type for {}: {}'.format(error_name, input_time))
+        raise TypeError("Unknown type for {}: {}".format(error_name, input_time))
 
 
 def bin_centers_from_edges(edges_kev):
diff --git a/becquerel/parsers/cnf_file.py b/becquerel/parsers/cnf_file.py
index d7733e54..28a9b9e8 100644
--- a/becquerel/parsers/cnf_file.py
+++ b/becquerel/parsers/cnf_file.py
@@ -10,7 +10,6 @@
 
 """
 
-from __future__ import print_function
 import datetime
 import os
 import struct
@@ -36,11 +35,11 @@ def _convert_date(data, index):
     # 3506716800 is the number of seconds between the start of the MJD epoch
     # to the start of the Unix epoch on 1 January 1970.
     d = _from_little_endian(data, index, 8)
-    t = (d / 10000000.) - 3506716800
+    t = (d / 10000000.0) - 3506716800
     try:
         return datetime.datetime.utcfromtimestamp(t)
     except:
-        raise CnfFileParsingError('Date conversion failed')
+        raise CnfFileParsingError("Date conversion failed")
 
 
 def _convert_time(data, index):
@@ -48,7 +47,7 @@ def _convert_time(data, index):
     # 2^(64) - number of 100ns intervals since timing started
     d = _from_little_endian(data, index, 8)
     d = (pow(2, 64) - 1) & ~d
-    return d * 1.e-7
+    return d * 1.0e-7
 
 
 def _from_pdp11(data, index):
@@ -61,24 +60,26 @@ def _from_pdp11(data, index):
         sign = 1
     else:
         sign = -1
-    exb = ((data[index + 1] & 0x7f) << 1) + ((data[index] & 0x80) >> 7)
+    exb = ((data[index + 1] & 0x7F) << 1) + ((data[index] & 0x80) >> 7)
     if exb == 0:
         if sign == -1:
             return np.NaN
         else:
-            return 0.
-    h = data[index + 2] / 256. / 256. / 256. \
-        + data[index + 3] / 256. / 256. \
-        + (128 + (data[index] & 0x7f)) / 256.
-    return sign * h * pow(2., exb - 128.)
+            return 0.0
+    h = (
+        data[index + 2] / 256.0 / 256.0 / 256.0
+        + data[index + 3] / 256.0 / 256.0
+        + (128 + (data[index] & 0x7F)) / 256.0
+    )
+    return sign * h * pow(2.0, exb - 128.0)
 
 
 def _read_energy_calibration(data, index):
     """Read the four energy calibration coefficients."""
-    coeff = [0., 0., 0., 0.]
+    coeff = [0.0, 0.0, 0.0, 0.0]
     for i in range(4):
         coeff[i] = _from_pdp11(data, index + 2 * 4 + 28 + 4 * i)
-    if coeff[1] == 0.:
+    if coeff[1] == 0.0:
         return None
     return coeff
 
@@ -103,29 +104,29 @@ def __init__(self, filename):
         """Initialize the CNF file."""
         super(CnfFile, self).__init__(filename)
         _, ext = os.path.splitext(self.filename)
-        if ext.lower() != '.cnf':
-            raise CnfFileParsingError('File extension is incorrect: ' + ext)
+        if ext.lower() != ".cnf":
+            raise CnfFileParsingError("File extension is incorrect: " + ext)
         # read in the data
         self.read()
         self.apply_calibration()
 
     def read(self, verbose=False):
         """Read in the file."""
-        print('CnfFile: Reading file ' + self.filename)
+        print("CnfFile: Reading file " + self.filename)
         self.realtime = 0.0
         self.livetime = 0.0
         self.channels = np.array([], dtype=np.float)
         self.data = np.array([], dtype=np.float)
         self.cal_coeff = []
         data = []
-        with open(self.filename, 'rb') as f:
+        with open(self.filename, "rb") as f:
             data_str = f.read(1)
             while data_str:
-                data_int = struct.unpack('1B', data_str)
+                data_int = struct.unpack("1B", data_str)
                 data.append(data_int[0])
                 data_str = f.read(1)
         if verbose:
-            print('file size (bytes):', len(data))
+            print("file size (bytes):", len(data))
         # skip first 112 bytes of file
         i = 112
         # scan for offsets
@@ -136,8 +137,11 @@ def read(self, verbose=False):
         offset_chan = 0
         for i in range(112, 128 * 1024, 48):
             offset = _from_little_endian(data, i + 10, 4)
-            if (data[i + 1] == 0x20 and data[i + 2] == 0x01) \
-                    or (data[i + 1] == 0) or (data[i + 2] == 0):
+            if (
+                (data[i + 1] == 0x20 and data[i + 2] == 0x01)
+                or (data[i + 1] == 0)
+                or (data[i + 2] == 0)
+            ):
                 if data[i] == 0:
                     if offset_acq == 0:
                         offset_acq = offset
@@ -154,117 +158,132 @@ def read(self, verbose=False):
                         offset_chan = offset
                 else:
                     pass
-                if offset_acq != 0 and offset_sam != 0 \
-                        and offset_eff != 0 and offset_chan != 0:
+                if (
+                    offset_acq != 0
+                    and offset_sam != 0
+                    and offset_eff != 0
+                    and offset_chan != 0
+                ):
                     break
         if offset_enc == 0:
             offset_enc = offset_acq
 
         # extract sample information
-        if (offset_sam + 48 + 80) >= len(data) or data[offset_sam] != 1 \
-                or data[offset_sam + 1] != 0x20:
+        if (
+            (offset_sam + 48 + 80) >= len(data)
+            or data[offset_sam] != 1
+            or data[offset_sam + 1] != 0x20
+        ):
             if verbose:
                 print(offset_sam + 48 + 80, len(data))
                 print(data[offset_sam], data[offset_sam + 1])
-            raise CnfFileParsingError('Sample information not found')
+            raise CnfFileParsingError("Sample information not found")
         else:
-            sample_name = ''
+            sample_name = ""
             for j in range(offset_sam + 48, offset_sam + 48 + 64):
                 sample_name += chr(data[j])
             if verbose:
-                print('sample name: ', sample_name)
-            sample_id = ''
+                print("sample name: ", sample_name)
+            sample_id = ""
             for j in range(offset_sam + 112, offset_sam + 112 + 64):
                 sample_id += chr(data[j])
             if verbose:
-                print('sample id:   ', sample_id)
-            sample_type = ''
+                print("sample id:   ", sample_id)
+            sample_type = ""
             for j in range(offset_sam + 176, offset_sam + 176 + 16):
                 sample_type += chr(data[j])
             if verbose:
-                print('sample type: ', sample_type)
-            sample_unit = ''
+                print("sample type: ", sample_type)
+            sample_unit = ""
             for j in range(offset_sam + 192, offset_sam + 192 + 64):
                 sample_unit += chr(data[j])
             if verbose:
-                print('sample unit: ', sample_unit)
-            user_name = ''
-            for j in range(offset_sam + 0x02d6, offset_sam + 0x02d6 + 32):
+                print("sample unit: ", sample_unit)
+            user_name = ""
+            for j in range(offset_sam + 0x02D6, offset_sam + 0x02D6 + 32):
                 user_name += chr(data[j])
             if verbose:
-                print('user name:   ', user_name)
-            sample_desc = ''
-            for j in range(offset_sam + 0x036e, offset_sam + 0x036e + 256):
+                print("user name:   ", user_name)
+            sample_desc = ""
+            for j in range(offset_sam + 0x036E, offset_sam + 0x036E + 256):
                 sample_desc += chr(data[j])
             if verbose:
-                print('sample desc: ', sample_desc)
+                print("sample desc: ", sample_desc)
             self.spectrum_id = sample_name
             self.sample_description = sample_desc
-            self.detector_description = ''
+            self.detector_description = ""
             self.location_description = sample_id
 
         # extract acquisition information
-        if (offset_acq + 48 + 128 + 10 + 4) >= len(data) \
-                or data[offset_acq] != 0 \
-                or data[offset_acq + 1] != 0x20:
+        if (
+            (offset_acq + 48 + 128 + 10 + 4) >= len(data)
+            or data[offset_acq] != 0
+            or data[offset_acq + 1] != 0x20
+        ):
             if verbose:
                 print(offset_acq + 48 + 128 + 10 + 4, len(data))
                 print(data[offset_acq], data[offset_acq + 1])
-            raise CnfFileParsingError('Acquisition information not found')
+            raise CnfFileParsingError("Acquisition information not found")
         else:
             offset1 = _from_little_endian(data, offset_acq + 34, 2)
             offset2 = _from_little_endian(data, offset_acq + 36, 2)
             offset_pha = offset_acq + 48 + 128
-            if chr(data[offset_pha + 0]) != 'P' \
-                    and chr(data[offset_pha + 1]) != 'H' \
-                    and chr(data[offset_pha + 2]) != 'A':
-                raise CnfFileParsingError('PHA keyword not found')
-            self.num_channels = 256 * \
-                _from_little_endian(data, offset_pha + 10, 2)
+            if (
+                chr(data[offset_pha + 0]) != "P"
+                and chr(data[offset_pha + 1]) != "H"
+                and chr(data[offset_pha + 2]) != "A"
+            ):
+                raise CnfFileParsingError("PHA keyword not found")
+            self.num_channels = 256 * _from_little_endian(data, offset_pha + 10, 2)
             if self.num_channels < 256 or self.num_channels > 16384:
                 raise CnfFileParsingError(
-                    'Unexpected number of channels: ', self.num_channels)
+                    "Unexpected number of channels: ", self.num_channels
+                )
             if verbose:
-                print('Number of channels: ', self.num_channels)
+                print("Number of channels: ", self.num_channels)
 
         # extract date and time information
         offset_date = offset_acq + 48 + offset2 + 1
         if offset_date + 24 >= len(data):
-            raise CnfFileParsingError('Problem with date offset')
+            raise CnfFileParsingError("Problem with date offset")
         self.collection_start = _convert_date(data, offset_date)
         self.realtime = _convert_time(data, offset_date + 8)
         if verbose:
-            print('realtime: ', self.realtime)
+            print("realtime: ", self.realtime)
         self.livetime = _convert_time(data, offset_date + 16)
         if verbose:
-            print('livetime: ', self.livetime)
-            print('{:%Y-%m-%d %H:%M:%S}'.format(self.collection_start))
-        self.collection_stop = self.collection_start \
-            + datetime.timedelta(seconds=self.realtime)
+            print("livetime: ", self.livetime)
+            print("{:%Y-%m-%d %H:%M:%S}".format(self.collection_start))
+        self.collection_stop = self.collection_start + datetime.timedelta(
+            seconds=self.realtime
+        )
         if verbose:
-            print('{:%Y-%m-%d %H:%M:%S}'.format(self.collection_stop))
+            print("{:%Y-%m-%d %H:%M:%S}".format(self.collection_stop))
 
         # extract energy calibration information
         offset_cal = offset_enc + 48 + 32 + offset1
         if offset_enc + 48 + 32 + offset1 >= len(data):
-            raise CnfFileParsingError('Problem with energy calibration offset')
+            raise CnfFileParsingError("Problem with energy calibration offset")
         coeff = _read_energy_calibration(data, offset_enc + 48 + 32 + offset1)
         if verbose:
-            print('calibration coefficients:', coeff)
+            print("calibration coefficients:", coeff)
         if coeff is None:
             if verbose:
-                print('Energy calibration - second try')
+                print("Energy calibration - second try")
             coeff = _read_energy_calibration(data, offset_enc + 48 + 32)
             if verbose:
-                print('calibration coefficients:', coeff)
+                print("calibration coefficients:", coeff)
         if coeff is None:
-            raise CnfFileParsingError('Energy calibration not found')
+            raise CnfFileParsingError("Energy calibration not found")
         self.cal_coeff = coeff
 
         # extract channel data
-        if offset_chan + 512 + 4 * self.num_channels > len(data) \
-                or data[offset_chan] != 5 or data[offset_chan + 1] != 0x20:
-            raise CnfFileParsingError('Channel data not found')
+        if (
+            offset_chan + 512 + 4 * self.num_channels > len(data)
+            or data[offset_chan] != 5
+            or data[offset_chan + 1] != 0x20
+        ):
+            raise CnfFileParsingError("Channel data not found")
         self.channels = np.array([], dtype=float)
         self.data = np.array([], dtype=float)
         for i in range(0, 2):
@@ -281,11 +300,13 @@ def read(self, verbose=False):
         # finished
         if self.realtime <= 0.0:
             raise CnfFileParsingError(
-                'Realtime not parsed correctly: {}'.format(self.realtime))
+                "Realtime not parsed correctly: {}".format(self.realtime)
+            )
         if self.livetime <= 0.0:
             raise CnfFileParsingError(
-                'Livetime not parsed correctly: {}'.format(self.livetime))
+                "Livetime not parsed correctly: {}".format(self.livetime)
+            )
         if self.livetime > self.realtime:
             raise CnfFileParsingError(
-                'Livetime > realtime: {} > {}'.format(
-                    self.livetime, self.realtime))
+                "Livetime > realtime: {} > {}".format(self.livetime, self.realtime)
+            )
diff --git a/becquerel/parsers/spc_file.py b/becquerel/parsers/spc_file.py
index e204f046..84016f1f 100644
--- a/becquerel/parsers/spc_file.py
+++ b/becquerel/parsers/spc_file.py
@@ -1,6 +1,5 @@
 """Read in an Ortec SPC file."""
 
-from __future__ import print_function
 import os
 import struct
 import dateutil.parser
@@ -42,130 +41,170 @@ class SpcFile(SpectrumFile):
     SPC_FORMAT_BEGINNING = [
         # Record 1
         [
-            ['INFTYP', 'H'],
-            ['FILTYP', 'H'],
-            ['3', 'H'],
-            ['4', 'H'],
-            ['ACQIRP', 'H'],
-            ['SAMDRP', 'H'],
-            ['DETDRP', 'H'],
-            ['EBRDESC', 'H'],
-            ['ANARP1', 'H'],
-            ['ANARP2', 'H'],
-            ['ANARP3', 'H'],
-            ['ANARP4', 'H'],
-            ['SRPDES', 'H'],
-            ['IEQDESC', 'H'],
-            ['GEODES', 'H'],
-            ['MPCDESC', 'H'],
-            ['CALDES', 'H'],
-            ['CALRP1', 'H'],
-            ['CALRP2', 'H'],
-            ['EFFPRP', 'H'],
-            ['ROIRP1', 'H'],
-            ['22', 'H'],
-            ['23', 'H'],
-            ['24', 'H'],
-            ['25', 'H'],
-            ['26', 'H'],
-            ['PERPTR', 'H'],
-            ['MAXRCS', 'H'],
-            ['LSTREC', 'H'],
-            ['EFFPNM', 'H'],
-            ['SPCTRP', 'H'],
-            ['SPCRCN', 'H'],
-            ['SPCCHN', 'H'],
-            ['ABSTCH', 'H'],
-            ['ACQTIM', 'f'],
-            ['ACQTI8', 'd'],
-            ['SEQNUM', 'H'],
-            ['MCANU', 'H'],
-            ['SEGNUM', 'H'],
-            ['MCADVT', 'H'],
-            ['CHNSRT', 'H'],
-            ['RLTMDT', 'f'],
-            ['LVTMDT', 'f'],
-            ['50', 'H'],
-            ['51', 'H'],
-            ['52', 'H'],
-            ['53', 'H'],
-            ['54', 'H'],
-            ['55', 'H'],
-            ['56', 'H'],
-            ['57', 'H'],
-            ['58', 'H'],
-            ['59', 'H'],
-            ['60', 'H'],
-            ['61', 'H'],
-            ['62', 'H'],
-            ['RRSFCT', 'f'],
+            ["INFTYP", "H"],
+            ["FILTYP", "H"],
+            ["3", "H"],
+            ["4", "H"],
+            ["ACQIRP", "H"],
+            ["SAMDRP", "H"],
+            ["DETDRP", "H"],
+            ["EBRDESC", "H"],
+            ["ANARP1", "H"],
+            ["ANARP2", "H"],
+            ["ANARP3", "H"],
+            ["ANARP4", "H"],
+            ["SRPDES", "H"],
+            ["IEQDESC", "H"],
+            ["GEODES", "H"],
+            ["MPCDESC", "H"],
+            ["CALDES", "H"],
+            ["CALRP1", "H"],
+            ["CALRP2", "H"],
+            ["EFFPRP", "H"],
+            ["ROIRP1", "H"],
+            ["22", "H"],
+            ["23", "H"],
+            ["24", "H"],
+            ["25", "H"],
+            ["26", "H"],
+            ["PERPTR", "H"],
+            ["MAXRCS", "H"],
+            ["LSTREC", "H"],
+            ["EFFPNM", "H"],
+            ["SPCTRP", "H"],
+            ["SPCRCN", "H"],
+            ["SPCCHN", "H"],
+            ["ABSTCH", "H"],
+            ["ACQTIM", "f"],
+            ["ACQTI8", "d"],
+            ["SEQNUM", "H"],
+            ["MCANU", "H"],
+            ["SEGNUM", "H"],
+            ["MCADVT", "H"],
+            ["CHNSRT", "H"],
+            ["RLTMDT", "f"],
+            ["LVTMDT", "f"],
+            ["50", "H"],
+            ["51", "H"],
+            ["52", "H"],
+            ["53", "H"],
+            ["54", "H"],
+            ["55", "H"],
+            ["56", "H"],
+            ["57", "H"],
+            ["58", "H"],
+            ["59", "H"],
+            ["60", "H"],
+            ["61", "H"],
+            ["62", "H"],
+            ["RRSFCT", "f"],
         ],
         # Unknown Record
-        [['Unknown Record 1', '128B'], ],
+        [
+            ["Unknown Record 1", "128B"],
+        ],
         # Acquisition Information Record
         [
-            ['Default spectrum file name', '16s'],
-            ['Date', '12s'],
-            ['Time', '10s'],
-            ['Live Time', '10s'],
-            ['Real Time', '10s'],
-            ['59--90', 'B33x'],
-            ['Start date of sample collection', '10s'],
-            ['Start time of sample collection', '8s'],
-            ['Stop date of sample collection', '10s'],
-            ['Stop time of sample collection', '8s'],
+            ["Default spectrum file name", "16s"],
+            ["Date", "12s"],
+            ["Time", "10s"],
+            ["Live Time", "10s"],
+            ["Real Time", "10s"],
+            ["59--90", "B33x"],
+            ["Start date of sample collection", "10s"],
+            ["Start time of sample collection", "8s"],
+            ["Stop date of sample collection", "10s"],
+            ["Stop time of sample collection", "8s"],
         ],
         # Sample Description Record
-        [['Sample Description', '128s'], ],
+        [
+            ["Sample Description", "128s"],
+        ],
         # Detector Description Record
-        [['Detector Description', '128s'], ],
+        [
+            ["Detector Description", "128s"],
+        ],
         # First Analysis Parameter
         [
-            ['Calibration ?', '16f'],
-            ['Testing', '64s'],
+            ["Calibration ?", "16f"],
+            ["Testing", "64s"],
         ],
         # Unknown Record
-        [['Unknown Record 2', '128B'], ],
+        [
+            ["Unknown Record 2", "128B"],
+        ],
         # Calibration Description Record
-        [['Calibration Description', '128s'], ],
+        [
+            ["Calibration Description", "128s"],
+        ],
         # Description Record 1
-        [['Location Description Record 1', 'x127s'], ],
+        [
+            ["Location Description Record 1", "x127s"],
+        ],
         # Description Record 2
-        [['Location Description Record 2', '128s'], ],
+        [
+            ["Location Description Record 2", "128s"],
+        ],
         # Unknown Record
-        [['Unknown Record 3', '128B'], ],
+        [
+            ["Unknown Record 3", "128B"],
+        ],
         # Unknown Record
-        [['Unknown Record 4', '128B'], ],
+        [
+            ["Unknown Record 4", "128B"],
+        ],
         # Unknown Record
-        [['Unknown Record 5', '128B'], ],
+        [
+            ["Unknown Record 5", "128B"],
+        ],
         # Unknown Record
-        [['Unknown Record 6', '128B'], ],
+        [
+            ["Unknown Record 6", "128B"],
+        ],
         # Empty Record
-        [['Empty Record 1', '128B'], ],
+        [
+            ["Empty Record 1", "128B"],
+        ],
         # Empty Record
-        [['Empty Record 2', '128B'], ],
+        [
+            ["Empty Record 2", "128B"],
+        ],
         # Empty Record
-        [['Empty Record 3', '128B'], ],
+        [
+            ["Empty Record 3", "128B"],
+        ],
         # Empty Record
-        [['Empty Record 4', '128B'], ],
+        [
+            ["Empty Record 4", "128B"],
+        ],
         # Empty Record
-        [['Empty Record 5', '128B'], ],
+        [
+            ["Empty Record 5", "128B"],
+        ],
         # Hardware Parameters Record 1
-        [['Hardware Parameters Record 1', '128s'], ],
+        [
+            ["Hardware Parameters Record 1", "128s"],
+        ],
         # Hardware Parameters Record 2
-        [['Hardware Parameters Record 2', '128s'], ],
+        [
+            ["Hardware Parameters Record 2", "128s"],
+        ],
     ]
 
     SPC_FORMAT_END = [
         # Unknown Record
-        [['Unknown Record 1', '128B'], ],
+        [
+            ["Unknown Record 1", "128B"],
+        ],
         # Unknown Record
-        [['Unknown Record 2', '128B'], ],
+        [
+            ["Unknown Record 2", "128B"],
+        ],
         # Calibration parameters
         [
-            ['Calibration parameter 0', 'f'],
-            ['Calibration parameter 1', 'f'],
-            ['Calibration parameter 2', 'f116x'],
+            ["Calibration parameter 0", "f"],
+            ["Calibration parameter 1", "f"],
+            ["Calibration parameter 2", "f116x"],
         ],
     ]
 
@@ -173,8 +212,8 @@ def __init__(self, filename):
         """Initialize the SPC file."""
         super(SpcFile, self).__init__(filename)
         _, ext = os.path.splitext(self.filename)
-        if ext.lower() != '.spc':
-            raise SpcFileParsingError('File extension is incorrect: ' + ext)
+        if ext.lower() != ".spc":
+            raise SpcFileParsingError("File extension is incorrect: " + ext)
         self.metadata = {}
         # read in the data
         self.read()
@@ -182,13 +221,13 @@ def __init__(self, filename):
 
     def read(self, verbose=False):
         """Read in the file."""
-        print('SpcFile: Reading file ' + self.filename)
+        print("SpcFile: Reading file " + self.filename)
         self.realtime = 0.0
         self.livetime = 0.0
         self.channels = np.array([], dtype=np.float)
         self.data = np.array([], dtype=np.float)
         self.cal_coeff = []
-        with open(self.filename, 'rb') as f:
+        with open(self.filename, "rb") as f:
             # read the file in chunks of 128 bytes
             data_records = []
             binary_data = None
@@ -198,53 +237,49 @@ def read(self, verbose=False):
                 try:
                     binary_data = f.read(128)
                 except IOError:
-                    raise SpcFileParsingError(
-                        'Unable to read 128 bytes from file')
+                    raise SpcFileParsingError("Unable to read 128 bytes from file")
                 if len(binary_data) < 128:
                     break
             if verbose:
                 print(
-                    'Done reading in SPC file.  Number of records: ',
-                    len(data_records))
+                    "Done reading in SPC file.  Number of records: ", len(data_records)
+                )
             if len(data_records) not in (279, 280):
                 raise SpcFileParsingError(
-                    'Number of data records incorrect: {}'.format(
-                        len(data_records)))
+                    "Number of data records incorrect: {}".format(len(data_records))
+                )
             # read record data
             i_rec = 0
             for record_format in self.SPC_FORMAT_BEGINNING:
-                if not (len(data_records) == 279 and
-                        record_format[0][0] == 'Location Description Record 2'):
+                if not (
+                    len(data_records) == 279
+                    and record_format[0][0] == "Location Description Record 2"
+                ):
                     binary_data = data_records[i_rec]
                     i_rec += 1
-                    fmt = '<'
+                    fmt = "<"
                     for data_format in record_format:
                         fmt += data_format[1]
                     if verbose:
-                        print('')
-                        print('')
-                        print('-' * 60)
-                        print('')
+                        print("")
+                        print("")
+                        print("-" * 60)
+                        print("")
                         print(record_format)
                         print(fmt)
-                        print('')
+                        print("")
                     data = struct.unpack(fmt, binary_data)
                     if verbose:
-                        print('')
+                        print("")
                         print(data)
-                        print('')
+                        print("")
                     for j, data_format in enumerate(record_format):
                         if isinstance(data[j], bytes):
-                            self.metadata[data_format[0]] = \
-                                data[j].decode('ascii')
+                            self.metadata[data_format[0]] = data[j].decode("ascii")
                         else:
                             self.metadata[data_format[0]] = data[j]
                         if verbose:
-                            print(
-                                data_format[0],
-                                ': ',
-                                self.metadata[
-                                    data_format[0]])
+                            print(data_format[0], ": ", self.metadata[data_format[0]])
             # read spectrum records
             # These records are the spectrum data stored as INTEGER*4
             # numbers beginning with the channel number given and going
@@ -256,7 +291,7 @@ def read(self, verbose=False):
             for j in range(256):
                 binary_data = data_records[i_rec]
                 i_rec += 1
-                N = struct.unpack('<32I', binary_data)
+                N = struct.unpack("<32I", binary_data)
                 # print(': ', N)
                 for j, N_j in enumerate(N):
                     self.channels = np.append(self.channels, i_channel)
@@ -266,89 +301,99 @@ def read(self, verbose=False):
             for record_format in self.SPC_FORMAT_END:
                 binary_data = data_records[i_rec]
                 i_rec += 1
-                fmt = '<'
+                fmt = "<"
                 for data_format in record_format:
                     fmt += data_format[1]
                 if verbose:
-                    print('')
-                    print('')
-                    print('-' * 60)
-                    print('')
+                    print("")
+                    print("")
+                    print("-" * 60)
+                    print("")
                     print(record_format)
                     print(fmt)
-                    print('')
+                    print("")
                 data = struct.unpack(fmt, binary_data)
                 if verbose:
-                    print('')
+                    print("")
                     print(data)
-                    print('')
+                    print("")
                 for j, data_format in enumerate(record_format):
                     if isinstance(data[j], bytes):
-                        self.metadata[data_format[0]] = data[j].decode('ascii')
+                        self.metadata[data_format[0]] = data[j].decode("ascii")
                     else:
                         self.metadata[data_format[0]] = data[j]
                     if verbose:
-                        print(
-                            data_format[0],
-                            ': ',
-                            self.metadata[
-                                data_format[0]])
+                        print(data_format[0], ": ", self.metadata[data_format[0]])
         # finish the parsing
-        self.sample_description = self.metadata['Sample Description']
-        self.detector_description = self.metadata['Detector Description']
+        self.sample_description = self.metadata["Sample Description"]
+        self.detector_description = self.metadata["Detector Description"]
         if verbose:
-            print(self.metadata['Start date of sample collection'])
-        self.metadata['Start date of sample collection'] = \
-            self.metadata['Start date of sample collection'][:-1]
+            print(self.metadata["Start date of sample collection"])
+        self.metadata["Start date of sample collection"] = self.metadata[
+            "Start date of sample collection"
+        ][:-1]
         if verbose:
-            print(self.metadata['Start date of sample collection'])
-            print(self.metadata['Start time of sample collection'])
+            print(self.metadata["Start date of sample collection"])
+            print(self.metadata["Start time of sample collection"])
         self.collection_start = dateutil.parser.parse(
-            self.metadata['Start date of sample collection'] + ' ' +
-            self.metadata['Start time of sample collection'])
+            self.metadata["Start date of sample collection"]
+            + " "
+            + self.metadata["Start time of sample collection"]
+        )
         if verbose:
             print(self.collection_start)
-            print(self.metadata['Stop date of sample collection'])
-        self.metadata['Stop date of sample collection'] = \
-            self.metadata['Stop date of sample collection'][:-1]
+            print(self.metadata["Stop date of sample collection"])
+        self.metadata["Stop date of sample collection"] = self.metadata[
+            "Stop date of sample collection"
+        ][:-1]
         if verbose:
-            print(self.metadata['Stop date of sample collection'])
-            print(self.metadata['Stop time of sample collection'])
+            print(self.metadata["Stop date of sample collection"])
+            print(self.metadata["Stop time of sample collection"])
         self.collection_stop = dateutil.parser.parse(
-            self.metadata['Stop date of sample collection'] + ' ' +
-            self.metadata['Stop time of sample collection'])
+            self.metadata["Stop date of sample collection"]
+            + " "
+            + self.metadata["Stop time of sample collection"]
+        )
         if verbose:
             print(self.collection_stop)
-        self.location_description = \
-            self.metadata['Location Description Record 1'][3:]
-        self.location_description = self.location_description.split(
-            '\x00\x00\x00')[0].replace('\x00', '\n')
+        self.location_description = self.metadata["Location Description Record 1"][3:]
+        self.location_description = self.location_description.split("\x00\x00\x00")[
+            0
+        ].replace("\x00", "\n")
         if len(data_records) > 279:
-            self.location_description += \
-                self.metadata['Location Description Record 2'].split(
-                    '\x00\x00\x00')[0].replace('\x00', '\n')
+            self.location_description += (
+                self.metadata["Location Description Record 2"]
+                .split("\x00\x00\x00")[0]
+                .replace("\x00", "\n")
+            )
         self.hardware_status = (
-            self.metadata['Hardware Parameters Record 1'] +
-            self.metadata['Hardware Parameters Record 2']).split(
-                '\x00\x00\x00')[0].replace('\x00', '\n')
-        self.livetime = float(self.metadata['Live Time'])
-        self.realtime = float(self.metadata['Real Time'])
+            (
+                self.metadata["Hardware Parameters Record 1"]
+                + self.metadata["Hardware Parameters Record 2"]
+            )
+            .split("\x00\x00\x00")[0]
+            .replace("\x00", "\n")
+        )
+        self.livetime = float(self.metadata["Live Time"])
+        self.realtime = float(self.metadata["Real Time"])
         if self.realtime <= 0.0:
             raise SpcFileParsingError(
-                'Realtime not parsed correctly: {}'.format(self.realtime))
+                "Realtime not parsed correctly: {}".format(self.realtime)
+            )
         if self.livetime <= 0.0:
             raise SpcFileParsingError(
-                'Livetime not parsed correctly: {}'.format(self.livetime))
+                "Livetime not parsed correctly: {}".format(self.livetime)
+            )
         if self.livetime > self.realtime:
             raise SpcFileParsingError(
-                'Livetime > realtime: {} > {}'.format(
-                    self.livetime, self.realtime))
+                "Livetime > realtime: {} > {}".format(self.livetime, self.realtime)
+            )
         self.num_channels = len(self.channels)
         try:
             self.cal_coeff = [
-                float(self.metadata['Calibration parameter 0']),
-                float(self.metadata['Calibration parameter 1']),
-                float(self.metadata['Calibration parameter 2']),
+                float(self.metadata["Calibration parameter 0"]),
+                float(self.metadata["Calibration parameter 1"]),
+                float(self.metadata["Calibration parameter 2"]),
             ]
         except KeyError:
-            raise SpcFileParsingError('Calibration parameters not found')
+            raise SpcFileParsingError("Calibration parameters not found")
diff --git a/becquerel/parsers/spe_file.py b/becquerel/parsers/spe_file.py
index 8aca968d..726ed084 100644
--- a/becquerel/parsers/spe_file.py
+++ b/becquerel/parsers/spe_file.py
@@ -1,14 +1,17 @@
 """Read in an Ortec SPE file."""
 
-from __future__ import print_function
 import datetime
 import os
 import warnings
 import dateutil.parser
 import numpy as np
-from .spectrum_file import (SpectrumFile, SpectrumFileParsingError,
-                            SpectrumFileParsingWarning)
-warnings.simplefilter('always', DeprecationWarning)
+from .spectrum_file import (
+    SpectrumFile,
+    SpectrumFileParsingError,
+    SpectrumFileParsingWarning,
+)
+
+warnings.simplefilter("always", DeprecationWarning)
 
 
 class SpeFileParsingError(SpectrumFileParsingError):
@@ -46,8 +49,8 @@ def __init__(self, filename):
         """Initialize the SPE file."""
         super(SpeFile, self).__init__(filename)
         _, ext = os.path.splitext(self.filename)
-        if ext.lower() != '.spe':
-            raise SpeFileParsingError('File extension is incorrect: ' + ext)
+        if ext.lower() != ".spe":
+            raise SpeFileParsingError("File extension is incorrect: " + ext)
         # SPE-specific members
         self.first_channel = 0
         self.ROIs = []
@@ -59,53 +62,53 @@ def __init__(self, filename):
 
     def read(self, verbose=False):
         """Read in the file."""
-        print('SpeFile: Reading file ' + self.filename)
+        print("SpeFile: Reading file " + self.filename)
         self.realtime = 0.0
         self.livetime = 0.0
         self.channels = np.array([], dtype=np.float)
         self.data = np.array([], dtype=np.float)
         self.cal_coeff = []
-        with open(self.filename, 'r') as f:
+        with open(self.filename, "r") as f:
             # read & remove newlines from end of each line
             lines = [line.strip() for line in f.readlines()]
             i = 0
             while i < len(lines):
                 # check whether we have reached a keyword and parse accordingly
-                if lines[i] == '$SPEC_ID:':
+                if lines[i] == "$SPEC_ID:":
                     i += 1
                     self.spectrum_id = lines[i]
                     if verbose:
                         print(self.spectrum_id)
-                elif lines[i] == '$SPEC_REM:':
-                    self.sample_description = ''
+                elif lines[i] == "$SPEC_REM:":
+                    self.sample_description = ""
                     i += 1
-                    while i < len(lines) and not lines[i].startswith('$'):
-                        self.sample_description += lines[i] + '\n'
+                    while i < len(lines) and not lines[i].startswith("$"):
+                        self.sample_description += lines[i] + "\n"
                         i += 1
                     self.sample_description = self.sample_description[:-1]
                     i -= 1
                     if verbose:
                         print(self.sample_description)
-                elif lines[i] == '$DATE_MEA:':
+                elif lines[i] == "$DATE_MEA:":
                     i += 1
                     self.collection_start = dateutil.parser.parse(lines[i])
                     if verbose:
                         print(self.collection_start)
-                elif lines[i] == '$MEAS_TIM:':
+                elif lines[i] == "$MEAS_TIM:":
                     i += 1
-                    self.livetime = float(lines[i].split(' ')[0])
-                    self.realtime = float(lines[i].split(' ')[1])
+                    self.livetime = float(lines[i].split(" ")[0])
+                    self.realtime = float(lines[i].split(" ")[1])
                     if verbose:
                         print(self.livetime, self.realtime)
-                elif lines[i] == '$DATA:':
+                elif lines[i] == "$DATA:":
                     i += 1
-                    self.first_channel = int(lines[i].split(' ')[0])
+                    self.first_channel = int(lines[i].split(" ")[0])
                     # I don't know why it would be nonzero
                     if self.first_channel != 0:
                         raise SpeFileParsingError(
-                            'First channel is not 0: {}'.format(
-                                self.first_channel))
-                    self.num_channels = int(lines[i].split(' ')[1])
+                            "First channel is not 0: {}".format(self.first_channel)
+                        )
+                    self.num_channels = int(lines[i].split(" ")[1])
                     if verbose:
                         print(self.first_channel, self.num_channels)
                     j = self.first_channel
@@ -114,115 +117,119 @@ def read(self, verbose=False):
                         self.data = np.append(self.data, int(lines[i]))
                         self.channels = np.append(self.channels, j)
                         j += 1
-                elif lines[i] == '$ROI:':
+                elif lines[i] == "$ROI:":
                     self.ROIs = []
                     i += 1
-                    while i < len(lines) and not lines[i].startswith('$'):
+                    while i < len(lines) and not lines[i].startswith("$"):
                         self.ROIs.append(lines[i])
                         i += 1
                     i -= 1
                     if verbose:
                         print(self.ROIs)
-                elif lines[i] == '$ENER_FIT:':
+                elif lines[i] == "$ENER_FIT:":
                     i += 1
-                    self.energy_cal.append(float(lines[i].split(' ')[0]))
-                    self.energy_cal.append(float(lines[i].split(' ')[1]))
+                    self.energy_cal.append(float(lines[i].split(" ")[0]))
+                    self.energy_cal.append(float(lines[i].split(" ")[1]))
                     if verbose:
                         print(self.energy_cal)
-                elif lines[i] == '$MCA_CAL:':
+                elif lines[i] == "$MCA_CAL:":
                     i += 1
                     n_coeff = int(lines[i])
                     i += 1
                     for j in range(n_coeff):
-                        self.cal_coeff.append(float(lines[i].split(' ')[j]))
+                        self.cal_coeff.append(float(lines[i].split(" ")[j]))
                     if verbose:
                         print(self.cal_coeff)
-                elif lines[i] == '$SHAPE_CAL:':
+                elif lines[i] == "$SHAPE_CAL:":
                     i += 1
                     n_coeff = int(lines[i])
                     i += 1
                     for j in range(n_coeff):
-                        self.shape_cal.append(float(lines[i].split(' ')[j]))
+                        self.shape_cal.append(float(lines[i].split(" ")[j]))
                     if verbose:
                         print(self.shape_cal)
-                elif lines[i].startswith('$'):
-                    key = lines[i][1:].rstrip(':')
+                elif lines[i].startswith("$"):
+                    key = lines[i][1:].rstrip(":")
                     i += 1
                     values = []
-                    while i < len(lines) and not lines[i].startswith('$'):
+                    while i < len(lines) and not lines[i].startswith("$"):
                         values.append(lines[i])
                         i += 1
                     if i < len(lines):
-                        if lines[i].startswith('$'):
+                        if lines[i].startswith("$"):
                             i -= 1
                     self.metadata[key] = values
                 else:
                     warnings.warn(
-                        'Line {} unknown: '.format(i + 1) + lines[i],
-                        SpectrumFileParsingWarning)
+                        "Line {} unknown: ".format(i + 1) + lines[i],
+                        SpectrumFileParsingWarning,
+                    )
                 i += 1
         if self.realtime <= 0.0:
             raise SpeFileParsingError(
-                'Realtime not parsed correctly: {}'.format(self.realtime))
+                "Realtime not parsed correctly: {}".format(self.realtime)
+            )
         if self.livetime <= 0.0:
             raise SpeFileParsingError(
-                'Livetime not parsed correctly: {}'.format(self.livetime))
+                "Livetime not parsed correctly: {}".format(self.livetime)
+            )
         if self.livetime > self.realtime:
             raise SpeFileParsingError(
-                'Livetime > realtime: {} > {}'.format(
-                    self.livetime, self.realtime))
-        self.collection_stop = self.collection_start + \
-            datetime.timedelta(seconds=self.realtime)
+                "Livetime > realtime: {} > {}".format(self.livetime, self.realtime)
+            )
+        self.collection_stop = self.collection_start + datetime.timedelta(
+            seconds=self.realtime
+        )
 
     def _spe_format(self):
         """Format of this spectrum for writing to file."""
-        s = ''
-        s += '$SPEC_ID:\n'
-        s += self.spectrum_id + '\n'
-        s += '$SPEC_REM:\n'
-        s += self.sample_description + '\n'
+        s = ""
+        s += "$SPEC_ID:\n"
+        s += self.spectrum_id + "\n"
+        s += "$SPEC_REM:\n"
+        s += self.sample_description + "\n"
         if self.collection_start is not None:
-            s += '$DATE_MEA:\n'
-            s += '{:%m/%d/%Y %H:%M:%S}\n'.format(self.collection_start)
-        s += '$MEAS_TIM:\n'
-        s += '{:.0f} {:.0f}\n'.format(self.livetime, self.realtime)
-        s += '$DATA:\n'
-        s += '{:.0f} {:d}\n'.format(self.first_channel, self.num_channels)
+            s += "$DATE_MEA:\n"
+            s += "{:%m/%d/%Y %H:%M:%S}\n".format(self.collection_start)
+        s += "$MEAS_TIM:\n"
+        s += "{:.0f} {:.0f}\n".format(self.livetime, self.realtime)
+        s += "$DATA:\n"
+        s += "{:.0f} {:d}\n".format(self.first_channel, self.num_channels)
         for j in range(self.num_channels):
-            s += '       {:.0f}\n'.format(self.data[j])
-        s += '$ROI:\n'
+            s += "       {:.0f}\n".format(self.data[j])
+        s += "$ROI:\n"
         for line in self.ROIs:
-            s += line + '\n'
+            s += line + "\n"
         if len(self.energy_cal) > 0:
-            s += '$ENER_FIT:\n'
-            s += '{:f} {:f}\n'.format(self.energy_cal[0], self.energy_cal[1])
+            s += "$ENER_FIT:\n"
+            s += "{:f} {:f}\n".format(self.energy_cal[0], self.energy_cal[1])
         if len(self.cal_coeff) > 0:
-            s += '$MCA_CAL:\n'
+            s += "$MCA_CAL:\n"
             n_coeff = len(self.cal_coeff)
-            s += '{:d}\n'.format(n_coeff)
-            s += '{:E}'.format(self.cal_coeff[0])
+            s += "{:d}\n".format(n_coeff)
+            s += "{:E}".format(self.cal_coeff[0])
             for j in range(1, n_coeff):
-                s += ' {:E}'.format(self.cal_coeff[j])
-            s += '\n'
+                s += " {:E}".format(self.cal_coeff[j])
+            s += "\n"
         if len(self.shape_cal) > 0:
-            s += '$SHAPE_CAL:\n'
+            s += "$SHAPE_CAL:\n"
             n_coeff = len(self.shape_cal)
-            s += '{:d}\n'.format(n_coeff)
-            s += '{:E}'.format(self.shape_cal[0])
+            s += "{:d}\n".format(n_coeff)
+            s += "{:E}".format(self.shape_cal[0])
             for j in range(1, n_coeff):
-                s += ' {:E}'.format(self.shape_cal[j])
-            s += '\n'
+                s += " {:E}".format(self.shape_cal[j])
+            s += "\n"
         if len(self.metadata.keys()) > 0:
             for key, values in self.metadata.items():
-                s += '$' + key + ':\n'
+                s += "$" + key + ":\n"
                 for val in values:
-                    s += str(val) + '\n'
+                    s += str(val) + "\n"
         return s[:-1]
 
     def write(self, filename):
         """Write back to a file."""
         _, ext = os.path.splitext(filename)
-        if ext.lower() != '.spe':
-            raise SpeFileWritingError('File extension is incorrect: ' + ext)
-        with open(filename, 'w') as outfile:
+        if ext.lower() != ".spe":
+            raise SpeFileWritingError("File extension is incorrect: " + ext)
+        with open(filename, "w") as outfile:
             print(self._spe_format(), file=outfile)
diff --git a/becquerel/parsers/spectrum_file.py b/becquerel/parsers/spectrum_file.py
index cdd2cc7f..65ef6276 100644
--- a/becquerel/parsers/spectrum_file.py
+++ b/becquerel/parsers/spectrum_file.py
@@ -1,11 +1,11 @@
 """Base class for spectrum file parsers."""
 
-from __future__ import print_function
 import os
 import warnings
 import numpy as np
 from scipy.interpolate import interp1d
-warnings.simplefilter('always', DeprecationWarning)
+
+warnings.simplefilter("always", DeprecationWarning)
 
 
 class SpectrumFileParsingWarning(UserWarning):
@@ -41,11 +41,11 @@ def __init__(self, filename):
         self.filename = filename
         assert os.path.exists(self.filename)
         # fields to read from file
-        self.spectrum_id = ''
-        self.sample_description = ''
-        self.detector_description = ''
-        self.location_description = ''
-        self.hardware_status = ''
+        self.spectrum_id = ""
+        self.sample_description = ""
+        self.detector_description = ""
+        self.location_description = ""
+        self.hardware_status = ""
         self.collection_start = None
         self.collection_stop = None
         self.realtime = 0.0
@@ -66,46 +66,50 @@ def __str__(self):
 
         print_channels = False
 
-        s = ''
-        s += 'Filename:              {:s}\n'.format(self.filename)
-        s += 'Spectrum ID:           {:s}\n'.format(self.spectrum_id)
-        s += 'Sample description:    {:s}\n'.format(self.sample_description)
-        s += 'Detector description:  {:s}\n'.format(self.detector_description)
-        s += 'Location Description:  {:s}\n'.format(self.location_description)
-        s += 'Hardware Status:       {:s}\n'.format(self.hardware_status)
+        s = ""
+        s += "Filename:              {:s}\n".format(self.filename)
+        s += "Spectrum ID:           {:s}\n".format(self.spectrum_id)
+        s += "Sample description:    {:s}\n".format(self.sample_description)
+        s += "Detector description:  {:s}\n".format(self.detector_description)
+        s += "Location Description:  {:s}\n".format(self.location_description)
+        s += "Hardware Status:       {:s}\n".format(self.hardware_status)
         if self.collection_start is not None:
-            s += 'Collection Start:      {:%Y-%m-%d %H:%M:%S}\n'.format(
-                self.collection_start)
+            s += "Collection Start:      {:%Y-%m-%d %H:%M:%S}\n".format(
+                self.collection_start
+            )
         else:
-            s += 'Collection Start:      None\n'
+            s += "Collection Start:      None\n"
         if self.collection_stop is not None:
-            s += 'Collection Stop:       {:%Y-%m-%d %H:%M:%S}\n'.format(
-                self.collection_stop)
+            s += "Collection Stop:       {:%Y-%m-%d %H:%M:%S}\n".format(
+                self.collection_stop
+            )
         else:
-            s += 'Collection Stop:       None\n'
-        s += 'Livetime:              {:.2f} sec\n'.format(self.livetime)
-        s += 'Realtime:              {:.2f} sec\n'.format(self.realtime)
+            s += "Collection Stop:       None\n"
+        s += "Livetime:              {:.2f} sec\n".format(self.livetime)
+        s += "Realtime:              {:.2f} sec\n".format(self.realtime)
         if len(self.metadata.keys()) > 0:
-            s += 'Metadata:\n'
+            s += "Metadata:\n"
             for key, value in self.metadata.items():
-                s += '    {} : {}\n'.format(key, value)
-        s += 'Number of channels:    {:d}\n'.format(self.num_channels)
+                s += "    {} : {}\n".format(key, value)
+        s += "Number of channels:    {:d}\n".format(self.num_channels)
         if len(self.cal_coeff) > 0:
-            s += 'Calibration coeffs:    '
-            s += ' '.join(['{:E}'.format(x) for x in self.cal_coeff])
-            s += '\n'
-        s += 'Data:                  \n'
+            s += "Calibration coeffs:    "
+            s += " ".join(["{:E}".format(x) for x in self.cal_coeff])
+            s += "\n"
+        s += "Data:                  \n"
         if print_channels:
             for ch, dt in zip(self.channels, self.data):
-                s += '    {:5.0f}    {:5.0f}\n'.format(ch, dt)
+                s += "    {:5.0f}    {:5.0f}\n".format(ch, dt)
         else:
-            s += '    [length {}]\n'.format(len(self.data))
+            s += "    [length {}]\n".format(len(self.data))
         return s
 
     @property
     def energy_bin_edges(self):
-        warnings.warn('The use of energy_bin_edges is deprecated, ' +
-                      'use bin_edges_kev instead', DeprecationWarning)
+        warnings.warn(
+            "The use of energy_bin_edges is deprecated, use bin_edges_kev instead",
+            DeprecationWarning,
+        )
         return self.bin_edges_kev
 
     @property
@@ -115,11 +119,11 @@ def energy_bin_widths(self):
 
     def read(self, verbose=False):
         """Read in the file."""
-        raise NotImplementedError('read method not implemented')
+        raise NotImplementedError("read method not implemented")
 
     def write(self, filename):
         """Write back to a file."""
-        raise NotImplementedError('write method not implemented')
+        raise NotImplementedError("write method not implemented")
 
     def apply_calibration(self):
         """Calculate energies corresponding to channels."""
@@ -131,9 +135,10 @@ def apply_calibration(self):
         # check that calibration makes sense, remove calibration if not
         if np.any(np.diff(self.energies) <= 0):
             warnings.warn(
-                'Spectrum will be initated without an energy calibration;' +
-                'invalid calibration, energies not monotonically increasing.',
-                SpectrumFileParsingWarning)
+                "Spectrum will be initated without an energy calibration;"
+                + "invalid calibration, energies not monotonically increasing.",
+                SpectrumFileParsingWarning,
+            )
             self.bin_edges_kev = None
 
     def channel_to_energy(self, channel):
diff --git a/becquerel/tools/__init__.py b/becquerel/tools/__init__.py
index 39704df0..d159c3ec 100644
--- a/becquerel/tools/__init__.py
+++ b/becquerel/tools/__init__.py
@@ -1,33 +1,81 @@
 """Tools for radiation spectral analysis."""
 
 from . import xcom, nndc, materials, element, isotope, isotope_qty
-from .element import (Element, ElementError, ElementZError,
-                      ElementSymbolError, ElementNameError)
+from .element import (
+    Element,
+    ElementError,
+    ElementZError,
+    ElementSymbolError,
+    ElementNameError,
+)
 from .isotope import Isotope, IsotopeError
-from .isotope_qty import (IsotopeQuantity, IsotopeQuantityError,
-                          NeutronIrradiation, NeutronIrradiationError,
-                          UCI_TO_BQ, N_AV)
-from .materials import (fetch_element_data, fetch_compound_data,
-                        NISTMaterialsError, NISTMaterialsRequestError)
-from .nndc import (fetch_wallet_card, fetch_decay_radiation,
-                   NNDCError, NoDataFound, NNDCInputError, NNDCRequestError)
-from .xcom import (fetch_xcom_data,
-                   XCOMError, XCOMInputError, XCOMRequestError,
-                   MIXTURE_AIR_DRY, MIXTURE_SEAWATER, MIXTURE_PORTLAND_CEMENT)
+from .isotope_qty import (
+    IsotopeQuantity,
+    IsotopeQuantityError,
+    NeutronIrradiation,
+    NeutronIrradiationError,
+    UCI_TO_BQ,
+    N_AV,
+)
+from .materials import (
+    fetch_element_data,
+    fetch_compound_data,
+    NISTMaterialsError,
+    NISTMaterialsRequestError,
+)
+from .nndc import (
+    fetch_wallet_card,
+    fetch_decay_radiation,
+    NNDCError,
+    NoDataFound,
+    NNDCInputError,
+    NNDCRequestError,
+)
+from .xcom import (
+    fetch_xcom_data,
+    XCOMError,
+    XCOMInputError,
+    XCOMRequestError,
+    MIXTURE_AIR_DRY,
+    MIXTURE_SEAWATER,
+    MIXTURE_PORTLAND_CEMENT,
+)
 
 __all__ = [
-    'xcom', 'nndc', 'materials', 'element', 'isotope', 'isotope_qty',
-    'Element', 'ElementError', 'ElementZError',
-    'ElementSymbolError', 'ElementNameError',
-    'Isotope', 'IsotopeError',
-    'IsotopeQuantity', 'IsotopeQuantityError',
-    'NeutronIrradiation', 'NeutronIrradiationError',
-    'UCI_TO_BQ', 'N_AV',
-    'fetch_element_data', 'fetch_compound_data',
-    'NISTMaterialsError', 'NISTMaterialsRequestError',
-    'fetch_wallet_card', 'fetch_decay_radiation',
-    'NNDCError', 'NoDataFound', 'NNDCInputError', 'NNDCRequestError',
-    'fetch_xcom_data',
-    'XCOMError', 'XCOMInputError', 'XCOMRequestError',
-    'MIXTURE_AIR_DRY', 'MIXTURE_SEAWATER', 'MIXTURE_PORTLAND_CEMENT',
+    "xcom",
+    "nndc",
+    "materials",
+    "element",
+    "isotope",
+    "isotope_qty",
+    "Element",
+    "ElementError",
+    "ElementZError",
+    "ElementSymbolError",
+    "ElementNameError",
+    "Isotope",
+    "IsotopeError",
+    "IsotopeQuantity",
+    "IsotopeQuantityError",
+    "NeutronIrradiation",
+    "NeutronIrradiationError",
+    "UCI_TO_BQ",
+    "N_AV",
+    "fetch_element_data",
+    "fetch_compound_data",
+    "NISTMaterialsError",
+    "NISTMaterialsRequestError",
+    "fetch_wallet_card",
+    "fetch_decay_radiation",
+    "NNDCError",
+    "NoDataFound",
+    "NNDCInputError",
+    "NNDCRequestError",
+    "fetch_xcom_data",
+    "XCOMError",
+    "XCOMInputError",
+    "XCOMRequestError",
+    "MIXTURE_AIR_DRY",
+    "MIXTURE_SEAWATER",
+    "MIXTURE_PORTLAND_CEMENT",
 ]
diff --git a/becquerel/tools/df_cache.py b/becquerel/tools/df_cache.py
index 3afc9884..e9a974d9 100644
--- a/becquerel/tools/df_cache.py
+++ b/becquerel/tools/df_cache.py
@@ -1,6 +1,5 @@
 """A simple class for caching a pandas DataFrame."""
 
-from __future__ import print_function
 import os
 import pandas as pd
 
@@ -33,7 +32,7 @@ class DataFrameCache(object):
       load: load the DataFrame, from file if available or from fetch()
     """
 
-    name = 'base'
+    name = "base"
     path = None
 
     def __init__(self):
@@ -46,8 +45,7 @@ def __init__(self):
         if self.path is None:
             self.path = os.path.split(__file__)[0]
         self.check_path()
-        self.filename = os.path.join(
-            self.path, '__df_cache__' + self.name + '.csv')
+        self.filename = os.path.join(self.path, "__df_cache__" + self.name + ".csv")
         self.df = None
         self.loaded = False
 
@@ -59,10 +57,9 @@ def check_path(self):
         """
 
         if not os.path.exists(self.path):
-            raise CacheError('Cache path does not exist: {}'.format(self.path))
+            raise CacheError(f"Cache path does not exist: {self.path}")
         if not os.path.isdir(self.path):
-            raise CacheError(
-                'Cache path is not a directory: {}'.format(self.path))
+            raise CacheError(f"Cache path is not a directory: {self.path}")
 
     def check_file(self):
         """Test that the file exists.
@@ -72,11 +69,9 @@ def check_file(self):
         """
 
         if not os.path.exists(self.filename):
-            raise CacheError(
-                'Cache filename does not exist: {}'.format(self.filename))
+            raise CacheError(f"Cache filename does not exist: {self.filename}")
         if not os.path.isfile(self.filename):
-            raise CacheError(
-                'Cache filename is not a file: {}'.format(self.filename))
+            raise CacheError(f"Cache filename is not a file: {self.filename}")
 
     def write_file(self):
         """Write the DataFrame to the cache file.
@@ -87,12 +82,11 @@ def write_file(self):
 
         self.check_path()
         if not self.loaded:
-            raise CacheError('Cache has not been fetched or loaded')
+            raise CacheError("Cache has not been fetched or loaded")
         try:
-            self.df.to_csv(self.filename, float_format='%.12f')
+            self.df.to_csv(self.filename, float_format="%.12f")
         except:
-            raise CacheError(
-                'Problem writing cache to file {}'.format(self.filename))
+            raise CacheError(f"Problem writing cache to file {self.filename}")
         self.check_file()
 
     def read_file(self):
@@ -106,8 +100,7 @@ def read_file(self):
         try:
             self.df = pd.read_csv(self.filename)
         except:
-            raise CacheError(
-                'Problem reading cache from file {}'.format(self.filename))
+            raise CacheError(f"Problem reading cache from file {self.filename}")
         self.loaded = True
 
     def delete_file(self):
@@ -121,15 +114,13 @@ def delete_file(self):
         try:
             os.remove(self.filename)
         except:
-            raise CacheError(
-                'Problem deleting cache file {}'.format(self.filename))
+            raise CacheError(f"Problem deleting cache file {self.filename}")
         try:
             self.check_file()
         except CacheError:
             pass  # this should be raised
         else:
-            raise CacheError(
-                'Cache file was not deleted: {}'.format(self.filename))
+            raise CacheError(f"Cache file was not deleted: {self.filename}")
 
     def fetch(self):
         """Fetch the DataFrame to be cached.
@@ -137,7 +128,7 @@ def fetch(self):
         This method must be implemented in child classes.
         """
 
-        raise NotImplementedError('Must implement fetch method')
+        raise NotImplementedError("Must implement fetch method")
 
     def load(self):
         """Read or download the cached DataFrame.
@@ -154,7 +145,7 @@ def load(self):
             try:
                 self.fetch()
             except CacheError:
-                raise CacheError('Cannot read or download DataFrame')
+                raise CacheError("Cannot read or download DataFrame")
             self.write_file()
             self.read_file()
         self.loaded = True
diff --git a/becquerel/tools/element.py b/becquerel/tools/element.py
index 5cca7587..4e21874f 100644
--- a/becquerel/tools/element.py
+++ b/becquerel/tools/element.py
@@ -1,130 +1,128 @@
 """Element symbols, names, Z values, and masses."""
 
-from __future__ import print_function
-
 
 # Element data (Z, symbol, name, and relative atomic mass) are from Wikipedia:
 # https://en.wikipedia.org/wiki/List_of_chemical_elements
 
 _Z_SYMBOL_NAME_MASS = (
-    (1, 'H', 'Hydrogen', 1.01),
-    (2, 'He', 'Helium', 4.00),
-    (3, 'Li', 'Lithium', 6.94),
-    (4, 'Be', 'Beryllium', 9.01),
-    (5, 'B', 'Boron', 10.81),
-    (6, 'C', 'Carbon', 12.01),
-    (7, 'N', 'Nitrogen', 14.01),
-    (8, 'O', 'Oxygen', 16.00),
-    (9, 'F', 'Fluorine', 19.00),
-    (10, 'Ne', 'Neon', 20.18),
-    (11, 'Na', 'Sodium', 22.99),
-    (12, 'Mg', 'Magnesium', 24.30),
-    (13, 'Al', 'Aluminum', 26.98),
-    (14, 'Si', 'Silicon', 28.09),
-    (15, 'P', 'Phosphorus', 30.97),
-    (16, 'S', 'Sulfur', 32.06),
-    (17, 'Cl', 'Chlorine', 35.45),
-    (18, 'Ar', 'Argon', 39.95),
-    (19, 'K', 'Potassium', 39.10),
-    (20, 'Ca', 'Calcium', 40.08),
-    (21, 'Sc', 'Scandium', 44.96),
-    (22, 'Ti', 'Titanium', 47.87),
-    (23, 'V', 'Vanadium', 50.94),
-    (24, 'Cr', 'Chromium', 52.00),
-    (25, 'Mn', 'Manganese', 54.94),
-    (26, 'Fe', 'Iron', 55.84),
-    (27, 'Co', 'Cobalt', 58.93),
-    (28, 'Ni', 'Nickel', 58.69),
-    (29, 'Cu', 'Copper', 63.55),
-    (30, 'Zn', 'Zinc', 65.38),
-    (31, 'Ga', 'Gallium', 69.72),
-    (32, 'Ge', 'Germanium', 72.63),
-    (33, 'As', 'Arsenic', 74.92),
-    (34, 'Se', 'Selenium', 78.97),
-    (35, 'Br', 'Bromine', 79.90),
-    (36, 'Kr', 'Krypton', 83.80),
-    (37, 'Rb', 'Rubidium', 85.47),
-    (38, 'Sr', 'Strontium', 87.62),
-    (39, 'Y', 'Yttrium', 88.91),
-    (40, 'Zr', 'Zirconium', 91.22),
-    (41, 'Nb', 'Niobium', 92.91),
-    (42, 'Mo', 'Molybdenum', 95.95),
-    (43, 'Tc', 'Technetium', 98),
-    (44, 'Ru', 'Ruthenium', 101.07),
-    (45, 'Rh', 'Rhodium', 102.91),
-    (46, 'Pd', 'Palladium', 106.42),
-    (47, 'Ag', 'Silver', 107.87),
-    (48, 'Cd', 'Cadmium', 112.41),
-    (49, 'In', 'Indium', 114.82),
-    (50, 'Sn', 'Tin', 118.71),
-    (51, 'Sb', 'Antimony', 121.76),
-    (52, 'Te', 'Tellurium', 127.60),
-    (53, 'I', 'Iodine', 126.90),
-    (54, 'Xe', 'Xenon', 131.29),
-    (55, 'Cs', 'Cesium', 132.91),
-    (56, 'Ba', 'Barium', 137.33),
-    (57, 'La', 'Lanthanum', 138.91),
-    (58, 'Ce', 'Cerium', 140.12),
-    (59, 'Pr', 'Praseodymium', 140.91),
-    (60, 'Nd', 'Neodymium', 144.24),
-    (61, 'Pm', 'Promethium', 145),
-    (62, 'Sm', 'Samarium', 150.36),
-    (63, 'Eu', 'Europium', 151.96),
-    (64, 'Gd', 'Gadolinium', 157.25),
-    (65, 'Tb', 'Terbium', 158.93),
-    (66, 'Dy', 'Dysprosium', 162.50),
-    (67, 'Ho', 'Holmium', 164.93),
-    (68, 'Er', 'Erbium', 167.26),
-    (69, 'Tm', 'Thulium', 168.93),
-    (70, 'Yb', 'Ytterbium', 173.04),
-    (71, 'Lu', 'Lutetium', 174.97),
-    (72, 'Hf', 'Hafnium', 178.49),
-    (73, 'Ta', 'Tantalum', 180.95),
-    (74, 'W', 'Tungsten', 183.84),
-    (75, 'Re', 'Rhenium', 186.21),
-    (76, 'Os', 'Osmium', 190.23),
-    (77, 'Ir', 'Iridium', 192.22),
-    (78, 'Pt', 'Platinum', 195.08),
-    (79, 'Au', 'Gold', 196.97),
-    (80, 'Hg', 'Mercury', 200.59),
-    (81, 'Tl', 'Thallium', 204.38),
-    (82, 'Pb', 'Lead', 207.20),
-    (83, 'Bi', 'Bismuth', 208.98),
-    (84, 'Po', 'Polonium', 209),
-    (85, 'At', 'Astatine', 210),
-    (86, 'Rn', 'Radon', 222),
-    (87, 'Fr', 'Francium', 223),
-    (88, 'Ra', 'Radium', 226),
-    (89, 'Ac', 'Actinium', 227),
-    (90, 'Th', 'Thorium', 232.04),
-    (91, 'Pa', 'Protactinium', 231.04),
-    (92, 'U', 'Uranium', 238.03),
-    (93, 'Np', 'Neptunium', 237),
-    (94, 'Pu', 'Plutonium', 244),
-    (95, 'Am', 'Americium', 243),
-    (96, 'Cm', 'Curium', 247),
-    (97, 'Bk', 'Berkelium', 247),
-    (98, 'Cf', 'Californium', 251),
-    (99, 'Es', 'Einsteinium', 252),
-    (100, 'Fm', 'Fermium', 257),
-    (101, 'Md', 'Mendelevium', 258),
-    (102, 'No', 'Nobelium', 259),
-    (103, 'Lr', 'Lawrencium', 266),
-    (104, 'Rf', 'Rutherfordium', 267),
-    (105, 'Db', 'Dubnium', 268),
-    (106, 'Sg', 'Seaborgium', 269),
-    (107, 'Bh', 'Bohrium', 270),
-    (108, 'Hs', 'Hassium', 269),
-    (109, 'Mt', 'Meitnerium', 278),
-    (110, 'Ds', 'Darmstadtium', 281),
-    (111, 'Rg', 'Roentgenium', 282),
-    (112, 'Cn', 'Copernicium', 285),
-    (113, 'Nh', 'Nihonium', 286),
-    (114, 'Fl', 'Flerovium', 289),
-    (115, 'Mc', 'Moscovium', 289),
-    (116, 'Lv', 'Livermorium', 293),
-    (117, 'Ts', 'Tennessine', 294),
-    (118, 'Og', 'Oganesson', 294),
+    (1, "H", "Hydrogen", 1.01),
+    (2, "He", "Helium", 4.00),
+    (3, "Li", "Lithium", 6.94),
+    (4, "Be", "Beryllium", 9.01),
+    (5, "B", "Boron", 10.81),
+    (6, "C", "Carbon", 12.01),
+    (7, "N", "Nitrogen", 14.01),
+    (8, "O", "Oxygen", 16.00),
+    (9, "F", "Fluorine", 19.00),
+    (10, "Ne", "Neon", 20.18),
+    (11, "Na", "Sodium", 22.99),
+    (12, "Mg", "Magnesium", 24.30),
+    (13, "Al", "Aluminum", 26.98),
+    (14, "Si", "Silicon", 28.09),
+    (15, "P", "Phosphorus", 30.97),
+    (16, "S", "Sulfur", 32.06),
+    (17, "Cl", "Chlorine", 35.45),
+    (18, "Ar", "Argon", 39.95),
+    (19, "K", "Potassium", 39.10),
+    (20, "Ca", "Calcium", 40.08),
+    (21, "Sc", "Scandium", 44.96),
+    (22, "Ti", "Titanium", 47.87),
+    (23, "V", "Vanadium", 50.94),
+    (24, "Cr", "Chromium", 52.00),
+    (25, "Mn", "Manganese", 54.94),
+    (26, "Fe", "Iron", 55.84),
+    (27, "Co", "Cobalt", 58.93),
+    (28, "Ni", "Nickel", 58.69),
+    (29, "Cu", "Copper", 63.55),
+    (30, "Zn", "Zinc", 65.38),
+    (31, "Ga", "Gallium", 69.72),
+    (32, "Ge", "Germanium", 72.63),
+    (33, "As", "Arsenic", 74.92),
+    (34, "Se", "Selenium", 78.97),
+    (35, "Br", "Bromine", 79.90),
+    (36, "Kr", "Krypton", 83.80),
+    (37, "Rb", "Rubidium", 85.47),
+    (38, "Sr", "Strontium", 87.62),
+    (39, "Y", "Yttrium", 88.91),
+    (40, "Zr", "Zirconium", 91.22),
+    (41, "Nb", "Niobium", 92.91),
+    (42, "Mo", "Molybdenum", 95.95),
+    (43, "Tc", "Technetium", 98),
+    (44, "Ru", "Ruthenium", 101.07),
+    (45, "Rh", "Rhodium", 102.91),
+    (46, "Pd", "Palladium", 106.42),
+    (47, "Ag", "Silver", 107.87),
+    (48, "Cd", "Cadmium", 112.41),
+    (49, "In", "Indium", 114.82),
+    (50, "Sn", "Tin", 118.71),
+    (51, "Sb", "Antimony", 121.76),
+    (52, "Te", "Tellurium", 127.60),
+    (53, "I", "Iodine", 126.90),
+    (54, "Xe", "Xenon", 131.29),
+    (55, "Cs", "Cesium", 132.91),
+    (56, "Ba", "Barium", 137.33),
+    (57, "La", "Lanthanum", 138.91),
+    (58, "Ce", "Cerium", 140.12),
+    (59, "Pr", "Praseodymium", 140.91),
+    (60, "Nd", "Neodymium", 144.24),
+    (61, "Pm", "Promethium", 145),
+    (62, "Sm", "Samarium", 150.36),
+    (63, "Eu", "Europium", 151.96),
+    (64, "Gd", "Gadolinium", 157.25),
+    (65, "Tb", "Terbium", 158.93),
+    (66, "Dy", "Dysprosium", 162.50),
+    (67, "Ho", "Holmium", 164.93),
+    (68, "Er", "Erbium", 167.26),
+    (69, "Tm", "Thulium", 168.93),
+    (70, "Yb", "Ytterbium", 173.04),
+    (71, "Lu", "Lutetium", 174.97),
+    (72, "Hf", "Hafnium", 178.49),
+    (73, "Ta", "Tantalum", 180.95),
+    (74, "W", "Tungsten", 183.84),
+    (75, "Re", "Rhenium", 186.21),
+    (76, "Os", "Osmium", 190.23),
+    (77, "Ir", "Iridium", 192.22),
+    (78, "Pt", "Platinum", 195.08),
+    (79, "Au", "Gold", 196.97),
+    (80, "Hg", "Mercury", 200.59),
+    (81, "Tl", "Thallium", 204.38),
+    (82, "Pb", "Lead", 207.20),
+    (83, "Bi", "Bismuth", 208.98),
+    (84, "Po", "Polonium", 209),
+    (85, "At", "Astatine", 210),
+    (86, "Rn", "Radon", 222),
+    (87, "Fr", "Francium", 223),
+    (88, "Ra", "Radium", 226),
+    (89, "Ac", "Actinium", 227),
+    (90, "Th", "Thorium", 232.04),
+    (91, "Pa", "Protactinium", 231.04),
+    (92, "U", "Uranium", 238.03),
+    (93, "Np", "Neptunium", 237),
+    (94, "Pu", "Plutonium", 244),
+    (95, "Am", "Americium", 243),
+    (96, "Cm", "Curium", 247),
+    (97, "Bk", "Berkelium", 247),
+    (98, "Cf", "Californium", 251),
+    (99, "Es", "Einsteinium", 252),
+    (100, "Fm", "Fermium", 257),
+    (101, "Md", "Mendelevium", 258),
+    (102, "No", "Nobelium", 259),
+    (103, "Lr", "Lawrencium", 266),
+    (104, "Rf", "Rutherfordium", 267),
+    (105, "Db", "Dubnium", 268),
+    (106, "Sg", "Seaborgium", 269),
+    (107, "Bh", "Bohrium", 270),
+    (108, "Hs", "Hassium", 269),
+    (109, "Mt", "Meitnerium", 278),
+    (110, "Ds", "Darmstadtium", 281),
+    (111, "Rg", "Roentgenium", 282),
+    (112, "Cn", "Copernicium", 285),
+    (113, "Nh", "Nihonium", 286),
+    (114, "Fl", "Flerovium", 289),
+    (115, "Mc", "Moscovium", 289),
+    (116, "Lv", "Livermorium", 293),
+    (117, "Ts", "Tennessine", 294),
+    (118, "Og", "Oganesson", 294),
 )
 
 
@@ -241,11 +239,11 @@ def validated_name(nm):
     except AttributeError:
         raise ElementNameError('Element name "{}" invalid'.format(nm))
     # special case: Alumin[i]um
-    if nm.lower() == 'aluminium':
-        nm = 'Aluminum'
+    if nm.lower() == "aluminium":
+        nm = "Aluminum"
     # special case: C[a]esium
-    if nm.lower() == 'caesium':
-        nm = 'Cesium'
+    if nm.lower() == "caesium":
+        nm = "Cesium"
     if nm.lower() not in NAMES_LOWER:
         raise ElementNameError('Element name "{}" not found'.format(nm))
     return _NAME_FROM_NAME_LOWER[nm.lower()]
@@ -271,7 +269,7 @@ def element_z(sym_or_name):
     try:
         return _Z_FROM_NAME[validated_name(sym_or_name)]
     except ElementNameError:
-        raise ElementZError('Must supply either the element symbol or name')
+        raise ElementZError("Must supply either the element symbol or name")
 
 
 def element_symbol(name_or_z):
@@ -294,7 +292,7 @@ def element_symbol(name_or_z):
     try:
         return _SYMBOL_FROM_NAME[validated_name(name_or_z)]
     except ElementNameError:
-        raise ElementSymbolError('Must supply either the Z or element name')
+        raise ElementSymbolError("Must supply either the Z or element name")
 
 
 def element_name(sym_or_z):
@@ -317,7 +315,7 @@ def element_name(sym_or_z):
     try:
         return _NAME_FROM_Z[validated_z(sym_or_z)]
     except ElementZError:
-        raise ElementNameError('Must supply either the element symbol or Z')
+        raise ElementNameError("Must supply either the element symbol or Z")
 
 
 class Element(object):
@@ -335,8 +333,6 @@ class Element(object):
       atomic_mass (read-only): a float giving the atomic mass in amu
     """
 
-    # pylint: disable=too-few-public-methods
-
     def __init__(self, arg):
         """Instantiate by providing a name, symbol, or Z value.
 
@@ -360,8 +356,7 @@ def __init__(self, arg):
                 try:
                     self._init_z(arg)
                 except ElementZError:
-                    raise ElementError(
-                        'Could not instantiate Element: {}'.format(arg))
+                    raise ElementError("Could not instantiate Element: {}".format(arg))
         self.atomic_mass = _MASS_FROM_SYMBOL[self.symbol]
 
     def _init_sym(self, arg):
@@ -384,7 +379,7 @@ def _init_z(self, arg):
 
     def __str__(self):
         """Define behavior of str() on this class."""
-        return '{}'.format(self)
+        return "{}".format(self)
 
     def __format__(self, formatstr):
         """Define behavior of string's format method.
@@ -397,17 +392,19 @@ def __format__(self, formatstr):
 
         str0 = str(formatstr)
         if len(str0) == 0:
-            str0 = '%n(%s) Z=%z'
-        str0 = str0.replace('%s', self.symbol)
-        str0 = str0.replace('%n', self.name)
-        str0 = str0.replace('%z', '{}'.format(self.Z))
+            str0 = "%n(%s) Z=%z"
+        str0 = str0.replace("%s", self.symbol)
+        str0 = str0.replace("%n", self.name)
+        str0 = str0.replace("%z", "{}".format(self.Z))
         return str0
 
     def __eq__(self, other):
         """Define equality of two elements."""
         try:
             return (
-                self.name == other.name and self.symbol == other.symbol and
-                self.Z == other.Z)
+                self.name == other.name
+                and self.symbol == other.symbol
+                and self.Z == other.Z
+            )
         except:
-            raise ElementError('Cannot determine equality')
+            raise ElementError("Cannot determine equality")
diff --git a/becquerel/tools/isotope.py b/becquerel/tools/isotope.py
index 07446329..e41ab2c7 100644
--- a/becquerel/tools/isotope.py
+++ b/becquerel/tools/isotope.py
@@ -1,6 +1,5 @@
 """Nuclear isotopes and isomers."""
 
-from __future__ import print_function
 from future.builtins import super
 import numpy as np
 import uncertainties
@@ -10,9 +9,6 @@
 N_AV = 6.022141e23  # mol^-1
 
 
-# pylint: disable=no-self-use
-
-
 class IsotopeError(element.ElementError):
     """Problem with isotope properties."""
 
@@ -40,13 +36,12 @@ def _split_element_mass(arg):
     arg = str(arg)
     element_id = None
     mass_isomer = None
-    if '-' in arg:
+    if "-" in arg:
         # parse string of the form "EE-AAA[m[M]]" or "AAA[m[M]]-EE"
         # (i.e., those with a hyphen)
-        tokens = arg.split('-')
+        tokens = arg.split("-")
         if len(tokens) != 2:
-            raise IsotopeError(
-                'Too many hyphens in isotope string: {}'.format(arg))
+            raise IsotopeError("Too many hyphens in isotope string: {}".format(arg))
         if tokens[0].isalpha() and tokens[1][0].isdigit():
             element_id = tokens[0]
             mass_isomer = tokens[1]
@@ -55,7 +50,8 @@ def _split_element_mass(arg):
             mass_isomer = tokens[0]
         else:
             raise IsotopeError(
-                'Could not find mass number for isotope: {}'.format(tokens))
+                "Could not find mass number for isotope: {}".format(tokens)
+            )
     else:
         # parse string of the form "EEAAA[m[M]]" or "AAA[m[M]]EE"
         # (i.e., those without a hyphen)
@@ -80,11 +76,10 @@ def _split_element_mass(arg):
             element_ids.append(p_element)
             mass_isomers.append(p_mass)
         if len(element_ids) == 0:
-            raise IsotopeError(
-                'Could not find element for isotope: {}'.format(arg))
+            raise IsotopeError("Could not find element for isotope: {}".format(arg))
         # if multiple element IDs were found, choose the longest
-        element_id = ''
-        mass_isomer = ''
+        element_id = ""
+        mass_isomer = ""
         for elem, mass in zip(element_ids, mass_isomers):
             if len(elem) > len(element_id):
                 element_id = elem
@@ -93,8 +88,7 @@ def _split_element_mass(arg):
     try:
         element.Element(element_id)
     except element.ElementError:
-        raise IsotopeError(
-            'Element name or symbol is invalid: {}'.format(element_id))
+        raise IsotopeError("Element name or symbol is invalid: {}".format(element_id))
     return element_id, mass_isomer
 
 
@@ -117,31 +111,29 @@ def _split_mass_isomer(arg):
 
     arg = str(arg)
     aa = 0
-    mm = ''
-    if 'm' in arg.lower():
-        tokens = arg.lower().split('m')
+    mm = ""
+    if "m" in arg.lower():
+        tokens = arg.lower().split("m")
         if len(tokens) != 2:
-            raise IsotopeError(
-                'Too many ms in mass number: {} {}'.format(arg, tokens))
+            raise IsotopeError("Too many ms in mass number: {} {}".format(arg, tokens))
         try:
             aa = int(tokens[0])
         except ValueError:
             raise IsotopeError(
-                'Mass number cannot be converted to int: {} {}'.format(
-                    tokens[0], arg))
-        mm = 'm'
+                "Mass number cannot be converted to int: {} {}".format(tokens[0], arg)
+            )
+        mm = "m"
         if len(tokens[1]) > 0:
             if not tokens[1].isdigit():
                 raise IsotopeError(
-                    'Metastable level must be a number: {} {}'.format(
-                        tokens[1], arg))
+                    "Metastable level must be a number: {} {}".format(tokens[1], arg)
+                )
             mm += tokens[1]
     else:
         try:
             aa = int(arg)
         except ValueError:
-            raise IsotopeError(
-                'Mass number cannot be converted to int: {}'.format(arg))
+            raise IsotopeError("Mass number cannot be converted to int: {}".format(arg))
     return (aa, mm)
 
 
@@ -191,8 +183,6 @@ class Isotope(element.Element):
       specific_activity: the specific activity in Bq/g
     """
 
-    # pylint: disable=too-few-public-methods
-
     def __init__(self, *args):
         """Initialize an Isotope.
 
@@ -207,11 +197,11 @@ def __init__(self, *args):
         """
 
         self.A = 0
-        self.m = ''
+        self.m = ""
         self.M = 0
         if len(args) == 1:
             if not isinstance(args[0], str):
-                raise IsotopeError('Single argument must be a string')
+                raise IsotopeError("Single argument must be a string")
             symbol, aa, mm = parse_isotope(args[0])
             super().__init__(symbol)
             self._init_A(aa)
@@ -220,17 +210,17 @@ def __init__(self, *args):
             try:
                 super().__init__(args[0])
             except element.ElementError:
-                raise IsotopeError('Unable to create Isotope: {}'.format(args))
+                raise IsotopeError("Unable to create Isotope: {}".format(args))
             self._init_A(args[1])
             if len(args) == 3:
                 self._init_m(args[2])
         else:
-            raise IsotopeError('One, two, or three arguments required')
+            raise IsotopeError("One, two, or three arguments required")
         self.N = self.A - self.Z
         if self.N < 0:
             raise IsotopeError(
-                'Neutron number N cannot be negative: {} {}'.format(
-                    args, self.N))
+                "Neutron number N cannot be negative: {} {}".format(args, self.N)
+            )
 
     def _init_A(self, arg):
         """Initialize with an isotope A."""
@@ -238,49 +228,52 @@ def _init_A(self, arg):
             self.A = int(arg)
         except ValueError:
             raise IsotopeError(
-                'Mass number cannot be converted to integer: {}'.format(arg))
+                "Mass number cannot be converted to integer: {}".format(arg)
+            )
         if self.A < 1:
-            raise IsotopeError(
-                'Mass number must be >= 1: {}'.format(self.A))
+            raise IsotopeError("Mass number must be >= 1: {}".format(self.A))
 
     def _init_m(self, arg):
         """Initialize with an isomer level number."""
-        if arg == '' or arg is None or arg == 0:
-            self.m = ''
+        if arg == "" or arg is None or arg == 0:
+            self.m = ""
             self.M = 0
         else:
             if isinstance(arg, int):
                 if arg == 1:
-                    self.m = 'm'
+                    self.m = "m"
                     self.M = 1
                 elif arg >= 2:
                     self.M = arg
-                    self.m = 'm{}'.format(self.M)
+                    self.m = "m{}".format(self.M)
                 else:
-                    raise IsotopeError(
-                        'Metastable level must be >= 0: {}'.format(arg))
+                    raise IsotopeError("Metastable level must be >= 0: {}".format(arg))
             elif isinstance(arg, str):
                 self.m = arg.lower()
-                if self.m[0] != 'm':
+                if self.m[0] != "m":
                     raise IsotopeError(
-                        'Metastable level must start with "m": {}'.format(
-                            self.m))
+                        'Metastable level must start with "m": {}'.format(self.m)
+                    )
                 if len(self.m) > 1:
                     if not self.m[1:].isdigit():
                         raise IsotopeError(
-                            'Metastable level must be numeric: {} {}'.format(
-                                self.m[0], self.m[1:]))
+                            "Metastable level must be numeric: {} {}".format(
+                                self.m[0], self.m[1:]
+                            )
+                        )
                     self.M = int(self.m[1:])
                 else:
                     self.M = 1
             else:
                 raise IsotopeError(
-                    'Metastable level must be integer or string: {} {}'.format(
-                        arg, type(arg)))
+                    "Metastable level must be integer or string: {} {}".format(
+                        arg, type(arg)
+                    )
+                )
 
     def __str__(self):
         """Define behavior of str() on Isotope."""
-        return '{}'.format(self)
+        return "{}".format(self)
 
     def __format__(self, formatstr):
         """Define behavior of string's format method.
@@ -295,22 +288,25 @@ def __format__(self, formatstr):
 
         str0 = str(formatstr)
         if len(str0) == 0:
-            str0 = '%s-%a%m'
-        str0 = str0.replace('%s', self.symbol)
-        str0 = str0.replace('%n', self.name)
-        str0 = str0.replace('%z', '{}'.format(self.Z))
-        str0 = str0.replace('%a', '{}'.format(self.A))
-        str0 = str0.replace('%m', self.m)
+            str0 = "%s-%a%m"
+        str0 = str0.replace("%s", self.symbol)
+        str0 = str0.replace("%n", self.name)
+        str0 = str0.replace("%z", "{}".format(self.Z))
+        str0 = str0.replace("%a", "{}".format(self.A))
+        str0 = str0.replace("%m", self.m)
         return str0
 
     def __eq__(self, other):
         """Define equality of two isotopes."""
         if isinstance(other, Isotope):
             return (
-                super().__eq__(other) and
-                self.A == other.A and self.Z == other.Z and self.M == other.M)
+                super().__eq__(other)
+                and self.A == other.A
+                and self.Z == other.Z
+                and self.M == other.M
+            )
         else:
-            raise TypeError('Cannot compare to non-isotope')
+            raise TypeError("Cannot compare to non-isotope")
 
     def _wallet_card(self):
         """Retrieve the wallet card data for this isotope.
@@ -324,14 +320,14 @@ def _wallet_card(self):
 
         if not wallet_cache.loaded:
             wallet_cache.load()
-        this_isotope = \
-            (wallet_cache.df['Z'] == self.Z) & \
-            (wallet_cache.df['A'] == self.A) & \
-            (wallet_cache.df['M'] == self.M)
+        this_isotope = (
+            (wallet_cache.df["Z"] == self.Z)
+            & (wallet_cache.df["A"] == self.A)
+            & (wallet_cache.df["M"] == self.M)
+        )
         df = wallet_cache.df[this_isotope]
         if len(df) == 0:
-            raise IsotopeError(
-                'No wallet card data found for isotope {}'.format(self))
+            raise IsotopeError("No wallet card data found for isotope {}".format(self))
         return df
 
     @property
@@ -343,7 +339,7 @@ def half_life(self):
         """
 
         df = self._wallet_card()
-        data = df['T1/2 (s)'].tolist()
+        data = df["T1/2 (s)"].tolist()
         assert len(np.unique(data)) == 1
         return data[0]
 
@@ -366,9 +362,9 @@ def is_stable(self):
         """
 
         df = self._wallet_card()
-        data = df['T1/2 (txt)'].tolist()
+        data = df["T1/2 (txt)"].tolist()
         assert len(np.unique(data)) == 1
-        return 'STABLE' in data
+        return "STABLE" in data
 
     @property
     def abundance(self):
@@ -379,7 +375,7 @@ def abundance(self):
         """
 
         df = self._wallet_card()
-        data = df['Abundance (%)'].tolist()
+        data = df["Abundance (%)"].tolist()
         if not isinstance(data[0], uncertainties.core.Variable):
             if np.isnan(data[0]):
                 return None
@@ -394,7 +390,7 @@ def j_pi(self):
         """
 
         df = self._wallet_card()
-        data = df['JPi'].tolist()
+        data = df["JPi"].tolist()
         assert len(np.unique(data)) == 1
         return data[0]
 
@@ -407,7 +403,7 @@ def energy_level(self):
         """
 
         df = self._wallet_card()
-        data = df['Energy Level (MeV)'].tolist()
+        data = df["Energy Level (MeV)"].tolist()
         assert len(np.unique(data)) == 1
         return data[0]
 
@@ -420,7 +416,7 @@ def mass_excess(self):
         """
 
         df = self._wallet_card()
-        data = df['Mass Excess (MeV)'].tolist()
+        data = df["Mass Excess (MeV)"].tolist()
         if not isinstance(data[0], uncertainties.core.Variable):
             if np.isnan(data[0]):
                 return None
@@ -435,8 +431,8 @@ def decay_modes(self):
         """
 
         df = self._wallet_card()
-        data1 = df['Decay Mode'].tolist()
-        data2 = df['Branching (%)'].tolist()
+        data1 = df["Decay Mode"].tolist()
+        data2 = df["Branching (%)"].tolist()
         if len(data1) == 1 and np.isnan(data2[0]):
             return [], []
         return data1, data2
diff --git a/becquerel/tools/isotope_qty.py b/becquerel/tools/isotope_qty.py
index 8382a695..025605ae 100644
--- a/becquerel/tools/isotope_qty.py
+++ b/becquerel/tools/isotope_qty.py
@@ -1,6 +1,5 @@
 """Quantities of a nuclear isotope, with decay and activation tools."""
 
-from __future__ import print_function
 import datetime
 import copy
 import numpy as np
@@ -35,12 +34,12 @@ def handle_isotope(isotope, error_name=None):
 
     if isinstance(isotope, Isotope):
         return isotope
-    elif utils.isstring(isotope):
+    elif isinstance(isotope, str):
         return Isotope(isotope)
     else:
         raise TypeError(
-            '{} needs an Isotope instance or string, not {}'.format(
-                error_name, isotope))
+            "{} needs an Isotope instance or string, not {}".format(error_name, isotope)
+        )
 
 
 class IsotopeQuantity(object):
@@ -115,7 +114,7 @@ def _init_isotope(self, isotope, stability):
           AttributeError: if isotope is missing half_life or decay_const
         """
 
-        self.isotope = handle_isotope(isotope, error_name='IsotopeQuantity')
+        self.isotope = handle_isotope(isotope, error_name="IsotopeQuantity")
 
         self.half_life = self.isotope.half_life
         self.decay_const = self.isotope.decay_const
@@ -129,7 +128,8 @@ def _init_date(self, date):
         """
 
         self.ref_date = utils.handle_datetime(
-            date, error_name='IsotopeQuantity date', allow_none=True)
+            date, error_name="IsotopeQuantity date", allow_none=True
+        )
         if self.ref_date is None:
             # assume a long-lived source in the current epoch
             self.ref_date = datetime.datetime.now()
@@ -152,20 +152,24 @@ def _quantities_from_kwargs(self, **kwargs):
 
         assert len(kwargs) == 1
         ref_quantities = OrderedDict()
-        if '_init_empty' in kwargs:
+        if "_init_empty" in kwargs:
             return ref_quantities
-        if ('bq' in kwargs or 'uci' in kwargs) and self.is_stable:
+        if ("bq" in kwargs or "uci" in kwargs) and self.is_stable:
             raise IsotopeQuantityError(
-                'Cannot initialize a stable IsotopeQuantity from activity')
+                "Cannot initialize a stable IsotopeQuantity from activity"
+            )
 
         # dictionary with functions that define how to calculate all quantities
         # in a circular manner
         conversions = dict(
-            atoms=lambda : ref_quantities["g"] / self.isotope.A * N_AV,
-            bq=lambda : ref_quantities["atoms"] * self.decay_const,
-            uci=lambda : ref_quantities["bq"] / UCI_TO_BQ,
-            g=lambda : ref_quantities["uci"] * UCI_TO_BQ /
-                       self.decay_const / N_AV * self.isotope.A
+            atoms=lambda: ref_quantities["g"] / self.isotope.A * N_AV,
+            bq=lambda: ref_quantities["atoms"] * self.decay_const,
+            uci=lambda: ref_quantities["bq"] / UCI_TO_BQ,
+            g=lambda: ref_quantities["uci"]
+            * UCI_TO_BQ
+            / self.decay_const
+            / N_AV
+            * self.isotope.A,
         )
 
         # rotates the order of the list so that the provided kwarg is at [0]
@@ -187,10 +191,11 @@ def _check_positive_qty(self, val):
           ValueError: if val is negative
         """
 
-        val *= 1.   # convert to float, or preserve ufloat, as appropriate
+        val *= 1.0  # convert to float, or preserve ufloat, as appropriate
         if val < 0:
             raise ValueError(
-                'Mass or activity must be a positive quantity: {}'.format(val))
+                "Mass or activity must be a positive quantity: {}".format(val)
+            )
         return val
 
     @property
@@ -225,16 +230,17 @@ def from_decays(cls, isotope, n_decays, start_time, stop_time):
         duration = (stop_time - obj.ref_date).total_seconds()
         if duration < 0:
             raise ValueError(
-                'Start time must precede stop time: {}, {}'.format(
-                    start_time, stop_time))
+                "Start time must precede stop time: {}, {}".format(
+                    start_time, stop_time
+                )
+            )
         atoms = float(n_decays) / (1 - np.exp(-obj.decay_const * duration))
 
         obj._ref_quantities = obj._quantities_from_kwargs(atoms=atoms)
         return obj
 
     @classmethod
-    def from_comparison(cls, isotope_qty1, counts1, interval1,
-                        counts2, interval2):
+    def from_comparison(cls, isotope_qty1, counts1, interval1, counts2, interval2):
         """Calculate an IsotopeQuantity by comparison with a known sample.
 
         Assumes the samples are in identical geometry with the detector.
@@ -280,7 +286,7 @@ def quantity_at(self, quantity, date=None):
         date = date if date is not None else datetime.datetime.now()
         t1 = utils.handle_datetime(date)
         dt = (t1 - self.ref_date).total_seconds()
-        return self._ref_quantities[quantity] * 2**(-dt / self.half_life)
+        return self._ref_quantities[quantity] * 2 ** (-dt / self.half_life)
 
     def atoms_at(self, date=None):
         """Calculate the number of atoms at a given time.
@@ -333,9 +339,9 @@ def atoms_now(self):
         """
 
         warnings.warn(
-            'atoms_now() is deprecated and will be removed in a future release'
-            '. Use atoms_at(date=None) instead.',
-            DeprecationWarning
+            "atoms_now() is deprecated and will be removed in a future release"
+            ". Use atoms_at(date=None) instead.",
+            DeprecationWarning,
         )
         return self.quantity_at("atoms", date=None)
 
@@ -346,9 +352,9 @@ def bq_now(self):
         """
 
         warnings.warn(
-            'bq_now() is deprecated and will be removed in a future release'
-            '. Use bq_at(date=None) instead.',
-            DeprecationWarning
+            "bq_now() is deprecated and will be removed in a future release"
+            ". Use bq_at(date=None) instead.",
+            DeprecationWarning,
         )
         return self.quantity_at("bq", date=None)
 
@@ -359,9 +365,9 @@ def uci_now(self):
         """
 
         warnings.warn(
-            'uci_now() is deprecated and will be removed in a future release'
-            '. Use uci_at(date=None) instead.',
-            DeprecationWarning
+            "uci_now() is deprecated and will be removed in a future release"
+            ". Use uci_at(date=None) instead.",
+            DeprecationWarning,
         )
         return self.quantity_at("uci", date=None)
 
@@ -372,9 +378,9 @@ def g_now(self):
         """
 
         warnings.warn(
-            'g_now() is deprecated and will be removed in a future release'
-            '. Use g_at(date=None) instead.',
-            DeprecationWarning
+            "g_now() is deprecated and will be removed in a future release"
+            ". Use g_at(date=None) instead.",
+            DeprecationWarning,
         )
         return self.quantity_at("g", date=None)
 
@@ -404,8 +410,8 @@ def bq_from(self, start_time, stop_time):
         As decays_from() except for return value.
         """
 
-        t0 = utils.handle_datetime(start_time, error_name='start_time')
-        t1 = utils.handle_datetime(stop_time, error_name='stop_time')
+        t0 = utils.handle_datetime(start_time, error_name="start_time")
+        t1 = utils.handle_datetime(stop_time, error_name="stop_time")
 
         return self.decays_from(t0, t1) / (t1 - t0).total_seconds()
 
@@ -474,8 +480,7 @@ def time_when(self, **kwargs):
         """
 
         if self.is_stable:
-            raise IsotopeQuantityError(
-                'Cannot calculate time_when for stable isotope')
+            raise IsotopeQuantityError("Cannot calculate time_when for stable isotope")
 
         assert len(kwargs) == 1
         key = next(iter(kwargs))
@@ -490,10 +495,11 @@ def __str__(self):
         """
 
         if self.isotope.is_stable:
-            s = '{} g of {}'.format(self.g_at(self.ref_date), self.isotope)
+            s = "{} g of {}".format(self.g_at(self.ref_date), self.isotope)
         else:
-            s = '{} Bq of {} (at {})'.format(
-                self.bq_at(self.ref_date), self.isotope, self.ref_date)
+            s = "{} Bq of {} (at {})".format(
+                self.bq_at(self.ref_date), self.isotope, self.ref_date
+            )
         return s
 
     def __mul__(self, other):
@@ -529,7 +535,7 @@ def _mul_div(self, other, div=False):
         key = next(iter(self._ref_quantities))
         return IsotopeQuantity(
             copy.deepcopy(self.isotope),
-            **{"date": self.ref_date, key: self._ref_quantities[key] * factor}
+            **{"date": self.ref_date, key: self._ref_quantities[key] * factor},
         )
 
     def __eq__(self, other):
@@ -541,9 +547,10 @@ def __eq__(self, other):
             # This supports uncertanties too
             a = self._ref_quantities["atoms"]
             b = other.atoms_at(self.ref_date)
-            return (self.isotope == other.isotope and
-                    abs(a - b) <= 1e-9 * max(abs(a), abs(b))
-                   )
+            return self.isotope == other.isotope and abs(a - b) <= 1e-9 * max(
+                abs(a), abs(b)
+            )
+
 
 class NeutronIrradiationError(Exception):
     """Exception from NeutronIrradiation class."""
@@ -584,16 +591,21 @@ def __init__(self, start_time, stop_time, n_cm2=None, n_cm2_s=None):
         """
 
         self.start_time = utils.handle_datetime(
-            start_time, error_name='NeutronIrradiation start_time')
+            start_time, error_name="NeutronIrradiation start_time"
+        )
         self.stop_time = utils.handle_datetime(
-            stop_time, error_name='NeutronIrradiation stop_time')
+            stop_time, error_name="NeutronIrradiation stop_time"
+        )
         if self.stop_time < self.start_time:
-            raise ValueError('Timestamps out of order: {}, {}'.format(
-                self.start_time, self.stop_time))
+            raise ValueError(
+                "Timestamps out of order: {}, {}".format(
+                    self.start_time, self.stop_time
+                )
+            )
         self.duration = (self.stop_time - self.start_time).total_seconds()
 
         if not ((n_cm2 is None) ^ (n_cm2_s is None)):
-            raise ValueError('Must specify either n_cm2 or n_cm2_s, not both')
+            raise ValueError("Must specify either n_cm2 or n_cm2_s, not both")
         elif n_cm2 is None:
             self.n_cm2_s = n_cm2_s
             self.n_cm2 = n_cm2_s * self.duration
@@ -611,10 +623,11 @@ def __str__(self):
         """
 
         if self.duration == 0:
-            return '{} neutrons/cm2 at {}'.format(self.n_cm2, self.start_time)
+            return "{} neutrons/cm2 at {}".format(self.n_cm2, self.start_time)
         else:
-            return '{} n/cm2/s from {} to {}'.format(
-                self.n_cm2_s, self.start_time, self.stop_time)
+            return "{} n/cm2/s from {} to {}".format(
+                self.n_cm2_s, self.start_time, self.stop_time
+            )
 
     def activate(self, barns, initial, activated):
         """
@@ -664,58 +677,63 @@ def activate(self, barns, initial, activated):
             IsotopeQuantity objects
         """
 
-        if (isinstance(initial, IsotopeQuantity) and
-                isinstance(activated, IsotopeQuantity)):
+        if isinstance(initial, IsotopeQuantity) and isinstance(
+            activated, IsotopeQuantity
+        ):
             raise NeutronIrradiationError(
-                "Two IsotopeQuantity's in args, nothing left to calculate!" +
-                'Args: {}, {}'.format(initial, activated))
-        elif (isinstance(initial, IsotopeQuantity) and
-              isinstance(activated, Isotope)):
+                "Two IsotopeQuantity's in args, nothing left to calculate!"
+                + "Args: {}, {}".format(initial, activated)
+            )
+        elif isinstance(initial, IsotopeQuantity) and isinstance(activated, Isotope):
             forward = True
-        elif (isinstance(initial, Isotope) and
-              isinstance(activated, IsotopeQuantity)):
+        elif isinstance(initial, Isotope) and isinstance(activated, IsotopeQuantity):
             forward = False
         elif isinstance(initial, Isotope) and isinstance(activated, Isotope):
             raise NeutronIrradiationError(
-                'No IsotopeQuantity specified, not enough data. ' +
-                'Args: {}, {}'.format(initial, activated))
+                "No IsotopeQuantity specified, not enough data. "
+                + "Args: {}, {}".format(initial, activated)
+            )
         else:
             raise TypeError(
-                'Input args should be Isotope or IsotopeQuantity objects: ' +
-                '{}, {}'.format(initial, activated))
+                "Input args should be Isotope or IsotopeQuantity objects: "
+                + "{}, {}".format(initial, activated)
+            )
 
         if not initial.is_stable:
             raise NotImplementedError(
-                'Activation not implemented for a radioactive initial isotope')
+                "Activation not implemented for a radioactive initial isotope"
+            )
 
         cross_section = barns * 1.0e-24
 
         if forward:
             if self.duration == 0:
                 activated_bq = (
-                    self.n_cm2 * cross_section *
-                    initial.atoms_at(self.stop_time) *
-                    activated.decay_const)
+                    self.n_cm2
+                    * cross_section
+                    * initial.atoms_at(self.stop_time)
+                    * activated.decay_const
+                )
             else:
                 activated_bq = (
-                    self.n_cm2_s * cross_section *
-                    initial.atoms_at(self.stop_time) *
-                    (1 - np.exp(-activated.decay_const * self.duration))
+                    self.n_cm2_s
+                    * cross_section
+                    * initial.atoms_at(self.stop_time)
+                    * (1 - np.exp(-activated.decay_const * self.duration))
                 )
-            return IsotopeQuantity(activated,
-                                   date=self.stop_time, bq=activated_bq)
+            return IsotopeQuantity(activated, date=self.stop_time, bq=activated_bq)
         else:
             if self.duration == 0:
-                initial_atoms = (
-                    activated.bq_at(self.stop_time) /
-                    (self.n_cm2 * cross_section * activated.decay_const))
+                initial_atoms = activated.bq_at(self.stop_time) / (
+                    self.n_cm2 * cross_section * activated.decay_const
+                )
             else:
-                initial_atoms = (
-                    activated.bq_at(self.stop_time) /
-                    (self.n_cm2_s * cross_section * (1 - np.exp(
-                        -activated.decay_const * self.duration))))
-            return IsotopeQuantity(initial,
-                                   date=self.start_time, atoms=initial_atoms)
+                initial_atoms = activated.bq_at(self.stop_time) / (
+                    self.n_cm2_s
+                    * cross_section
+                    * (1 - np.exp(-activated.decay_const * self.duration))
+                )
+            return IsotopeQuantity(initial, date=self.start_time, atoms=initial_atoms)
 
 
 def decay_normalize(isotope, interval1, interval2):
@@ -739,23 +757,23 @@ def decay_normalize(isotope, interval1, interval2):
       TypeError: if timestamps are not parseable, or isotope is not an Isotope
     """
 
-    isotope = handle_isotope(isotope, error_name='decay_normalize')
+    isotope = handle_isotope(isotope, error_name="decay_normalize")
     if len(interval1) != 2:
-        raise IsotopeQuantityError(
-            'interval1 should be length 2: {}'.format(interval1))
+        raise IsotopeQuantityError("interval1 should be length 2: {}".format(interval1))
     elif len(interval2) != 2:
-        raise IsotopeQuantityError(
-            'interval2 should be length 2: {}'.format(interval2))
-    start1 = utils.handle_datetime(interval1[0], error_name='decay_normalize')
-    stop1 = utils.handle_datetime(interval1[1], error_name='decay_normalize')
-    start2 = utils.handle_datetime(interval2[0], error_name='decay_normalize')
-    stop2 = utils.handle_datetime(interval2[1], error_name='decay_normalize')
+        raise IsotopeQuantityError("interval2 should be length 2: {}".format(interval2))
+    start1 = utils.handle_datetime(interval1[0], error_name="decay_normalize")
+    stop1 = utils.handle_datetime(interval1[1], error_name="decay_normalize")
+    start2 = utils.handle_datetime(interval2[0], error_name="decay_normalize")
+    stop2 = utils.handle_datetime(interval2[1], error_name="decay_normalize")
     if stop1 < start1:
-        raise ValueError('Timestamps in interval1 out of order: {}, {}'.format(
-            start1, stop1))
+        raise ValueError(
+            "Timestamps in interval1 out of order: {}, {}".format(start1, stop1)
+        )
     elif stop2 < start2:
-        raise ValueError('Timestamps in interval2 out of order: {}, {}'.format(
-            start2, stop2))
+        raise ValueError(
+            "Timestamps in interval2 out of order: {}, {}".format(start2, stop2)
+        )
 
     # TODO base this on countrate, not counts
 
@@ -783,6 +801,8 @@ def decay_normalize_spectra(isotope, spec1, spec2):
       AttributeError: if spec1 or spec2 do not have start_time and stop_time
     """
 
-    return decay_normalize(isotope,
-                           (spec1.start_time, spec1.stop_time),
-                           (spec2.start_time, spec2.stop_time))
+    return decay_normalize(
+        isotope,
+        (spec1.start_time, spec1.stop_time),
+        (spec2.start_time, spec2.stop_time),
+    )
diff --git a/becquerel/tools/materials.py b/becquerel/tools/materials.py
index ddba641a..fc47896d 100644
--- a/becquerel/tools/materials.py
+++ b/becquerel/tools/materials.py
@@ -7,18 +7,17 @@
 
 """
 
-from __future__ import print_function
 import requests
 import pandas as pd
+from collections.abc import Iterable
 from .element import element_symbol
-from ..core.utils import isstring, Iterable
 
 MAX_Z = 92
 N_COMPOUNDS = 48
 
 
-_URL_TABLE1 = 'http://physics.nist.gov/PhysRefData/XrayMassCoef/tab1.html'
-_URL_TABLE2 = 'http://physics.nist.gov/PhysRefData/XrayMassCoef/tab2.html'
+_URL_TABLE1 = "http://physics.nist.gov/PhysRefData/XrayMassCoef/tab1.html"
+_URL_TABLE2 = "http://physics.nist.gov/PhysRefData/XrayMassCoef/tab2.html"
 
 
 class NISTMaterialsError(Exception):
@@ -48,10 +47,12 @@ def _get_request(url):
     """
 
     req = requests.get(url)
-    if not req.ok or req.reason != 'OK' or req.status_code != 200:
+    if not req.ok or req.reason != "OK" or req.status_code != 200:
         raise NISTMaterialsRequestError(
-            'NIST materials request failed: reason={}, status_code={}'.format(
-                req.reason, req.status_code))
+            "NIST materials request failed: reason={}, status_code={}".format(
+                req.reason, req.status_code
+            )
+        )
     return req
 
 
@@ -74,32 +75,35 @@ def fetch_element_data():
     # rename first two header columns
     text = text.replace(
         '<TH scope="col" COLSPAN="2"><I>Z</I></TH>',
-        '<TH scope="col">Z</TH><TH scope="col">Symbol</TH>')
+        '<TH scope="col">Z</TH><TH scope="col">Symbol</TH>',
+    )
     # remove row that makes the table too wide
-    text = text.replace('<TD COLSPAN="10"><HR SIZE="1" NOSHADE></TD>', '')
+    text = text.replace('<TD COLSPAN="10"><HR SIZE="1" NOSHADE></TD>', "")
     # remove extra header columns
-    text = text.replace('<TD COLSPAN="2">', '<TD>')
-    text = text.replace('<TD COLSPAN="4">', '<TD>')
-    text = text.replace('TD>&nbsp;</TD>', '')
+    text = text.replace('<TD COLSPAN="2">', "<TD>")
+    text = text.replace('<TD COLSPAN="4">', "<TD>")
+    text = text.replace("TD>&nbsp;</TD>", "")
     # remove extra columns in Hydrogen row
-    text = text.replace('<TD ROWSPAN="92">&nbsp;</TD>', '')
+    text = text.replace('<TD ROWSPAN="92">&nbsp;</TD>', "")
     # remove open <TR> at the end of the table
-    text = text.replace('</TD></TR><TR>', '</TD></TR>')
+    text = text.replace("</TD></TR><TR>", "</TD></TR>")
     # read HTML table into pandas DataFrame
     tables = pd.read_html(text, header=0, skiprows=[1, 2])
     if len(tables) != 1:
         raise NISTMaterialsRequestError(
-            '1 HTML table expected, but found {}'.format(len(tables)))
+            "1 HTML table expected, but found {}".format(len(tables))
+        )
     df = tables[0]
     if len(df) != MAX_Z:
         raise NISTMaterialsRequestError(
-            '{} elements expected, but found {}'.format(MAX_Z, len(df)))
+            "{} elements expected, but found {}".format(MAX_Z, len(df))
+        )
     if len(df.columns) != 6:
         raise NISTMaterialsRequestError(
-            '10 columns expected, but found {} ({})'.format(
-                len(df.columns), df.columns))
+            "10 columns expected, but found {} ({})".format(len(df.columns), df.columns)
+        )
     # rename columns
-    df.columns = ['Z', 'Symbol', 'Element', 'Z_over_A', 'I_eV', 'Density']
+    df.columns = ["Z", "Symbol", "Element", "Z_over_A", "I_eV", "Density"]
     return df
 
 
@@ -121,25 +125,30 @@ def convert_composition(comp):
     comp_sym = []
     if not isinstance(comp, Iterable):
         raise NISTMaterialsRequestError(
-            'Compound must be an iterable of strings: {}'.format(comp))
+            "Compound must be an iterable of strings: {}".format(comp)
+        )
     for line in comp:
-        if not isstring(line):
+        if not isinstance(line, str):
             raise NISTMaterialsRequestError(
-                'Line must be a string type: {} {}'.format(line, type(line)))
+                "Line must be a string type: {} {}".format(line, type(line))
+            )
         try:
-            z, weight = line.split(':')
+            z, weight = line.split(":")
         except ValueError:
             raise NISTMaterialsRequestError(
-                'Unable to split compound line: {}'.format(line))
+                "Unable to split compound line: {}".format(line)
+            )
         try:
             z = int(z)
         except ValueError:
             raise NISTMaterialsRequestError(
-                'Unable to convert Z {} to integer: {}'.format(z, line))
+                "Unable to convert Z {} to integer: {}".format(z, line)
+            )
         if z < 1 or z > MAX_Z:
             raise NISTMaterialsRequestError(
-                'Z {} out of range [1, {}]: {}'.format(z, line, MAX_Z))
-        comp_sym.append(element_symbol(z) + ' ' + weight.strip())
+                "Z {} out of range [1, {}]: {}".format(z, line, MAX_Z)
+            )
+        comp_sym.append(element_symbol(z) + " " + weight.strip())
     return comp_sym
 
 
@@ -160,35 +169,38 @@ def fetch_compound_data():
     req = _get_request(_URL_TABLE2)
     text = req.text
     # remove extra header columns
-    text = text.replace('<TD ROWSPAN="2">&nbsp;</TD>', '')
+    text = text.replace('<TD ROWSPAN="2">&nbsp;</TD>', "")
     # remove extra rows in header row
-    text = text.replace(' ROWSPAN="2"', '')
+    text = text.replace(' ROWSPAN="2"', "")
     # remove extra columns in third header row
-    text = text.replace('<TD COLSPAN="9"><HR SIZE="1" NOSHADE></TD>', '')
+    text = text.replace('<TD COLSPAN="9"><HR SIZE="1" NOSHADE></TD>', "")
     # remove extra columns in the first material row
-    text = text.replace('<TD ROWSPAN="50"> &nbsp; </TD>', '')
+    text = text.replace('<TD ROWSPAN="50"> &nbsp; </TD>', "")
     # replace <BR> symbols in composition lists with semicolons
-    text = text.replace('<BR>', ';')
+    text = text.replace("<BR>", ";")
     # read HTML table into pandas DataFrame
     tables = pd.read_html(text, header=0, skiprows=[1, 2])
     if len(tables) != 1:
         raise NISTMaterialsRequestError(
-            '1 HTML table expected, but found {}'.format(len(tables)))
+            "1 HTML table expected, but found {}".format(len(tables))
+        )
     df = tables[0]
     if len(df) != N_COMPOUNDS:
         raise NISTMaterialsRequestError(
-            '{} compounds expected, but found {}'.format(N_COMPOUNDS, len(df)))
+            "{} compounds expected, but found {}".format(N_COMPOUNDS, len(df))
+        )
     if len(df.columns) != 5:
         raise NISTMaterialsRequestError(
-            '5 columns expected, but found {} ({})'.format(
-                len(df.columns), df.columns))
+            "5 columns expected, but found {} ({})".format(len(df.columns), df.columns)
+        )
     # rename columns
-    df.columns = ['Material', 'Z_over_A', 'I_eV', 'Density', 'Composition_Z']
+    df.columns = ["Material", "Z_over_A", "I_eV", "Density", "Composition_Z"]
     # clean up Z composition
-    df['Composition_Z'] = [
-        [line.strip() for line in comp.split(';')]
-        for comp in df['Composition_Z']]
+    df["Composition_Z"] = [
+        [line.strip() for line in comp.split(";")] for comp in df["Composition_Z"]
+    ]
     # create a column of compositions by symbol (for use with fetch_xcom_data)
-    df['Composition_symbol'] = [
-        convert_composition(comp) for comp in df['Composition_Z']]
+    df["Composition_symbol"] = [
+        convert_composition(comp) for comp in df["Composition_Z"]
+    ]
     return df
diff --git a/becquerel/tools/nndc.py b/becquerel/tools/nndc.py
index 1531e1d3..b2394b02 100644
--- a/becquerel/tools/nndc.py
+++ b/becquerel/tools/nndc.py
@@ -7,79 +7,79 @@
 
 """
 
-from __future__ import print_function
 from future.builtins import super
 import warnings
 import numpy as np
 import requests
 import pandas as pd
 import uncertainties
-from ..core.utils import isstring, ResourceWarning
 
 
-PARITIES = ['+', '-', 'any']
+PARITIES = ["+", "-", "any"]
 
 
 DECAYRAD_DECAY_MODE = {
-    'any': 'ANY',
-    'internal transition': 'IT',
-    'it': 'IT',
-    'beta-': 'B-',
-    'b-': 'B-',
-    'electron capture beta+': 'ECBP',
-    'ecbp': 'ECBP',
-    'ecb+': 'ECBP',
-    'ec+b+': 'ECBP',
-    'electron capture': 'ECBP',
-    'ec': 'ECBP',
-    'beta+': 'ECBP',
-    'b+': 'ECBP',
-    'neutron': 'N',
-    'n': 'N',
-    'proton': 'P',
-    'p': 'P',
-    'alpha': 'A',
-    'a': 'A',
-    'spontaneous fission': 'SF',
-    'sf': 'SF',
+    "any": "ANY",
+    "internal transition": "IT",
+    "it": "IT",
+    "beta-": "B-",
+    "b-": "B-",
+    "electron capture beta+": "ECBP",
+    "ecbp": "ECBP",
+    "ecb+": "ECBP",
+    "ec+b+": "ECBP",
+    "electron capture": "ECBP",
+    "ec": "ECBP",
+    "beta+": "ECBP",
+    "b+": "ECBP",
+    "neutron": "N",
+    "n": "N",
+    "proton": "P",
+    "p": "P",
+    "alpha": "A",
+    "a": "A",
+    "spontaneous fission": "SF",
+    "sf": "SF",
 }
 
 
 WALLET_DECAY_MODE = dict(DECAYRAD_DECAY_MODE)
-WALLET_DECAY_MODE.update({
-    'double beta': 'DB',
-    'bb': 'DB',
-    'cluster': 'C',
-    'c': 'C',
-    'beta-delayed neutron': 'DN',
-    'b-delayed n': 'DN',
-    'bdn': 'DN',
-    'beta-delayed proton': 'DP',
-    'b-delayed p': 'DP',
-    'bdp': 'DP',
-    'beta-delayed alpha': 'DA',
-    'b-delayed a': 'DA',
-    'bda': 'DA',
-    'beta-delayed fission': 'DF',
-    'b-delayed f': 'DF',
-    'bdf': 'DF',
-})
+WALLET_DECAY_MODE.update(
+    {
+        "double beta": "DB",
+        "bb": "DB",
+        "cluster": "C",
+        "c": "C",
+        "beta-delayed neutron": "DN",
+        "b-delayed n": "DN",
+        "bdn": "DN",
+        "beta-delayed proton": "DP",
+        "b-delayed p": "DP",
+        "bdp": "DP",
+        "beta-delayed alpha": "DA",
+        "b-delayed a": "DA",
+        "bda": "DA",
+        "beta-delayed fission": "DF",
+        "b-delayed f": "DF",
+        "bdf": "DF",
+    }
+)
 
 
 DECAYRAD_RADIATION_TYPE = {
-    'any': 'ANY',
-    'gamma': 'G',
-    'g': 'G',
-    'beta-': 'BM',
-    'b-': 'BM',
-    'beta+': 'BP',
-    'b+': 'BP',
-    'electron': 'E',
-    'e': 'E',
-    'proton': 'P',
-    'p': 'P',
-    'alpha': 'A',
-    'a': 'A',
+    "any": "ANY",
+    "gamma": "G",
+    "g": "G",
+    "beta-": "BM",
+    "b-": "BM",
+    "beta+": "BP",
+    "b+": "BP",
+    "electron": "E",
+    "e": "E",
+    "proton": "P",
+    "p": "P",
+    "alpha": "A",
+    "a": "A",
 }
 
 
@@ -123,38 +123,39 @@ def _parse_headers(headers):
     # reformat column headers if needed
     for j, hd in enumerate(headers):
         # rename so always have T1/2 (s)
-        if hd == 'T1/2 (num)' or hd == 'T1/2 (seconds)':
-            hd = 'T1/2 (s)'
+        if hd == "T1/2 (num)" or hd == "T1/2 (seconds)":
+            hd = "T1/2 (s)"
         # for uncertainties, add previous column header to it
-        if j > 0 and 'Unc' in hd:
-            hd = headers[j - 1] + ' ' + hd
-        if 'Unc' in hd and 'Unc.' not in hd:
-            hd = hd.replace('Unc', 'Unc.')
+        if j > 0 and "Unc" in hd:
+            hd = headers[j - 1] + " " + hd
+        if "Unc" in hd and "Unc." not in hd:
+            hd = hd.replace("Unc", "Unc.")
         # expand abbreviated headers
-        if 'Energy' in hd and 'Energy Level' not in hd:
-            hd = hd.replace('Energy', 'Energy Level')
-        if 'Par. Elevel' in hd:
-            hd = hd.replace('Par. Elevel', 'Parent Energy Level')
-        if 'Abund.' in hd:
-            hd = hd.replace('Abund.', 'Abundance (%)')
-        if 'Ene.' in hd:
-            hd = hd.replace('Ene.', 'Energy')
-        if 'Int.' in hd:
-            hd = hd.replace('Int.', 'Intensity (%)')
-        if 'Dec' in hd and 'Decay' not in hd:
-            hd = hd.replace('Dec', 'Decay')
-        if 'Rad' in hd and 'Radiation' not in hd:
-            hd = hd.replace('Rad', 'Radiation')
-        if 'EP' in hd:
-            hd = hd.replace('EP', 'Endpoint')
-        if 'Mass Exc' in hd and 'Mass Excess' not in hd:
-            hd = hd.replace('Mass Exc', 'Mass Excess')
+        if "Energy" in hd and "Energy Level" not in hd:
+            hd = hd.replace("Energy", "Energy Level")
+        if "Par. Elevel" in hd:
+            hd = hd.replace("Par. Elevel", "Parent Energy Level")
+        if "Abund." in hd:
+            hd = hd.replace("Abund.", "Abundance (%)")
+        if "Ene." in hd:
+            hd = hd.replace("Ene.", "Energy")
+        if "Int." in hd:
+            hd = hd.replace("Int.", "Intensity (%)")
+        if "Dec" in hd and "Decay" not in hd:
+            hd = hd.replace("Dec", "Decay")
+        if "Rad" in hd and "Radiation" not in hd:
+            hd = hd.replace("Rad", "Radiation")
+        if "EP" in hd:
+            hd = hd.replace("EP", "Endpoint")
+        if "Mass Exc" in hd and "Mass Excess" not in hd:
+            hd = hd.replace("Mass Exc", "Mass Excess")
         headers_new.append(hd)
     if len(set(headers_new)) != len(headers_new):
         raise NNDCRequestError(
-            'Duplicate headers after parsing\n' +
-            '    Original headers: "{}"\n'.format(headers) +
-            '    Parsed headers:   "{}"'.format(headers_new))
+            "Duplicate headers after parsing\n"
+            + '    Original headers: "{}"\n'.format(headers)
+            + '    Parsed headers:   "{}"'.format(headers_new)
+        )
     return headers_new
 
 
@@ -174,17 +175,16 @@ def _parse_table(text):
 
     text = str(text)
     try:
-        text = text.split('<pre>')[1]
-        text = text.split('</pre>')[0]
-        text = text.split('To save this output')[0]
-        lines = text.split('\n')
+        text = text.split("<pre>")[1]
+        text = text.split("</pre>")[0]
+        text = text.split("To save this output")[0]
+        lines = text.split("\n")
     except Exception as exc:
-        raise NNDCRequestError('Unable to parse text:\n{}\n{}'.format(exc,
-                                                                      text))
+        raise NNDCRequestError("Unable to parse text:\n{}\n{}".format(exc, text))
     table = {}
     headers = None
     for line in lines:
-        tokens = line.split('\t')
+        tokens = line.split("\t")
         tokens = [t.strip() for t in tokens]
         if len(tokens) <= 1:
             continue
@@ -196,9 +196,10 @@ def _parse_table(text):
         else:
             if len(tokens) != len(headers):
                 raise NNDCRequestError(
-                    'Too few data in table row\n' +
-                    '    Headers: "{}"\n'.format(headers) +
-                    '    Row:     "{}"'.format(tokens))
+                    "Too few data in table row\n"
+                    + '    Headers: "{}"\n'.format(headers)
+                    + '    Row:     "{}"'.format(tokens)
+                )
             for header, token in zip(headers, tokens):
                 table[header].append(token)
     return table
@@ -225,19 +226,19 @@ def _parse_float_uncertainty(x, dx):
 
     """
 
-    if not isstring(x):
-        raise NNDCRequestError('Value must be a string: {}'.format(x))
-    if not isstring(dx):
-        raise NNDCRequestError('Uncertainty must be a string: {}'.format(dx))
+    if not isinstance(x, str):
+        raise NNDCRequestError("Value must be a string: {}".format(x))
+    if not isinstance(dx, str):
+        raise NNDCRequestError("Uncertainty must be a string: {}".format(dx))
     # ignore percents
-    if '%' in x:
-        x = x.replace('%', '')
+    if "%" in x:
+        x = x.replace("%", "")
     # ignore unknown ground state levels (X, Y, Z, W)
-    for sym in ['X', 'Y', 'Z', 'W']:
-        if '+' + sym in x:
-            x = x.replace('+' + sym, '')
+    for sym in ["X", "Y", "Z", "W"]:
+        if "+" + sym in x:
+            x = x.replace("+" + sym, "")
         elif x == sym:
-            x = '0'
+            x = "0"
     # handle special ENSDF abbreviations, e.g.,
     # http://www.iaea.org/inis/collection/NCLCollectionStore/_Public/14/785/14785563.pdf
     # "One of the following expressions:
@@ -245,46 +246,44 @@ def _parse_float_uncertainty(x, dx):
     # for less than, greater than, less than or equal to greater
     # than or equal to. approximately equal to, calculated, and
     # from systematics, respectively."
-    for sym in ['*', '<', '>', '=', '~', '?', '@', '&', 'P', 'N']:
+    for sym in ["*", "<", ">", "=", "~", "?", "@", "&", "P", "N"]:
         while sym in x:
-            x = x.replace(sym, '')
+            x = x.replace(sym, "")
     # correct specific typos in the database
-    if 'E-11 0' in x:
-        x = x.replace('E-11 0', 'E-11')
-    if 'E-12 0' in x:
-        x = x.replace('E-12 0', 'E-12')
-    if '0.0000 1' in x:
-        x = x.replace('0.0000 1', '0.0000')
-    if '2 .8E-7' in x:
-        x = x.replace('2 .8E-7', '2.8E-7')
-    if '8 .0E-E5' in x:
-        x = x.replace('8 .0E-E5', '8.0E-5')
+    if "E-11 0" in x:
+        x = x.replace("E-11 0", "E-11")
+    if "E-12 0" in x:
+        x = x.replace("E-12 0", "E-12")
+    if "0.0000 1" in x:
+        x = x.replace("0.0000 1", "0.0000")
+    if "2 .8E-7" in x:
+        x = x.replace("2 .8E-7", "2.8E-7")
+    if "8 .0E-E5" in x:
+        x = x.replace("8 .0E-E5", "8.0E-5")
     # handle blank or missing data
-    if x == '' or x == ' ':
+    if x == "" or x == " ":
         return None
-    if '****' in dx:
-        dx = ''
-    elif dx in ['LT', 'GT', 'LE', 'GE', 'AP', 'CA', 'SY']:
-        dx = ''
+    if "****" in dx:
+        dx = ""
+    elif dx in ["LT", "GT", "LE", "GE", "AP", "CA", "SY"]:
+        dx = ""
     try:
         x2 = float(x)
     except ValueError:
-        raise NNDCRequestError(
-            'Value cannot be parsed as float: "{}"'.format(x))
-    if dx == '':
+        raise NNDCRequestError(f'Value cannot be parsed as float: "{x}"')
+    if dx == "":
         return x2
     # handle multiple exponents with some uncertainties, e.g., "7E-4E-5"
-    tokens = dx.split('E')
+    tokens = dx.split("E")
     if len(tokens) == 3:
-        dx = 'E'.join(tokens[:2])
-        factor = pow(10., int(tokens[2]))
+        dx = "E".join(tokens[:2])
+        factor = pow(10.0, int(tokens[2]))
     else:
-        factor = 1.
+        factor = 1.0
     try:
         dx2 = float(dx) * factor
     except ValueError:
-        raise NNDCRequestError(
-            'Uncertainty cannot be parsed as float: "{}"'.format(dx))
+        raise NNDCRequestError('Uncertainty cannot be parsed as float: "{}"'.format(dx))
     return uncertainties.ufloat(x2, dx2)
 
 
@@ -307,21 +306,22 @@ def _format_range(x_range):
         x1, x2 = x_range
     except (TypeError, ValueError):
         raise NNDCInputError(
-            'Range keyword arg must have two elements: "{}"'.format(x_range))
+            'Range keyword arg must have two elements: "{}"'.format(x_range)
+        )
     try:
         if np.isfinite(x1):
-            x1 = '{}'.format(x1)
+            x1 = "{}".format(x1)
         else:
-            x1 = ''
+            x1 = ""
     except TypeError:
-        x1 = ''
+        x1 = ""
     try:
         if np.isfinite(x2):
-            x2 = '{}'.format(x2)
+            x2 = "{}".format(x2)
         else:
-            x2 = ''
+            x2 = ""
     except TypeError:
-        x2 = ''
+        x2 = ""
     return x1, x2
 
 
@@ -344,48 +344,60 @@ class _NNDCQuery(object):
 
     """
 
-    _URL = ''
+    _URL = ""
     _DATA = {
-        'spnuc': '',           # specify parent ('name', 'zan', or 'zanrange')
-        'nuc': '',             # isotope name (use with 'name')
-        'z': '',               # Z or element (use with 'zan')
-        'zmin': '',            # Z min        (use with 'zanrange')
-        'zmax': '',            # Z max        (use with 'zanrange')
-        'a': '',               # A            (use with 'zan')
-        'amin': '',            # A min        (use with 'zanrange')
-        'amax': '',            # A max        (use with 'zanrange')
-        'n': '',               # N            (use with 'zan')
-        'nmin': '',            # N min        (use with 'zanrange')
-        'nmax': '',            # N max        (use with 'zanrange')
-        'evenz': '',           # 'any', 'even', or 'odd' Z (use with zanrange)
-        'evena': '',           # 'any', 'even', or 'odd' A (use with zanrange')
-        'evenn': '',           # 'any', 'even', or 'odd' N (use with zanrange)
-        'tled': 'disabled',    # half-life condition on/off
-        'tlmin': '0',          # half-life min
-        'utlow': 'S',          # half-life min units ('S' = seconds)
-        'tlmax': '3E17',       # half-life max
-        'utupp': 'S',          # half-life max units ('ST' = stable, 'GY' = Gy)
-        'notlim': 'disabled',  # half-life: no limit
-        'dmed': 'disabled',    # decay mode condition on/off
-        'dmn': 'ANY',          # decay mode: 'ANY' = any
-        'out': 'file',         # output to formatted file
-        'unc': 'stdandard',    # standard style uncertainties
-        'sub': 'Search',       # search for the data
+        "spnuc": "",  # specify parent ('name', 'zan', or 'zanrange')
+        "nuc": "",  # isotope name (use with 'name')
+        "z": "",  # Z or element (use with 'zan')
+        "zmin": "",  # Z min        (use with 'zanrange')
+        "zmax": "",  # Z max        (use with 'zanrange')
+        "a": "",  # A            (use with 'zan')
+        "amin": "",  # A min        (use with 'zanrange')
+        "amax": "",  # A max        (use with 'zanrange')
+        "n": "",  # N            (use with 'zan')
+        "nmin": "",  # N min        (use with 'zanrange')
+        "nmax": "",  # N max        (use with 'zanrange')
+        "evenz": "",  # 'any', 'even', or 'odd' Z (use with zanrange)
+        "evena": "",  # 'any', 'even', or 'odd' A (use with zanrange')
+        "evenn": "",  # 'any', 'even', or 'odd' N (use with zanrange)
+        "tled": "disabled",  # half-life condition on/off
+        "tlmin": "0",  # half-life min
+        "utlow": "S",  # half-life min units ('S' = seconds)
+        "tlmax": "3E17",  # half-life max
+        "utupp": "S",  # half-life max units ('ST' = stable, 'GY' = Gy)
+        "notlim": "disabled",  # half-life: no limit
+        "dmed": "disabled",  # decay mode condition on/off
+        "dmn": "ANY",  # decay mode: 'ANY' = any
+        "out": "file",  # output to formatted file
+        "unc": "stdandard",  # standard style uncertainties
+        "sub": "Search",  # search for the data
     }
     _ALLOWED_KEYWORDS = [
-        'perform', 'nuc', 'z', 'a', 'n',
-        'z_range', 'a_range', 'n_range',
-        'z_any', 'z_even', 'z_odd',
-        'a_any', 'a_even', 'a_odd',
-        'n_any', 'n_even', 'n_odd',
-        't_range',
+        "perform",
+        "nuc",
+        "z",
+        "a",
+        "n",
+        "z_range",
+        "a_range",
+        "n_range",
+        "z_any",
+        "z_even",
+        "z_odd",
+        "a_any",
+        "a_even",
+        "a_odd",
+        "n_any",
+        "n_even",
+        "n_odd",
+        "t_range",
     ]
-    _DUMMY_TEXT = ''
+    _DUMMY_TEXT = ""
 
     def __init__(self, **kwargs):
         """Initialize query of NNDC data."""
-        perform = kwargs.get('perform', True)
-        kwargs['perform'] = False
+        perform = kwargs.get("perform", True)
+        kwargs["perform"] = False
         self._data = dict(self._DATA)
         self._text = self._DUMMY_TEXT
         self.df = pd.DataFrame()
@@ -429,16 +441,16 @@ def _request(self):
             with warnings.catch_warnings():
                 warnings.simplefilter("ignore", category=ResourceWarning)
                 resp = session.post(self._URL, data=self._data, stream=False)
-            if not resp.ok or resp.reason != 'OK' or resp.status_code != 200:
-                raise NNDCRequestError('Request failed: ' + resp.reason)
+            if not resp.ok or resp.reason != "OK" or resp.status_code != 200:
+                raise NNDCRequestError("Request failed: " + resp.reason)
             for msg in [
-                    'Your search was unsuccessful',
-                    'Your search exceeded the maximum number of results',
-                    'There are too many results for your search',
+                "Your search was unsuccessful",
+                "Your search exceeded the maximum number of results",
+                "There are too many results for your search",
             ]:
                 if msg in resp.text:
-                    raise NNDCRequestError('Request failed: ' + msg)
-            msg = 'No datasets were found within the specified search'
+                    raise NNDCRequestError("Request failed: " + msg)
+            msg = "No datasets were found within the specified search"
             if msg in resp.text:
                 raise NoDataFound(msg)
         return resp.text
@@ -448,40 +460,41 @@ def update(self, **kwargs):
         for kwarg in kwargs:
             if kwarg not in self._ALLOWED_KEYWORDS:
                 raise NNDCInputError('Unknown keyword: "{}"'.format(kwarg))
-        if 'nuc' in kwargs:
-            self._data['spnuc'] = 'name'
-            self._data['nuc'] = kwargs['nuc']
-        for x in ['z', 'a', 'n']:
+        if "nuc" in kwargs:
+            self._data["spnuc"] = "name"
+            self._data["nuc"] = kwargs["nuc"]
+        for x in ["z", "a", "n"]:
             # handle Z, A, and N settings
             if x in kwargs:
-                self._data['spnuc'] = 'zanrange'
-                self._data[x + 'min'], self._data[x + 'max'] = \
-                    _format_range((kwargs[x], kwargs[x]))
+                self._data["spnuc"] = "zanrange"
+                self._data[x + "min"], self._data[x + "max"] = _format_range(
+                    (kwargs[x], kwargs[x])
+                )
             # handle *_range, *_any, *_odd, *_even
-            elif x + '_range' in kwargs:
-                self._data['spnuc'] = 'zanrange'
-                self._data[x + 'min'], self._data[x + 'max'] = \
-                    _format_range(kwargs[x + '_range'])
-                if self._data[x + 'min'] == '':
-                    self._data[x + 'min'] = '0'
-                if self._data[x + 'max'] == '':
-                    self._data[x + 'max'] = '300'
-            if x + '_any' in kwargs:
-                self._data['even' + x] = 'any'
-            elif x + '_even' in kwargs:
-                self._data['even' + x] = 'even'
-            elif x + '_odd' in kwargs:
-                self._data['even' + x] = 'odd'
+            elif x + "_range" in kwargs:
+                self._data["spnuc"] = "zanrange"
+                self._data[x + "min"], self._data[x + "max"] = _format_range(
+                    kwargs[x + "_range"]
+                )
+                if self._data[x + "min"] == "":
+                    self._data[x + "min"] = "0"
+                if self._data[x + "max"] == "":
+                    self._data[x + "max"] = "300"
+            if x + "_any" in kwargs:
+                self._data["even" + x] = "any"
+            elif x + "_even" in kwargs:
+                self._data["even" + x] = "even"
+            elif x + "_odd" in kwargs:
+                self._data["even" + x] = "odd"
         # handle half-life range condition
-        if 't_range' in kwargs:
-            self._data['tled'] = 'enabled'
-            self._data['tlmin'], self._data['tlmax'] = \
-                _format_range(kwargs['t_range'])
+        if "t_range" in kwargs:
+            self._data["tled"] = "enabled"
+            self._data["tlmin"], self._data["tlmax"] = _format_range(kwargs["t_range"])
 
     def perform(self):
         """Perform the query."""
         # check the conditions
-        if self._data['spnuc'] == '':
+        if self._data["spnuc"] == "":
             self.update(z_range=(None, None))
         # submit the query
         try:
@@ -489,13 +502,13 @@ def perform(self):
         except NoDataFound:
             self._text = self._DUMMY_TEXT
         if len(self._text) == 0:
-            raise NNDCRequestError('NNDC returned no text')
+            raise NNDCRequestError("NNDC returned no text")
         # package the output into a dictionary of arrays
         data = _parse_table(self._text)
         # create the DataFrame
         self.df = pd.DataFrame(data)
         # convert dimensionless integers to ints
-        for col in ['A', 'Z', 'N', 'M']:
+        for col in ["A", "Z", "N", "M"]:
             if col in self.keys():
                 self._convert_column(col, int)
         # combine uncertainty columns and add unit labels
@@ -507,20 +520,20 @@ def perform(self):
 
     def _add_columns_energy_levels(self):
         """Add nuclear energy level 'M' and 'm' columns using energy levels."""
-        if 'Energy Level (MeV)' not in self.df:
+        if "Energy Level (MeV)" not in self.df:
             return
         # add column of integer M giving the isomer level (0, 1, 2, ...)
-        self.df['M'] = [0] * len(self)
+        self.df["M"] = [0] * len(self)
         # add string m giving the isomer level name (e.g., '' or 'm' or 'm2')
-        self.df['m'] = [''] * len(self)
+        self.df["m"] = [""] * len(self)
         # loop over each isotope in the dataframe
-        A_Z = [(a, z) for a, z in zip(self['A'], self['Z'])]
+        A_Z = [(a, z) for a, z in zip(self["A"], self["Z"])]
         A_Z = set(A_Z)
         for a, z in A_Z:
-            isotope = (self['A'] == a) & (self['Z'] == z)
+            isotope = (self["A"] == a) & (self["Z"] == z)
             e_levels = []
             e_levels_nominal = []
-            for e_level in self['Energy Level (MeV)'][isotope]:
+            for e_level in self["Energy Level (MeV)"][isotope]:
                 if isinstance(e_level, uncertainties.core.Variable):
                     e_level_nominal = e_level.nominal_value
                 else:
@@ -530,71 +543,67 @@ def _add_columns_energy_levels(self):
                     e_levels_nominal.append(e_level_nominal)
             e_levels = sorted(e_levels)
             for M, e_level in enumerate(e_levels):
-                isomer = isotope & \
-                    (abs(self['Energy Level (MeV)'] - e_level) < 1e-10)
-                self.df.loc[isomer, 'M'] = M
+                isomer = isotope & (abs(self["Energy Level (MeV)"] - e_level) < 1e-10)
+                self.df.loc[isomer, "M"] = M
                 if M > 0:
                     if len(e_levels) > 2:
-                        self.df.loc[isomer, 'm'] = 'm{}'.format(M)
+                        self.df.loc[isomer, "m"] = "m{}".format(M)
                     else:
-                        self.df.loc[isomer, 'm'] = 'm'
+                        self.df.loc[isomer, "m"] = "m"
 
     def _add_units_uncertainties(self):
         """Add units and uncertainties with some columns as applicable."""
-        if 'Energy Level' in self.keys():
+        if "Energy Level" in self.keys():
             self._convert_column(
-                'Energy Level',
-                lambda x: _parse_float_uncertainty(x, ''))
-            self.df.rename(
-                columns={'Energy Level': 'Energy Level (MeV)'}, inplace=True)
+                "Energy Level", lambda x: _parse_float_uncertainty(x, "")
+            )
+            self.df.rename(columns={"Energy Level": "Energy Level (MeV)"}, inplace=True)
 
-        if 'Parent Energy Level' in self.keys():
-            self._convert_column_uncertainty('Parent Energy Level')
+        if "Parent Energy Level" in self.keys():
+            self._convert_column_uncertainty("Parent Energy Level")
             self.df.rename(
-                columns={'Parent Energy Level': 'Energy Level (MeV)'},
-                inplace=True)
-            self.df['Energy Level (MeV)'] *= 0.001
+                columns={"Parent Energy Level": "Energy Level (MeV)"}, inplace=True
+            )
+            self.df["Energy Level (MeV)"] *= 0.001
 
-        if 'Mass Excess' in self.keys():
-            self._convert_column_uncertainty('Mass Excess')
-        self.df.rename(
-            columns={'Mass Excess': 'Mass Excess (MeV)'}, inplace=True)
+        if "Mass Excess" in self.keys():
+            self._convert_column_uncertainty("Mass Excess")
+        self.df.rename(columns={"Mass Excess": "Mass Excess (MeV)"}, inplace=True)
 
-        self._convert_column('T1/2 (s)', float)
+        self._convert_column("T1/2 (s)", float)
 
-        if 'Abundance (%)' in self.keys():
-            self._convert_column_uncertainty('Abundance (%)')
+        if "Abundance (%)" in self.keys():
+            self._convert_column_uncertainty("Abundance (%)")
 
-        if 'Branching (%)' in self.keys():
+        if "Branching (%)" in self.keys():
             self._convert_column(
-                'Branching (%)',
-                lambda x: _parse_float_uncertainty(x, ''))
+                "Branching (%)", lambda x: _parse_float_uncertainty(x, "")
+            )
 
-        if 'Radiation Energy' in self.keys():
-            self._convert_column_uncertainty('Radiation Energy')
+        if "Radiation Energy" in self.keys():
+            self._convert_column_uncertainty("Radiation Energy")
             self.df.rename(
-                columns={'Radiation Energy': 'Radiation Energy (keV)'},
-                inplace=True)
+                columns={"Radiation Energy": "Radiation Energy (keV)"}, inplace=True
+            )
 
-        if 'Endpoint Energy' in self.keys():
-            self._convert_column_uncertainty('Endpoint Energy')
+        if "Endpoint Energy" in self.keys():
+            self._convert_column_uncertainty("Endpoint Energy")
             self.df.rename(
-                columns={'Endpoint Energy': 'Endpoint Energy (keV)'},
-                inplace=True)
+                columns={"Endpoint Energy": "Endpoint Energy (keV)"}, inplace=True
+            )
 
-        if 'Radiation Intensity (%)' in self.keys():
-            self._convert_column_uncertainty('Radiation Intensity (%)')
+        if "Radiation Intensity (%)" in self.keys():
+            self._convert_column_uncertainty("Radiation Intensity (%)")
 
-        if 'Dose' in self.keys():
-            self._convert_column_uncertainty('Dose')
-            self.df.rename(
-                columns={'Dose': 'Dose (MeV / Bq / s)'}, inplace=True)
+        if "Dose" in self.keys():
+            self._convert_column_uncertainty("Dose")
+            self.df.rename(columns={"Dose": "Dose (MeV / Bq / s)"}, inplace=True)
 
     def _convert_column(self, col, function):
         """Convert column from string to another type."""
         col_new = []
         for x in self[col]:
-            if x == '':
+            if x == "":
                 col_new.append(None)
             else:
                 col_new.append(function(x))
@@ -603,19 +612,30 @@ def _convert_column(self, col, function):
     def _convert_column_uncertainty(self, col):
         """Combine column and its uncertainty into one column."""
         col_new = []
-        for x, dx in zip(self[col], self[col + ' Unc.']):
+        for x, dx in zip(self[col], self[col + " Unc."]):
             x2 = _parse_float_uncertainty(x, dx)
             col_new.append(x2)
         self.df[col] = col_new
-        del self.df[col + ' Unc.']
+        del self.df[col + " Unc."]
 
     def _sort_columns(self):
         """Sort columns."""
         preferred_order = [
-            'Z', 'Element', 'A', 'm', 'M', 'N', 'JPi', 'T1/2',
-            'Energy Level (MeV)', 'Decay Mode', 'Branching (%)',
-            'Radiation', 'Radiation subtype',
-            'Radiation Energy (keV)', 'Radiation Intensity (%)',
+            "Z",
+            "Element",
+            "A",
+            "m",
+            "M",
+            "N",
+            "JPi",
+            "T1/2",
+            "Energy Level (MeV)",
+            "Decay Mode",
+            "Branching (%)",
+            "Radiation",
+            "Radiation subtype",
+            "Radiation Energy (keV)",
+            "Radiation Intensity (%)",
         ]
         new_cols = []
         for col in preferred_order:
@@ -666,19 +686,21 @@ class _NuclearWalletCardQuery(_NNDCQuery):
 
     """
 
-    _URL = 'https://www.nndc.bnl.gov/nudat2/sigma_searchi.jsp'
+    _URL = "https://www.nndc.bnl.gov/nudat2/sigma_searchi.jsp"
     _DATA = dict(_NNDCQuery._DATA)
-    _DATA.update({
-        'eled': 'disabled',    # E(level) condition on/off
-        'elmin': '0',          # E(level) min
-        'elmax': '40',         # E(level) max
-        'jled': 'disabled',    # J_pi(level) condition on/off
-        'jlv': '',             # J
-        'plv': 'ANY',          # parity
-        'ord': 'zalt',         # order file by Z, A, E(level), T1/2
-    })
+    _DATA.update(
+        {
+            "eled": "disabled",  # E(level) condition on/off
+            "elmin": "0",  # E(level) min
+            "elmax": "40",  # E(level) max
+            "jled": "disabled",  # J_pi(level) condition on/off
+            "jlv": "",  # J
+            "plv": "ANY",  # parity
+            "ord": "zalt",  # order file by Z, A, E(level), T1/2
+        }
+    )
     _ALLOWED_KEYWORDS = list(_NNDCQuery._ALLOWED_KEYWORDS)
-    _ALLOWED_KEYWORDS.extend(['elevel_range', 'decay', 'j', 'parity'])
+    _ALLOWED_KEYWORDS.extend(["elevel_range", "decay", "j", "parity"])
     _DUMMY_TEXT = """
 <html>
 <body>
@@ -695,34 +717,41 @@ def update(self, **kwargs):
         """Update the search criteria."""
         super().update(**kwargs)
         # handle decay mode
-        if 'decay' in kwargs:
-            if kwargs['decay'].lower() not in WALLET_DECAY_MODE:
+        if "decay" in kwargs:
+            if kwargs["decay"].lower() not in WALLET_DECAY_MODE:
                 raise NNDCInputError(
-                    'Decay mode must be one of {}, not {}'.format(
-                        WALLET_DECAY_MODE.keys(), kwargs['decay'].lower()))
-            warnings.warn('query kwarg "decay" may not be working on NNDC, ' +
-                          'and the user is advised to check the ' +
-                          '"Decay Mode" column of the resulting DataFrame')
-            self._data['dmed'] = 'enabled'
-            self._data['dmn'] = WALLET_DECAY_MODE[kwargs['decay'].lower()]
+                    "Decay mode must be one of {}, not {}".format(
+                        WALLET_DECAY_MODE.keys(), kwargs["decay"].lower()
+                    )
+                )
+            warnings.warn(
+                'query kwarg "decay" may not be working on NNDC, '
+                + "and the user is advised to check the "
+                + '"Decay Mode" column of the resulting DataFrame'
+            )
+            self._data["dmed"] = "enabled"
+            self._data["dmn"] = WALLET_DECAY_MODE[kwargs["decay"].lower()]
         # handle energy level condition
-        if 'elevel_range' in kwargs:
-            self._data['eled'] = 'enabled'
-            self._data['elmin'], self._data['elmax'] = \
-                _format_range(kwargs['elevel_range'])
-            if self._data['elmax'] == '':
-                self._data['elmax'] = '1000000000'
+        if "elevel_range" in kwargs:
+            self._data["eled"] = "enabled"
+            self._data["elmin"], self._data["elmax"] = _format_range(
+                kwargs["elevel_range"]
+            )
+            if self._data["elmax"] == "":
+                self._data["elmax"] = "1000000000"
         # handle spin and parity
-        if 'j' in kwargs:
-            self._data['jled'] = 'enabled'
-            self._data['jlv'] = kwargs['j']
-        if 'parity' in kwargs:
-            if kwargs['parity'].lower() not in PARITIES:
+        if "j" in kwargs:
+            self._data["jled"] = "enabled"
+            self._data["jlv"] = kwargs["j"]
+        if "parity" in kwargs:
+            if kwargs["parity"].lower() not in PARITIES:
                 raise NNDCInputError(
-                    'Parity must be one of {}, not {}'.format(
-                        PARITIES, kwargs['parity'].lower()))
-            self._data['jled'] = 'enabled'
-            self._data['plv'] = kwargs['parity'].upper()
+                    "Parity must be one of {}, not {}".format(
+                        PARITIES, kwargs["parity"].lower()
+                    )
+                )
+            self._data["jled"] = "enabled"
+            self._data["plv"] = kwargs["parity"].upper()
 
 
 def fetch_wallet_card(**kwargs):
@@ -806,22 +835,23 @@ class _DecayRadiationQuery(_NNDCQuery):
 
     """
 
-    _URL = 'https://www.nndc.bnl.gov/nudat2/dec_searchi.jsp'
+    _URL = "https://www.nndc.bnl.gov/nudat2/dec_searchi.jsp"
     _DATA = dict(_NNDCQuery._DATA)
-    _DATA.update({
-        'rted': 'enabled',     # radiation type condition on/off
-        'rtn': 'ANY',          # radiation type: 'ANY' = any, 'G' = gamma
-        'reed': 'disabled',    # radiation energy condition on/off
-        'remin': '0',          # radiation energy min (keV)
-        'remax': '10000',      # radiation energy max (keV)
-        'ried': 'disabled',    # radiation intensity condition on/off
-        'rimin': '0',          # radiation intensity min (%)
-        'rimax': '100',        # radiation intensity max (%)
-        'ord': 'zate',         # order file by Z, A, T1/2, E
-    })
+    _DATA.update(
+        {
+            "rted": "enabled",  # radiation type condition on/off
+            "rtn": "ANY",  # radiation type: 'ANY' = any, 'G' = gamma
+            "reed": "disabled",  # radiation energy condition on/off
+            "remin": "0",  # radiation energy min (keV)
+            "remax": "10000",  # radiation energy max (keV)
+            "ried": "disabled",  # radiation intensity condition on/off
+            "rimin": "0",  # radiation intensity min (%)
+            "rimax": "100",  # radiation intensity max (%)
+            "ord": "zate",  # order file by Z, A, T1/2, E
+        }
+    )
     _ALLOWED_KEYWORDS = list(_NNDCQuery._ALLOWED_KEYWORDS)
-    _ALLOWED_KEYWORDS.extend([
-        'elevel_range', 'decay', 'type', 'e_range', 'i_range'])
+    _ALLOWED_KEYWORDS.extend(["elevel_range", "decay", "type", "e_range", "i_range"])
     _DUMMY_TEXT = """
 <html>
 <body>
@@ -837,45 +867,46 @@ def update(self, **kwargs):
         """Update the search criteria."""
         super().update(**kwargs)
         # handle decay mode
-        if 'decay' in kwargs:
-            if kwargs['decay'].lower() not in DECAYRAD_DECAY_MODE:
+        if "decay" in kwargs:
+            if kwargs["decay"].lower() not in DECAYRAD_DECAY_MODE:
                 raise NNDCInputError(
-                    'Decay mode must be one of {}, not {}'.format(
-                        DECAYRAD_DECAY_MODE.keys(), kwargs['decay'].lower()))
-            self._data['dmed'] = 'enabled'
-            self._data['dmn'] = DECAYRAD_DECAY_MODE[kwargs['decay'].lower()]
+                    "Decay mode must be one of {}, not {}".format(
+                        DECAYRAD_DECAY_MODE.keys(), kwargs["decay"].lower()
+                    )
+                )
+            self._data["dmed"] = "enabled"
+            self._data["dmn"] = DECAYRAD_DECAY_MODE[kwargs["decay"].lower()]
         # handle radiation type
-        if 'type' in kwargs:
-            if kwargs['type'].lower() not in DECAYRAD_RADIATION_TYPE:
+        if "type" in kwargs:
+            if kwargs["type"].lower() not in DECAYRAD_RADIATION_TYPE:
                 raise NNDCInputError(
-                    'Radiation type must be one of {}, not {}'.format(
-                        DECAYRAD_RADIATION_TYPE.keys(),
-                        kwargs['type'].lower()))
-            self._data['rted'] = 'enabled'
-            self._data['rtn'] = DECAYRAD_RADIATION_TYPE[kwargs['type'].lower()]
+                    "Radiation type must be one of {}, not {}".format(
+                        DECAYRAD_RADIATION_TYPE.keys(), kwargs["type"].lower()
+                    )
+                )
+            self._data["rted"] = "enabled"
+            self._data["rtn"] = DECAYRAD_RADIATION_TYPE[kwargs["type"].lower()]
         # handle energy level condition
         self.elevel_range = (0, 1e9)
-        if 'elevel_range' in kwargs:
-            x = _format_range(kwargs['elevel_range'])
+        if "elevel_range" in kwargs:
+            x = _format_range(kwargs["elevel_range"])
             try:
                 x0 = float(x[0])
             except ValueError:
-                x0 = 0.
+                x0 = 0.0
             try:
                 x1 = float(x[1])
             except ValueError:
                 x1 = 1e9
             self.elevel_range = (x0, x1)
         # handle radiation energy range
-        if 'e_range' in kwargs:
-            self._data['reed'] = 'enabled'
-            self._data['remin'], self._data['remax'] = \
-                _format_range(kwargs['e_range'])
+        if "e_range" in kwargs:
+            self._data["reed"] = "enabled"
+            self._data["remin"], self._data["remax"] = _format_range(kwargs["e_range"])
         # handle radiation intensity range
-        if 'i_range' in kwargs:
-            self._data['ried'] = 'enabled'
-            self._data['rimin'], self._data['rimax'] = \
-                _format_range(kwargs['i_range'])
+        if "i_range" in kwargs:
+            self._data["ried"] = "enabled"
+            self._data["rimin"], self._data["rimax"] = _format_range(kwargs["i_range"])
 
 
 def fetch_decay_radiation(**kwargs):
@@ -919,8 +950,7 @@ def fetch_decay_radiation(**kwargs):
 
     query = _DecayRadiationQuery(**kwargs)
     # apply elevel_range filter (hack around the web API)
-    elevel = query.df['Energy Level (MeV)']
-    keep = (elevel >= query.elevel_range[0]) & \
-           (elevel <= query.elevel_range[1])
+    elevel = query.df["Energy Level (MeV)"]
+    keep = (elevel >= query.elevel_range[0]) & (elevel <= query.elevel_range[1])
     query.df = query.df[keep]
     return query.df
diff --git a/becquerel/tools/wallet_cache.py b/becquerel/tools/wallet_cache.py
index a1c54172..91f7bc82 100644
--- a/becquerel/tools/wallet_cache.py
+++ b/becquerel/tools/wallet_cache.py
@@ -1,14 +1,10 @@
 """A cache of all NNDC wallet card data."""
 
-from __future__ import print_function
 from future.builtins import super
 import pandas as pd
 import uncertainties
 from . import nndc
 from . import df_cache
-from ..core.utils import isstring
-
-# pylint: disable=no-self-use
 
 
 def convert_float_ufloat(x):
@@ -18,16 +14,16 @@ def convert_float_ufloat(x):
       x: a string giving the number
     """
 
-    if isstring(x):
-        if '+/-' in x:
-            tokens = x.split('+/-')
+    if isinstance(x, str):
+        if "+/-" in x:
+            tokens = x.split("+/-")
             return uncertainties.ufloat(float(tokens[0]), float(tokens[1]))
-        if x == '':
+        if x == "":
             return None
     return float(x)
 
 
-def format_ufloat(x, fmt='{:.12f}'):
+def format_ufloat(x, fmt="{:.12f}"):
     """Convert ufloat to a string, including None ('') and NaN.
 
     Args:
@@ -35,19 +31,19 @@ def format_ufloat(x, fmt='{:.12f}'):
     """
 
     if x is None:
-        return ''
+        return ""
     return fmt.format(x)
 
 
 class WalletCardCache(df_cache.DataFrameCache):
     """A cache of all isotope wallet cards from NNDC."""
 
-    name = 'all_wallet_cards'
+    name = "all_wallet_cards"
 
     def write_file(self):
         """Format ufloat columns before writing so they keep precision."""
 
-        for col in ['Abundance (%)', 'Mass Excess (MeV)']:
+        for col in ["Abundance (%)", "Mass Excess (MeV)"]:
             self.df[col] = self.df[col].apply(format_ufloat)
         super().write_file()
 
@@ -55,7 +51,7 @@ def read_file(self):
         """Ensure some columns are properly converted to float/ufloat."""
 
         super().read_file()
-        for col in ['Abundance (%)', 'Mass Excess (MeV)']:
+        for col in ["Abundance (%)", "Mass Excess (MeV)"]:
             self.df[col] = self.df[col].apply(convert_float_ufloat)
 
     def fetch(self):
diff --git a/becquerel/tools/xcom.py b/becquerel/tools/xcom.py
index 0a127339..4e04d7a0 100644
--- a/becquerel/tools/xcom.py
+++ b/becquerel/tools/xcom.py
@@ -9,88 +9,95 @@
 
 """
 
-from __future__ import print_function
 import requests
 import pandas as pd
+from collections.abc import Iterable
 from . import element
-from ..core.utils import isstring, Iterable
 
 # Dry air relative weights taken from:
 # http://www.engineeringtoolbox.com/air-composition-d_212.html
 MIXTURE_AIR_DRY = [
-    'N2 75.47',
-    'O2 23.20',
-    'Ar 0.933',
-    'CO2 0.03',
+    "N2 75.47",
+    "O2 23.20",
+    "Ar 0.933",
+    "CO2 0.03",
 ]
 
 # Seawater composition for 3.5% salinity taken from:
 # https://en.wikipedia.org/wiki/Seawater#Compositional_differences_from_freshwater
 MIXTURE_SEAWATER = [
-    'O 85.84',
-    'H 10.82',
-    'Cl 1.94',
-    'Na 1.08',
-    'Mg 0.1292',
-    'S 0.091',
-    'Ca 0.04',
-    'K 0.04',
-    'Br 0.0067',
-    'C 0.0028',
+    "O 85.84",
+    "H 10.82",
+    "Cl 1.94",
+    "Na 1.08",
+    "Mg 0.1292",
+    "S 0.091",
+    "Ca 0.04",
+    "K 0.04",
+    "Br 0.0067",
+    "C 0.0028",
 ]
 
 # typical Portland cement values taken from:
 # http://matse1.matse.illinois.edu/concrete/prin.html
 MIXTURE_PORTLAND_CEMENT = [
-    'Ca3SiO5 50',
-    'Ca2SiO4 25',
-    'Ca3Al2O6 10',
-    'Ca4Al2Fe2O10 10',
-    'CaSO6H4 5',
+    "Ca3SiO5 50",
+    "Ca2SiO4 25",
+    "Ca3Al2O6 10",
+    "Ca4Al2Fe2O10 10",
+    "CaSO6H4 5",
 ]
 
 
 # base URL for three scripts on NIST where data can be queried
-_URL = 'https://physics.nist.gov/cgi-bin/Xcom/xcom3_'
+_URL = "https://physics.nist.gov/cgi-bin/Xcom/xcom3_"
 
 
 # Dictionary of data that will be posted to URL
 _DATA = {
-    'NumAdd': '1',          # always '1'?
-    'Energies': '',         # additional energies separated by ; (MeV)
-    'WindowXmin': '0.001',  # lower limit of energy grid (MeV)
-    'WindowXmax': '100',    # upper limit of energy grid (MeV)
-    'Output': '',           # 'on' for standard energy grid
-    'OutOpt': 'PIC',        # return cross sections in cm^2/g
-    'ResizeFlag': 'on',     # seems to determine whether Xmin and Xmax are used
+    "NumAdd": "1",  # always '1'?
+    "Energies": "",  # additional energies separated by ; (MeV)
+    "WindowXmin": "0.001",  # lower limit of energy grid (MeV)
+    "WindowXmax": "100",  # upper limit of energy grid (MeV)
+    "Output": "",  # 'on' for standard energy grid
+    "OutOpt": "PIC",  # return cross sections in cm^2/g
+    "ResizeFlag": "on",  # seems to determine whether Xmin and Xmax are used
 }
 
 # abbreviated names for the table columns (these are used in the dataframe)
-COLUMNS_SHORT = ['energy', 'coherent', 'incoherent', 'photoelec',
-                 'pair_nuc', 'pair_elec', 'total_w_coh', 'total_wo_coh']
+COLUMNS_SHORT = [
+    "energy",
+    "coherent",
+    "incoherent",
+    "photoelec",
+    "pair_nuc",
+    "pair_elec",
+    "total_w_coh",
+    "total_wo_coh",
+]
 
 # medium length names for the table columns
 COLUMNS_MEDIUM = {
-    'energy': 'Energy',
-    'coherent': 'Coherent',
-    'incoherent': 'Incoherent',
-    'photoelec': 'Photoelectric',
-    'pair_nuc': 'Pair production (nuclear)',
-    'pair_elec': 'Pair Production (electron)',
-    'total_w_coh': 'Total with coherent',
-    'total_wo_coh': 'Total without coherent'
+    "energy": "Energy",
+    "coherent": "Coherent",
+    "incoherent": "Incoherent",
+    "photoelec": "Photoelectric",
+    "pair_nuc": "Pair production (nuclear)",
+    "pair_elec": "Pair Production (electron)",
+    "total_w_coh": "Total with coherent",
+    "total_wo_coh": "Total without coherent",
 }
 
 # verbose names for the table columns
 COLUMNS_LONG = {
-    'energy': 'Photon Energy',
-    'coherent': 'Coherent Scattering',
-    'incoherent': 'Incoherent Scattering',
-    'photoelec': 'Photoelectric Absorption',
-    'pair_nuc': 'Pair Production in Nuclear Field',
-    'pair_elec': 'Pair Production in Electron Field',
-    'total_w_coh': 'Total Attenuation with Coherent Scattering',
-    'total_wo_coh': 'Total Attenuation without Coherent Scattering'
+    "energy": "Photon Energy",
+    "coherent": "Coherent Scattering",
+    "incoherent": "Incoherent Scattering",
+    "photoelec": "Photoelectric Absorption",
+    "pair_nuc": "Pair Production in Nuclear Field",
+    "pair_elec": "Pair Production in Electron Field",
+    "total_w_coh": "Total Attenuation with Coherent Scattering",
+    "total_wo_coh": "Total Attenuation without Coherent Scattering",
 }
 
 
@@ -166,11 +173,11 @@ def __init__(self, arg, **kwargs):
         self._text = None
         self.df = None
         self._data = dict(_DATA)
-        self._method = ''
+        self._method = ""
         # determine which kind of argument 'arg' is (symbol, Z, compound, mix)
         kwargs.update(_XCOMQuery._argument_type(arg))
         self.update(**kwargs)
-        if kwargs.get('perform', True):
+        if kwargs.get("perform", True):
             self.perform()
 
     def __len__(self):
@@ -201,20 +208,21 @@ def __format__(self, formatstr):
     @staticmethod
     def _argument_type(arg):
         """Determine if argument is a symbol, Z, compound, or mixture."""
-        if isstring(arg):
+        if isinstance(arg, str):
             if arg.isdigit():
-                return {'z': arg}
+                return {"z": arg}
             elif arg.lower() in [s.lower() for s in element.SYMBOLS]:
-                return {'symbol': arg}
+                return {"symbol": arg}
             else:
-                return {'compound': arg}
+                return {"compound": arg}
         elif isinstance(arg, int):
-            return {'z': arg}
+            return {"z": arg}
         elif isinstance(arg, Iterable):
-            return {'mixture': arg}
+            return {"mixture": arg}
         raise XCOMInputError(
-            'Cannot determine if argument {}'.format(arg) +
-            ' is a symbol, Z, compound, or mixture')
+            "Cannot determine if argument {}".format(arg)
+            + " is a symbol, Z, compound, or mixture"
+        )
 
     @staticmethod
     def _check_z(zstr):
@@ -222,38 +230,44 @@ def _check_z(zstr):
         zint = int(zstr)
         if zint < 1 or zint > 100:
             raise XCOMInputError(
-                'XCOM only supports Z from 1 to 100 (z={})'.format(zstr))
+                "XCOM only supports Z from 1 to 100 (z={})".format(zstr)
+            )
 
     @staticmethod
     def _check_compound(formula):
         """Check whether the compound is valid. Raise XCOMInputError if not."""
         if not formula.isalnum():
-            raise XCOMInputError('Formula not valid: {}'.format(formula))
+            raise XCOMInputError("Formula not valid: {}".format(formula))
 
     @staticmethod
     def _check_mixture(formulae):
         """Check whether the mixture is valid. Raise XCOMInputError if not."""
         if not isinstance(formulae, Iterable):
             raise XCOMInputError(
-                'Mixture formulae must be an iterable: {}'.format(formulae))
+                "Mixture formulae must be an iterable: {}".format(formulae)
+            )
         for formula in formulae:
             try:
                 compound, weight = formula.split()
             except AttributeError:
                 raise XCOMInputError(
                     'Mixture formulae "{}" line "{}" must be a string'.format(
-                        formulae, formula))
+                        formulae, formula
+                    )
+                )
             except ValueError:
                 raise XCOMInputError(
                     'Mixture formulae "{}" line "{}" must split into 2'.format(
-                        formulae, formula))
+                        formulae, formula
+                    )
+                )
             _XCOMQuery._check_compound(compound)
             try:
                 float(weight)
             except (ValueError, TypeError):
                 raise XCOMInputError(
-                    'Mixture formulae "{}" has bad weight "{}"'.format(
-                        formulae, weight))
+                    'Mixture formulae "{}" has bad weight "{}"'.format(formulae, weight)
+                )
 
     def update(self, **kwargs):
         """Update the search criteria.
@@ -282,112 +296,134 @@ def update(self, **kwargs):
         # check for valid keywords
         for kwarg in kwargs:
             if kwarg not in [
-                    'symbol', 'z', 'compound', 'mixture',
-                    'e_range_kev', 'energies_kev', 'perform']:
+                "symbol",
+                "z",
+                "compound",
+                "mixture",
+                "e_range_kev",
+                "energies_kev",
+                "perform",
+            ]:
                 raise XCOMInputError('Unknown keyword: "{}"'.format(kwarg))
 
         # determine the search method (element, compound, or mixture)
-        if 'symbol' in kwargs:
-            self._method = '1'
-            sym = kwargs['symbol']
-            self._data['ZSym'] = sym
-        elif 'z' in kwargs:
-            self._method = '1'
-            znum = kwargs['z']
+        if "symbol" in kwargs:
+            self._method = "1"
+            sym = kwargs["symbol"]
+            self._data["ZSym"] = sym
+        elif "z" in kwargs:
+            self._method = "1"
+            znum = kwargs["z"]
             _XCOMQuery._check_z(znum)
-            self._data['ZNum'] = '{:d}'.format(int(znum))
-        elif 'compound' in kwargs:
+            self._data["ZNum"] = "{:d}".format(int(znum))
+        elif "compound" in kwargs:
             # convert compound to mixture to avoid occasional problem
             # with XCOM compound queries (see issue #76)
             # self._method = '2'
-            self._method = '3'
-            formula = kwargs['compound']
+            self._method = "3"
+            formula = kwargs["compound"]
             _XCOMQuery._check_compound(formula)
-            formulae = [formula + ' 1']
+            formulae = [formula + " 1"]
             _XCOMQuery._check_mixture(formulae)
-            formulae = '\r\n'.join(formulae)
+            formulae = "\r\n".join(formulae)
             # self._data['Formula'] = formula
-            self._data['Formulae'] = formulae
-        elif 'mixture' in kwargs:
-            self._method = '3'
-            formulae = kwargs['mixture']
+            self._data["Formulae"] = formulae
+        elif "mixture" in kwargs:
+            self._method = "3"
+            formulae = kwargs["mixture"]
             _XCOMQuery._check_mixture(formulae)
-            formulae = '\r\n'.join(formulae)
-            self._data['Formulae'] = formulae
+            formulae = "\r\n".join(formulae)
+            self._data["Formulae"] = formulae
 
         # include standard grid of energies
-        if 'e_range_kev' in kwargs:
-            if not isinstance(kwargs['e_range_kev'], Iterable):
+        if "e_range_kev" in kwargs:
+            if not isinstance(kwargs["e_range_kev"], Iterable):
                 raise XCOMInputError(
-                    'XCOM e_range_kev must be iterable of length 2: {}'.format(
-                        kwargs['e_range_kev']))
-            if len(kwargs['e_range_kev']) != 2:
+                    "XCOM e_range_kev must be iterable of length 2: {}".format(
+                        kwargs["e_range_kev"]
+                    )
+                )
+            if len(kwargs["e_range_kev"]) != 2:
                 raise XCOMInputError(
-                    'XCOM e_range_kev must be iterable of length 2: {}'.format(
-                        kwargs['e_range_kev']))
-            if kwargs['e_range_kev'][0] < 1:
+                    "XCOM e_range_kev must be iterable of length 2: {}".format(
+                        kwargs["e_range_kev"]
+                    )
+                )
+            if kwargs["e_range_kev"][0] < 1:
                 raise XCOMInputError(
-                    'XCOM e_range_kev[0] must be >= 1 keV: {}'.format(
-                        kwargs['e_range_kev'][0]))
-            if kwargs['e_range_kev'][1] > 1e8:
+                    "XCOM e_range_kev[0] must be >= 1 keV: {}".format(
+                        kwargs["e_range_kev"][0]
+                    )
+                )
+            if kwargs["e_range_kev"][1] > 1e8:
                 raise XCOMInputError(
-                    'XCOM e_range_kev[1] must be <= 1E8 keV: {}'.format(
-                        kwargs['e_range_kev'][1]))
-            if kwargs['e_range_kev'][0] >= kwargs['e_range_kev'][1]:
+                    "XCOM e_range_kev[1] must be <= 1E8 keV: {}".format(
+                        kwargs["e_range_kev"][1]
+                    )
+                )
+            if kwargs["e_range_kev"][0] >= kwargs["e_range_kev"][1]:
                 raise XCOMInputError(
-                    'XCOM e_range_kev[0] must be < e_range_kev[1]: {}'.format(
-                        kwargs['e_range_kev']))
-            self._data['WindowXmin'] = '{:.6f}'.format(
-                kwargs['e_range_kev'][0] / 1000.)
-            self._data['WindowXmax'] = '{:.6f}'.format(
-                kwargs['e_range_kev'][1] / 1000.)
-            self._data['Output'] = 'on'
+                    "XCOM e_range_kev[0] must be < e_range_kev[1]: {}".format(
+                        kwargs["e_range_kev"]
+                    )
+                )
+            self._data["WindowXmin"] = "{:.6f}".format(
+                kwargs["e_range_kev"][0] / 1000.0
+            )
+            self._data["WindowXmax"] = "{:.6f}".format(
+                kwargs["e_range_kev"][1] / 1000.0
+            )
+            self._data["Output"] = "on"
 
         # additional energies
-        if 'energies_kev' in kwargs:
-            if not isinstance(kwargs['energies_kev'], Iterable):
+        if "energies_kev" in kwargs:
+            if not isinstance(kwargs["energies_kev"], Iterable):
                 raise XCOMInputError(
-                    'XCOM energies_kev must be an iterable: {}'.format(
-                        kwargs['energies_kev']))
-            for energy in kwargs['energies_kev']:
+                    "XCOM energies_kev must be an iterable: {}".format(
+                        kwargs["energies_kev"]
+                    )
+                )
+            for energy in kwargs["energies_kev"]:
                 if energy < 1 or energy > 1e8:
                     raise XCOMInputError(
-                        'XCOM energy must be >= 1 and <= 1E8 keV: {}'.format(
-                            energy))
-            self._data['Energies'] = ';'.join([
-                '{:.6f}'.format(erg / 1000.)
-                for erg in kwargs['energies_kev']])
+                        "XCOM energy must be >= 1 and <= 1E8 keV: {}".format(energy)
+                    )
+            self._data["Energies"] = ";".join(
+                ["{:.6f}".format(erg / 1000.0) for erg in kwargs["energies_kev"]]
+            )
 
     def _request(self):
         """Request data table from the URL."""
         self._req = requests.post(self._url + self._method, data=self._data)
-        if not self._req.ok or self._req.reason != 'OK' or \
-                self._req.status_code != 200:
-            raise XCOMRequestError(
-                'XCOM Request failed: reason={}, status_code={}'.format(
-                    self._req.reason, self._req.status_code))
-        if 'Error' in self._req.text:
+        if not self._req.ok or self._req.reason != "OK" or self._req.status_code != 200:
             raise XCOMRequestError(
-                'XCOM returned an error:\n{}'.format(self._req.text))
+                "XCOM Request failed: reason={}, status_code={}".format(
+                    self._req.reason, self._req.status_code
+                )
+            )
+        if "Error" in self._req.text:
+            raise XCOMRequestError("XCOM returned an error:\n{}".format(self._req.text))
 
     def _parse_text(self):
         """Parse table contained in the text into a dictionary."""
         self._text = str(self._req.text)
         if len(self._text) == 0:
-            raise XCOMRequestError('XCOM returned no text')
+            raise XCOMRequestError("XCOM returned no text")
         tables = pd.read_html(self._text, header=0, skiprows=[1, 2])
         if len(tables) != 1:
-            raise XCOMRequestError('More than one HTML table found')
+            raise XCOMRequestError("More than one HTML table found")
         self.df = tables[0]
         if len(self.df.keys()) != 1 + len(COLUMNS_SHORT):
             raise XCOMRequestError(
-                'Found {} columns but expected {}'.format(
-                    len(self.df.keys()), 1 + len(COLUMNS_SHORT)))
+                "Found {} columns but expected {}".format(
+                    len(self.df.keys()), 1 + len(COLUMNS_SHORT)
+                )
+            )
         # remove 'edge' column
         self.df = self.df[self.df.keys()[1:]]
         self.df.columns = COLUMNS_SHORT
         if len(self) == 0:
-            raise XCOMRequestError('Parsed DataFrame is empty')
+            raise XCOMRequestError("Parsed DataFrame is empty")
 
     def perform(self):
         """Perform the query.
@@ -401,17 +437,18 @@ def perform(self):
             a problem parsing the data.
 
         """
-        if self._method not in ['1', '2', '3']:
+        if self._method not in ["1", "2", "3"]:
             raise XCOMInputError(
-                'XCOM search method not set. Need to call update() method.')
-        if self._data['Energies'] == '' and self._data['Output'] == '':
-            raise XCOMInputError('No energies_kev or e_range_kev requested.')
+                "XCOM search method not set. Need to call update() method."
+            )
+        if self._data["Energies"] == "" and self._data["Output"] == "":
+            raise XCOMInputError("No energies_kev or e_range_kev requested.")
         # submit the query
         self._request()
         # package the output into a pandas DataFrame
         self._parse_text()
         # convert energy from MeV to keV
-        self.df['energy'] *= 1000.
+        self.df["energy"] *= 1000.0
 
 
 def fetch_xcom_data(arg, **kwargs):
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..9cb333c4
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,16 @@
+[tool.black]
+target-version = ['py36', 'py37', 'py38']
+include = '\.pyi?$'
+exclude = '''
+/(
+    \.eggs
+  | \.git
+  | \.pytest_cache
+  | htmlcov
+  | figures
+  | build
+  | dist
+  # Specific to this package
+  | becquerel.egg-info
+)/
+'''
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 8967ae41..d441277b 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,6 +1,7 @@
 -r requirements.txt
 flake8
+black>=20.8b1
 pydocstyle
-pylint
 pytest>=3.0.0
 pytest-cov
+pytest-black
diff --git a/requirements.txt b/requirements.txt
index 98ca97fa..70c782ce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@ lmfit
 lxml
 matplotlib
 numba>=0.47.0
-llvmlite==0.31.0; python_version < "3"
+llvmlite
 numpy
 pandas
 python-dateutil
diff --git a/setup.cfg b/setup.cfg
index a64cceb7..32c726c6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,7 +2,7 @@
 test=pytest
 
 [tool:pytest]
-addopts = --cov=becquerel --cov-report term --cov-report html:htmlcov -m "not plottest"
+addopts = --black --cov=becquerel --cov-report term --cov-report html:htmlcov -m "not plottest"
 markers =
     webtest: test requires internet connection
     plottest: test will produce plot figures
diff --git a/setup.py b/setup.py
index 6cd096b3..69634f5e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,18 +1,23 @@
 #!/usr/bin/env python
 """Becquerel: Tools for radiation spectral analysis."""
 
-from __future__ import print_function
+import sys
+import site
 from setuptools import setup, find_packages
 
-NAME = 'becquerel'
+# Enables --editable install with --user
+# https://github.com/pypa/pip/issues/7953
+site.ENABLE_USER_SITE = "--user" in sys.argv[1:]
+
+NAME = "becquerel"
 
 MAJOR = 0
 MINOR = 3
 MICRO = 0
-VERSION = '{}.{}.{}'.format(MAJOR, MINOR, MICRO)
+VERSION = f"{MAJOR}.{MINOR}.{MICRO}"
 
-DESCRIPTION = __doc__.split('\n')[0].split(': ')[-1]
-URL = 'https://github.com/lbl-anp/becquerel'
+DESCRIPTION = __doc__.split("\n")[0].split(": ")[-1]
+URL = "https://github.com/lbl-anp/becquerel"
 MAINTAINER = "The Becquerel Development Team"
 EMAIL = "becquerel-dev@lbl.gov"
 
@@ -23,58 +28,61 @@
 License :: Other/Proprietary License
 Operating System :: OS Independent
 Programming Language :: Python
-Programming Language :: Python :: 2
 Programming Language :: Python :: 3
+Programming Language :: Python :: 3 :: Only
+Programming Language :: Python :: 3.6
+Programming Language :: Python :: 3.7
+Programming Language :: Python :: 3.8
 Topic :: Scientific/Engineering
 Topic :: Scientific/Engineering :: Physics
 """
 
-with open('README.md', 'r') as fh:
+with open("README.md", "r") as fh:
     README = fh.read()
 # remove package title from description
-README = '\n'.join(README.split('\n')[2:])
+README = "\n".join(README.split("\n")[2:])
 
-with open('CONTRIBUTING.md', 'r') as fh:
+with open("CONTRIBUTING.md", "r") as fh:
     CONTRIBUTING = fh.read()
 
-with open('LICENSE.txt', 'r') as fh:
+with open("LICENSE.txt", "r") as fh:
     LICENSE = fh.read()
 
-with open('requirements.txt', 'r') as fh:
+with open("requirements.txt", "r") as fh:
     REQUIREMENTS = fh.read()
 
 # make long description from README and CONTRIBUTING
 # but move copyright notice to the end
-LONG_DESCRIPTION, COPYRIGHT = README.split('## Copyright Notice')
-LONG_DESCRIPTION += '\n' + CONTRIBUTING
-LONG_DESCRIPTION += '\n' + '## Copyright Notice' + COPYRIGHT
+LONG_DESCRIPTION, COPYRIGHT = README.split("## Copyright Notice")
+LONG_DESCRIPTION += "\n" + CONTRIBUTING
+LONG_DESCRIPTION += "\n" + "## Copyright Notice" + COPYRIGHT
 
 # write metadata to a file that will be imported by becquerel
-with open('becquerel/__metadata__.py', 'w') as f:
+with open("becquerel/__metadata__.py", "w") as f:
     print('"""Becquerel package metadata."""', file=f)
-    print('', file=f)
-    print('__description__ = "{}"'.format(DESCRIPTION), file=f)
-    print('__url__ = "{}"'.format(URL), file=f)
-    print('__version__ = "{}"'.format(VERSION), file=f)
-    print('__license__ = """{}"""'.format(LICENSE), file=f)
-    print('__copyright__ = """{}"""'.format(COPYRIGHT), file=f)
+    print("", file=f)
+    print(f'__description__ = "{DESCRIPTION}"', file=f)
+    print(f'__url__ = "{URL}"', file=f)
+    print(f'__version__ = "{VERSION}"', file=f)
+    print(f'__license__ = """{LICENSE}"""', file=f)
+    print(f'__copyright__ = """{COPYRIGHT}"""', file=f)
 
 setup(
     name=NAME,
     version=VERSION,
     description=DESCRIPTION,
     long_description=LONG_DESCRIPTION,
-    long_description_content_type='text/markdown',
+    long_description_content_type="text/markdown",
     url=URL,
-    download_url=URL + '/releases',
+    download_url=URL + "/releases",
     maintainer=MAINTAINER,
     maintainer_email=EMAIL,
-    classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
-    platforms='any',
+    classifiers=[_f for _f in CLASSIFIERS.split("\n") if _f],
+    platforms="any",
     packages=find_packages(),
-    python_requires='>=2.6',
-    install_requires=[_f for _f in REQUIREMENTS.split('\n') if _f],
-    setup_requires=['pytest-runner'],
-    tests_require=['pytest', 'pytest-cov'],
+    python_requires=">=3.6",
+    install_requires=[_f for _f in REQUIREMENTS.split("\n") if _f],
+    setup_requires=["pytest-runner"],
+    tests_require=["pytest", "pytest-cov"],
     license="Other/Proprietary License (see LICENSE.txt)",
 )


Test patch:
diff --git a/.github/workflows/pip_build_test.yaml b/.github/workflows/pip_build_test.yaml
new file mode 100644
index 00000000..e0f6a5fd
--- /dev/null
+++ b/.github/workflows/pip_build_test.yaml
@@ -0,0 +1,45 @@
+# This workflow will install dependencies, install becquerel and run tests.
+# It runs on multiple python3 versions on macOS and Ubuntu.
+# It runs when a commit (merge) is push to the main (default) branch or for updates to a PR targeting main.
+# Based on: https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
+
+name: Python package
+
+on:
+
+  # When to test
+  push:
+    branches: [ $default-branch ]
+  pull_request:
+
+jobs:
+  pip_build_test:
+
+    # OS and python combos to test
+    # Ubuntu 20.04 is specified because GHA are currently transitioning from
+    # 18.04 to 20.04 and the `latest` tag could run on either.
+    strategy:
+      matrix:
+        os: [ 'macos-latest', 'ubuntu-20.04', 'windows-latest' ]
+        python-version: ['3.6', '3.7', '3.8']
+    runs-on: ${{ matrix.os }}
+
+    # Installation and testing
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip setuptools wheel
+          pip install --only-binary=numpy,scipy numpy scipy
+          pip install -r requirements-dev.txt
+      - name: Install package
+        run: |
+          pip install -e .
+      - name: Test with pytest
+        run: |
+          python -c 'import becquerel;'
+          pytest
diff --git a/tests/autocal_test.py b/tests/autocal_test.py
index 0a4c16c4..065c68d6 100644
--- a/tests/autocal_test.py
+++ b/tests/autocal_test.py
@@ -8,20 +8,19 @@
 
 
 # read in spectra
-filename1 = os.path.join(
-    os.path.dirname(bq.__file__), '../tests/samples/sim_spec.csv')
+filename1 = os.path.join(os.path.dirname(bq.__file__), "../tests/samples/sim_spec.csv")
 filename2 = os.path.join(
-    os.path.dirname(bq.__file__),
-    '../tests/samples/Mendocino_07-10-13_Acq-10-10-13.Spe')
+    os.path.dirname(bq.__file__), "../tests/samples/Mendocino_07-10-13_Acq-10-10-13.Spe"
+)
 filename3 = os.path.join(
-    os.path.dirname(bq.__file__), '../tests/samples/nai_detector.csv')
-filename4 = os.path.join(
-    os.path.dirname(bq.__file__), '../tests/samples/SGM102432.csv')
+    os.path.dirname(bq.__file__), "../tests/samples/nai_detector.csv"
+)
+filename4 = os.path.join(os.path.dirname(bq.__file__), "../tests/samples/SGM102432.csv")
 
 counts = []
-with open(filename1, 'r') as f:
+with open(filename1, "r") as f:
     for line in f:
-        tokens = line.strip().split(',')
+        tokens = line.strip().split(",")
         if len(tokens) == 2:
             counts.append(float(tokens[1]))
 spec1 = bq.Spectrum(counts=counts)
@@ -29,25 +28,24 @@
 spec2 = bq.Spectrum.from_file(filename2)
 
 counts = []
-with open(filename3, 'r') as f:
+with open(filename3, "r") as f:
     for line in f:
-        tokens = line.strip().split(',')
+        tokens = line.strip().split(",")
         if len(tokens) == 2:
             counts.append(float(tokens[1]))
 spec3 = bq.Spectrum(counts=counts)
 
 counts = []
-with open(filename4, 'r') as f:
+with open(filename4, "r") as f:
     for line in f:
-        tokens = line.strip().split(',')
+        tokens = line.strip().split(",")
         if len(tokens) == 2:
             counts.append(float(tokens[1]))
 spec4 = bq.Spectrum(counts=counts)
 
 
 REQUIRED = [609.32, 1460.82, 2614.3]
-OPTIONAL = [
-    238.63, 338.32, 351.93, 911.20, 1120.294, 1620.50, 1764.49, 2118.514]
+OPTIONAL = [238.63, 338.32, 351.93, 911.20, 1120.294, 1620.50, 1764.49, 2118.514]
 
 
 def test_peakfilter_base():
@@ -68,7 +66,7 @@ def test_peakfilter_exceptions():
         bq.PeakFilter(700, 20, fwhm_at_0=-15)
 
 
-@pytest.mark.parametrize('cls', [bq.GaussianPeakFilter])
+@pytest.mark.parametrize("cls", [bq.GaussianPeakFilter])
 def test_peakfilter(cls):
     """Test basic functionality of PeakFilter."""
     pf = cls(700, 20, fwhm_at_0=15)
@@ -78,7 +76,7 @@ def test_peakfilter(cls):
 
 
 @pytest.mark.plottest
-@pytest.mark.parametrize('cls', [bq.GaussianPeakFilter])
+@pytest.mark.parametrize("cls", [bq.GaussianPeakFilter])
 def test_peakfilter_plot_matrix(cls):
     """Test PeakFilter.plot_matrix."""
     pf = cls(200, 20, fwhm_at_0=10)
@@ -91,7 +89,7 @@ def test_peakfinder():
     """Test basic functionality of PeakFinder."""
     kernel = bq.GaussianPeakFilter(500, 50, fwhm_at_0=10)
     finder = bq.PeakFinder(spec1, kernel)
-    finder.find_peak(500, min_snr=3.)
+    finder.find_peak(500, min_snr=3.0)
     assert np.isclose(finder.channels[0], 485.5)
     finder.reset()
     finder.find_peaks()
@@ -167,7 +165,7 @@ def test_peakfinder_plot():
     finder.find_peaks(min_snr=0.5, xmin=50, xmax=1000, max_num=10)
 
     plt.figure()
-    finder.plot(linecolor='k')
+    finder.plot(linecolor="k")
     plt.show()
 
 
@@ -201,8 +199,8 @@ def test_autocal_spec1():
     cal.fit(
         REQUIRED,
         optional=OPTIONAL,
-        gain_range=[2.5, 4.],
-        de_max=25.,
+        gain_range=[2.5, 4.0],
+        de_max=25.0,
         verbose=True,
     )
     assert len(cal.fit_channels) == 8
@@ -218,8 +216,8 @@ def test_autocal_one_line():
     assert len(cal.peakfinder.channels) == 1
     cal.fit(
         [1460.82],
-        gain_range=[2.5, 4.],
-        de_max=20.,
+        gain_range=[2.5, 4.0],
+        de_max=20.0,
     )
     assert len(cal.fit_channels) == 1
     assert np.isclose(cal.gain, 3.01, rtol=1e-2)
@@ -239,8 +237,8 @@ def test_autocal_exceptions():
     with pytest.raises(bq.AutoCalibratorError):
         cal.fit(
             REQUIRED,
-            gain_range=[2.5, 4.],
-            de_max=20.,
+            gain_range=[2.5, 4.0],
+            de_max=20.0,
         )
     # multiple peaks found, but only one energy given
     finder.reset()
@@ -249,8 +247,8 @@ def test_autocal_exceptions():
     with pytest.raises(bq.AutoCalibratorError):
         cal.fit(
             [1460.82],
-            gain_range=[2.5, 4.],
-            de_max=20.,
+            gain_range=[2.5, 4.0],
+            de_max=20.0,
         )
     # more energies required than peaks found
     finder.reset()
@@ -259,8 +257,8 @@ def test_autocal_exceptions():
     with pytest.raises(bq.AutoCalibratorError):
         cal.fit(
             [238.63, 351.93, 609.32, 1460.82, 2614.3],
-            gain_range=[2.5, 4.],
-            de_max=20.,
+            gain_range=[2.5, 4.0],
+            de_max=20.0,
         )
 
 
@@ -275,8 +273,8 @@ def test_autocal_no_fit():
         cal.fit(
             REQUIRED,
             optional=OPTIONAL,
-            gain_range=[1, 2.],
-            de_max=20.,
+            gain_range=[1, 2.0],
+            de_max=20.0,
         )
 
 
@@ -291,8 +289,8 @@ def test_autocal_plot():
     cal.fit(
         REQUIRED,
         optional=OPTIONAL,
-        gain_range=[2.5, 4.],
-        de_max=20.,
+        gain_range=[2.5, 4.0],
+        de_max=20.0,
     )
     plt.figure()
     cal.plot()
@@ -310,7 +308,7 @@ def test_autocal_spec2():
         REQUIRED,
         optional=OPTIONAL,
         gain_range=[0.1, 0.6],
-        de_max=5.,
+        de_max=5.0,
     )
     assert len(cal.fit_channels) == 6
     assert np.isclose(cal.gain, 0.3785, rtol=1e-2)
@@ -327,8 +325,8 @@ def test_autocal_spec3():
     cal.fit(
         [609.32, 1460.82],
         optional=[],
-        gain_range=[0.1, 5.],
-        de_max=50.,
+        gain_range=[0.1, 5.0],
+        de_max=50.0,
     )
     assert len(cal.fit_channels) == 2
     assert np.isclose(cal.gain, 3.22, rtol=1e-2)
@@ -336,8 +334,8 @@ def test_autocal_spec3():
     cal.fit(
         [609.32, 1460.82],
         optional=OPTIONAL,
-        gain_range=[0.1, 5.],
-        de_max=50.,
+        gain_range=[0.1, 5.0],
+        de_max=50.0,
     )
     assert len(cal.fit_channels) == 7
     assert np.isclose(cal.gain, 2.02, rtol=1e-2)
@@ -354,7 +352,7 @@ def test_autocal_spec4():
         [356.0129, 661.657, 1460.82],
         optional=[911.20, 1120.294, 1620.50, 1764.49, 2118.514, 2614.3],
         gain_range=[0.3, 0.7],
-        de_max=50.,
+        de_max=50.0,
     )
     assert len(cal.fit_channels) == 3
     assert np.isclose(cal.gain, 0.6133, rtol=1e-2)
diff --git a/tests/df_cache_test.py b/tests/df_cache_test.py
index e1415bee..5897693b 100644
--- a/tests/df_cache_test.py
+++ b/tests/df_cache_test.py
@@ -4,22 +4,19 @@
 from becquerel.tools.df_cache import DataFrameCache, CacheError
 import pytest
 
-# pylint: disable=no-self-use
-
 
 class ExampleCache(DataFrameCache):
 
-    name = 'example'
+    name = "example"
 
     def fetch(self):
         self.df = pd.DataFrame()
-        self.df['letters'] = ['a', 'b', 'c', 'g', 'h']
-        self.df['numbers'] = [1, 2, 6, 8, 3]
+        self.df["letters"] = ["a", "b", "c", "g", "h"]
+        self.df["numbers"] = [1, 2, 6, 8, 3]
         self.loaded = True
 
 
 class TestCacheFunctionality(object):
-
     def test_fetch(self):
         """Test ExampleCache.fetch()."""
         d = ExampleCache()
@@ -60,11 +57,10 @@ def test_load_multiple(self):
 
 
 class TestCacheExceptions(object):
-
     def test_bad_path(self):
         """Test ExampleCache.check_path() exception for a bad path."""
         d = ExampleCache()
-        d.path = '/bad/path'
+        d.path = "/bad/path"
         with pytest.raises(CacheError):
             d.check_path()
         with pytest.raises(CacheError):
diff --git a/tests/element_test.py b/tests/element_test.py
index 2a1791cc..1c3772bf 100644
--- a/tests/element_test.py
+++ b/tests/element_test.py
@@ -1,11 +1,8 @@
 """Test Element class."""
 
-from __future__ import print_function
 from becquerel.tools import element
 import pytest
 
-# pylint: disable=protected-access,unused-variable,no-self-use
-
 
 class TestElementFunctions(object):
     """Test Element functions."""
@@ -29,7 +26,7 @@ def test_validated_symbol_good(self):
     def test_validated_symbol_exception(self):
         """Test validated_symbol('Xz') raises ElementSymbolError..........."""
         with pytest.raises(element.ElementSymbolError):
-            element.validated_symbol('Xz')
+            element.validated_symbol("Xz")
 
     def test_validated_name_good(self):
         """Test validated_name............................................."""
@@ -40,31 +37,31 @@ def test_validated_name_good(self):
     def test_validated_name_exception(self):
         """Test validated_name('Xzzzzz') raises ElementNameError..........."""
         with pytest.raises(element.ElementNameError):
-            element.validated_name('Xzzzzz')
+            element.validated_name("Xzzzzz")
 
     def test_validated_name_aluminum(self):
         """Test validated_name('Aluminum') returns 'Aluminum'.............."""
-        name1 = 'Aluminum'
+        name1 = "Aluminum"
         for name2 in [name1, name1.lower(), name1.upper()]:
-            assert element.validated_name(name2) == 'Aluminum'
+            assert element.validated_name(name2) == "Aluminum"
 
     def test_validated_name_aluminium(self):
         """Test validated_name('Aluminium') returns 'Aluminum'............."""
-        name1 = 'Aluminium'
+        name1 = "Aluminium"
         for name2 in [name1, name1.lower(), name1.upper()]:
-            assert element.validated_name(name2) == 'Aluminum'
+            assert element.validated_name(name2) == "Aluminum"
 
     def test_validated_name_cesium(self):
         """Test validated_name('Cesium') returns 'Cesium'.................."""
-        name1 = 'Cesium'
+        name1 = "Cesium"
         for name2 in [name1, name1.lower(), name1.upper()]:
-            assert element.validated_name(name2) == 'Cesium'
+            assert element.validated_name(name2) == "Cesium"
 
     def test_validated_name_caesium(self):
         """Test validated_name('Caesium') returns 'Cesium'................."""
-        name1 = 'Caesium'
+        name1 = "Caesium"
         for name2 in [name1, name1.lower(), name1.upper()]:
-            assert element.validated_name(name2) == 'Cesium'
+            assert element.validated_name(name2) == "Cesium"
 
     def test_element_z(self):
         """Test element_z.................................................."""
@@ -107,15 +104,19 @@ def test_element_name_exception(self):
                 element.element_name(name1)
 
 
-@pytest.mark.parametrize('z, sym, name', [
-    (1, 'H', 'Hydrogen'),
-    (2, 'He', 'Helium'),
-    (13, 'Al', 'Aluminum'),
-    (19, 'K', 'Potassium'),
-    (32, 'Ge', 'Germanium'),
-    (70, 'Yb', 'Ytterbium'),
-    (92, 'U', 'Uranium'),
-    (118, 'Og', 'Oganesson')])
+@pytest.mark.parametrize(
+    "z, sym, name",
+    [
+        (1, "H", "Hydrogen"),
+        (2, "He", "Helium"),
+        (13, "Al", "Aluminum"),
+        (19, "K", "Potassium"),
+        (32, "Ge", "Germanium"),
+        (70, "Yb", "Ytterbium"),
+        (92, "U", "Uranium"),
+        (118, "Og", "Oganesson"),
+    ],
+)
 def test_element(z, sym, name):
     """Run instantiation tests for various elements.
 
@@ -128,8 +129,8 @@ def test_element(z, sym, name):
     args.extend([z, str(z)])
     print(args)
     for arg in args:
-        print('')
-        print('arg: ', arg)
+        print("")
+        print("arg: ", arg)
         elem = element.Element(arg)
         print(elem)
         assert elem.Z == z
@@ -143,12 +144,12 @@ class TestElementInitExceptions(object):
     def test_bad_arg_symbol(self):
         """Test Element init with a bad symbol raises ElementError........."""
         with pytest.raises(element.ElementError):
-            element.Element('Xx')
+            element.Element("Xx")
 
     def test_bad_arg_name(self):
         """Test Element init with a bad name raises ElementError..........."""
         with pytest.raises(element.ElementError):
-            element.Element('Xirconium')
+            element.Element("Xirconium")
 
     def test_bad_arg_z(self):
         """Test Element init with a bad Z raises ElementError.............."""
@@ -161,16 +162,16 @@ class TestElementsEqual(object):
 
     def test_h(self):
         """Test Element equality: H........................................"""
-        assert element.Element('H') == element.Element(1)
+        assert element.Element("H") == element.Element(1)
 
     def test_og(self):
         """Test Element equality: Og......................................."""
-        assert element.Element('Og') == element.Element(118)
+        assert element.Element("Og") == element.Element(118)
 
     def test_bad(self):
         """Test Element equality: H != 0..................................."""
         with pytest.raises(element.ElementError):
-            elem = element.Element('H')
+            elem = element.Element("H")
             elem == 0
 
 
@@ -179,10 +180,8 @@ class TestElementStrFormat(object):
 
     def test_h(self):
         """Test Element string formatting: H..............................."""
-        assert '{:%n (%s) %z}'.format(element.Element('H')) == \
-            'Hydrogen (H) 1'
+        assert "{:%n (%s) %z}".format(element.Element("H")) == "Hydrogen (H) 1"
 
     def test_og(self):
         """Test Element string formatting: Og.............................."""
-        assert '{:%n (%s) %z}'.format(element.Element('Og')) == \
-            'Oganesson (Og) 118'
+        assert "{:%n (%s) %z}".format(element.Element("Og")) == "Oganesson (Og) 118"
diff --git a/tests/energycal_test.py b/tests/energycal_test.py
index 7cd97a83..77bfa833 100644
--- a/tests/energycal_test.py
+++ b/tests/energycal_test.py
@@ -1,6 +1,5 @@
 """Test energycal.py"""
 
-from __future__ import print_function
 import pytest
 import numpy as np
 
@@ -22,28 +21,26 @@ def offset(request):
     return request.param
 
 
-@pytest.fixture(params=[
-    (32, 67, 115),
-    [32, 67, 115],
-    np.array((32, 67, 115)),
-    [31.7, 67.2, 115]
-])
+@pytest.fixture(
+    params=[(32, 67, 115), [32, 67, 115], np.array((32, 67, 115)), [31.7, 67.2, 115]]
+)
 def chlist(request):
     return request.param
 
 
-@pytest.fixture(params=[
-    (661.66, 1460.83, 2614.5),
-    [661.66, 1460.83, 2614.5],
-    np.array((661.66, 1460.83, 2614.5)),
-    (662, 1461, 2615)
-])
+@pytest.fixture(
+    params=[
+        (661.66, 1460.83, 2614.5),
+        [661.66, 1460.83, 2614.5],
+        np.array((661.66, 1460.83, 2614.5)),
+        (662, 1461, 2615),
+    ]
+)
 def kevlist(request):
     return request.param
 
 
-@pytest.fixture(params=[
-    32, -5, 67.83, (34, 35), np.arange(113, 7678.3, 55.5)])
+@pytest.fixture(params=[32, -5, 67.83, (34, 35), np.arange(113, 7678.3, 55.5)])
 def channels(request):
     return request.param
 
@@ -78,14 +75,16 @@ def linear_regression(x, y):
     n = len(x)
     sx = np.sum(x)
     sy = np.sum(y)
-    b = float(n*np.sum(x*y)-sx*sy)/float(n*np.sum(x**2)-sx*sx)
-    a = 1.0/n*(sy-b*sx)
+    b = float(n * np.sum(x * y) - sx * sy) / float(n * np.sum(x ** 2) - sx * sx)
+    a = 1.0 / n * (sy - b * sx)
     return [a, b]
 
+
 # ----------------------------------------------------
 #        Construction tests
 # ----------------------------------------------------
 
+
 def test_construction_empty():
     """Test empty construction"""
 
@@ -95,8 +94,7 @@ def test_construction_empty():
 def test_construction_chkevlist(chlist, kevlist):
     """Test construction (not fitting) from chlist, kevlist"""
 
-    cal = bq.LinearEnergyCal.from_points(
-        chlist=chlist, kevlist=kevlist)
+    cal = bq.LinearEnergyCal.from_points(chlist=chlist, kevlist=kevlist)
     assert len(cal.channels) == len(chlist)
     assert len(cal.energies) == len(chlist)
     assert len(cal.calpoints) == len(chlist)
@@ -106,16 +104,16 @@ def test_construction_chkevlist(chlist, kevlist):
 def test_construction_coefficients(slope, offset):
     """Test construction from coefficients"""
 
-    coeffs = {'b': slope, 'c': offset}
+    coeffs = {"b": slope, "c": offset}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
-    assert cal.coeffs['b'] == slope
-    assert cal.coeffs['c'] == offset
+    assert cal.coeffs["b"] == slope
+    assert cal.coeffs["c"] == offset
 
 
 def test_construction_bad_coefficients(slope, offset):
     """Test construction with bad coefficients"""
 
-    coeffs = {'a': offset, 'b': slope}
+    coeffs = {"a": offset, "b": slope}
     with pytest.raises(bq.EnergyCalError):
         bq.LinearEnergyCal.from_coeffs(coeffs)
 
@@ -124,31 +122,33 @@ def test_construction_wrong_length(chlist, kevlist):
     """Test from_points with bad input"""
 
     with pytest.raises(bq.BadInput) as excinfo:
-        bq.LinearEnergyCal.from_points(
-            chlist=chlist, kevlist=kevlist[:-1])
-    excinfo.match('Channels and energies must be same length')
-
-
-@pytest.mark.parametrize('cl, kl, io', [
-    (32, 661.7, False),
-    (32, 661.7, True),
-    ({"val": 32}, {"ene": 661.7}, False),
-    ({"val": 32}, {"ene": 661.7}, True),
-    ([[32, 64], [72, 108]], [[661.7, 1172.7], [1332.5, 2614.5]], False),
-    ([[32, 64], [72, 108]], [[661.7, 1172.7], [1332.5, 2614.5]], True)])
+        bq.LinearEnergyCal.from_points(chlist=chlist, kevlist=kevlist[:-1])
+    excinfo.match("Channels and energies must be same length")
+
+
+@pytest.mark.parametrize(
+    "cl, kl, io",
+    [
+        (32, 661.7, False),
+        (32, 661.7, True),
+        ({"val": 32}, {"ene": 661.7}, False),
+        ({"val": 32}, {"ene": 661.7}, True),
+        ([[32, 64], [72, 108]], [[661.7, 1172.7], [1332.5, 2614.5]], False),
+        ([[32, 64], [72, 108]], [[661.7, 1172.7], [1332.5, 2614.5]], True),
+    ],
+)
 def test_construction_bad_points(cl, kl, io):
     """Test errors of from_points with bad input"""
 
     with pytest.raises(bq.BadInput) as excinfo:
         bq.LinearEnergyCal.from_points(chlist=cl, kevlist=kl, include_origin=io)
-    excinfo.match('Inputs must be one dimensional iterables')
+    excinfo.match("Inputs must be one dimensional iterables")
 
 
-@pytest.mark.parametrize('cl, kl, io', [
-    ([], [], False),
-    ([], [], True),
-    ((32,), (661.7,), False),
-    ([32], [661.7], False)])
+@pytest.mark.parametrize(
+    "cl, kl, io",
+    [([], [], False), ([], [], True), ((32,), (661.7,), False), ([32], [661.7], False)],
+)
 def test_construction_empty_points(cl, kl, io):
     """Test errors of from_points with empty/insufficient input"""
 
@@ -156,21 +156,20 @@ def test_construction_empty_points(cl, kl, io):
         bq.LinearEnergyCal.from_points(chlist=cl, kevlist=kl, include_origin=io)
 
 
-@pytest.mark.parametrize('cl, kl, io', [
-    (None, 661.7, False),
-    (32, None, False)])
+@pytest.mark.parametrize("cl, kl, io", [(None, 661.7, False), (32, None, False)])
 def test_construction_None_points(cl, kl, io):
     """Test errors if input to from_points is None"""
 
     with pytest.raises(bq.EnergyCalError) as excinfo:
         bq.LinearEnergyCal.from_points(chlist=cl, kevlist=kl, include_origin=io)
-    excinfo.match('Channel list and energy list are required')
+    excinfo.match("Channel list and energy list are required")
 
 
 # ----------------------------------------------------
 #        other EnergyCal method tests
 # ----------------------------------------------------
 
+
 def test_methods_add_calpoint():
     """Test add_calpoint"""
 
@@ -211,20 +210,19 @@ def test_methods_rm_calpoint():
 def test_methods_ch2kev(slope, offset, channels):
     """Test ch2kev (both scalar and array)"""
 
-    coeffs = {'b': slope, 'c': offset}
+    coeffs = {"b": slope, "c": offset}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
 
     if np.isscalar(channels):
         assert cal.ch2kev(channels) == slope * channels + offset
     else:
-        assert np.all(
-            cal.ch2kev(channels) == slope * np.array(channels) + offset)
+        assert np.all(cal.ch2kev(channels) == slope * np.array(channels) + offset)
 
 
 def test_methods_kev2ch(slope, offset, channels):
     """Test kev2ch"""
 
-    coeffs = {'b': slope, 'c': offset}
+    coeffs = {"b": slope, "c": offset}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
 
     if np.isscalar(channels):
@@ -232,6 +230,7 @@ def test_methods_kev2ch(slope, offset, channels):
     else:
         assert np.all(np.isclose(cal.kev2ch(cal.ch2kev(channels)), channels))
 
+
 # update_fit is in LinearEnergyCal section
 
 
@@ -239,20 +238,21 @@ def test_methods_kev2ch(slope, offset, channels):
 #        LinearEnergyCal tests
 # ----------------------------------------------------
 
+
 def test_linear_construction_coefficients(slope, offset):
     """Test alternate construction coefficient names"""
 
-    coeffs = {'p0': offset, 'p1': slope}
+    coeffs = {"p0": offset, "p1": slope}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
     assert cal.slope == slope
     assert cal.offset == offset
 
-    coeffs = {'offset': offset, 'slope': slope}
+    coeffs = {"offset": offset, "slope": slope}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
     assert cal.slope == slope
     assert cal.offset == offset
 
-    coeffs = {'b': offset, 'm': slope}
+    coeffs = {"b": offset, "m": slope}
     cal = bq.LinearEnergyCal.from_coeffs(coeffs)
     assert cal.slope == slope
     assert cal.offset == offset
@@ -264,7 +264,9 @@ def test_linear_fitting_simple():
     cal = bq.LinearEnergyCal.from_points(chlist=[0.0, 1.0], kevlist=[1.0, 3.0])
     assert np.allclose([1.0, 2.0], [cal.offset, cal.slope])
 
-    cal = bq.LinearEnergyCal.from_points(chlist=[1.0], kevlist=[2.0], include_origin=True)
+    cal = bq.LinearEnergyCal.from_points(
+        chlist=[1.0], kevlist=[2.0], include_origin=True
+    )
     assert np.allclose([0.0, 2.0], [cal.offset, cal.slope])
 
 
@@ -288,7 +290,9 @@ def test_linear_fitting_with_origin(chlist, kevlist):
 
     c = np.append(0, chlist)
     k = np.append(0, kevlist)
-    cal = bq.LinearEnergyCal.from_points(chlist=chlist, kevlist=kevlist, include_origin=True)
+    cal = bq.LinearEnergyCal.from_points(
+        chlist=chlist, kevlist=kevlist, include_origin=True
+    )
     assert np.allclose(linear_regression(c, k), [cal.offset, cal.slope])
 
 
@@ -309,18 +313,19 @@ def test_bad_access_error(uncal_spec):
     with pytest.raises(bq.EnergyCalError):
         cal.offset
 
+
 # ----------------------------------------------------
 #        Spectrum calibration methods tests
 # ----------------------------------------------------
 
+
 def test_apply_calibration(uncal_spec, chlist, kevlist):
     """Apply calibration on an uncalibrated spectrum"""
 
     cal = bq.LinearEnergyCal.from_points(chlist=chlist, kevlist=kevlist)
     uncal_spec.apply_calibration(cal)
     assert uncal_spec.is_calibrated
-    assert np.allclose(
-        uncal_spec.bin_edges_kev[:-1], cal.ch2kev(uncal_spec.channels))
+    assert np.allclose(uncal_spec.bin_edges_kev[:-1], cal.ch2kev(uncal_spec.channels))
     assert uncal_spec.energy_cal is not None
 
 
@@ -350,6 +355,7 @@ def test_rm_calibration_error(uncal_spec):
     uncal_spec.rm_calibration()
     assert not uncal_spec.is_calibrated
 
+
 def test_calibration_not_initialized_error(uncal_spec):
     """Test that calling apply_calibration on empty calibration causes an error"""
 
diff --git a/tests/fitting_test.py b/tests/fitting_test.py
index a9fd8849..8c385b94 100644
--- a/tests/fitting_test.py
+++ b/tests/fitting_test.py
@@ -4,14 +4,13 @@
 from copy import deepcopy
 import numpy as np
 import becquerel as bq
-import matplotlib.pyplot as plt
 
-SAMPLES_PATH = os.path.join(os.path.dirname(__file__), 'samples')
+SAMPLES_PATH = os.path.join(os.path.dirname(__file__), "samples")
 
 # TODO: use these for fitting actual data
 SAMPLES = {}
-for extension in ['.spe', '.spc', '.cnf']:
-    filenames = glob.glob(os.path.join(SAMPLES_PATH, '*.*'))
+for extension in [".spe", ".spc", ".cnf"]:
+    filenames = glob.glob(os.path.join(SAMPLES_PATH, "*.*"))
     filenames_filtered = []
     for filename in filenames:
         fname, ext = os.path.splitext(filename)
@@ -21,18 +20,18 @@
 
 
 def get_model_name(x):
-    if bq.core.utils.isstring(x):
+    if isinstance(x, str):
         return x
     else:
-        return ' '.join(x)
+        return " ".join(x)
 
 
-def sim_data(x_min, x_max, y_func, num_x=200, binning='linear', **params):
-    if binning == 'linear':
-        edges =  np.linspace(x_min, x_max, num_x, dtype=np.float)
-    elif binning == 'sqrt':
+def sim_data(x_min, x_max, y_func, num_x=200, binning="linear", **params):
+    if binning == "linear":
+        edges = np.linspace(x_min, x_max, num_x, dtype=np.float)
+    elif binning == "sqrt":
         edges = np.linspace(np.sqrt(x_min), np.sqrt(x_max), num_x)
-        edges = edges**2
+        edges = edges ** 2
     x = (edges[1:] + edges[:-1]) * 0.5
     dx = edges[1:] - edges[:-1]
 
@@ -40,7 +39,7 @@ def sim_data(x_min, x_max, y_func, num_x=200, binning='linear', **params):
     np.random.seed(1)
     y = np.random.poisson(y_smooth).astype(np.float)
     y_unc = np.sqrt(y)
-    return {'x': x, 'y': y, 'y_unc': y_unc, 'dx': dx}
+    return {"x": x, "y": y, "y_unc": y_unc, "dx": dx}
 
 
 def compare_params(true_params, fit_params, rtol, fitter):
@@ -51,96 +50,93 @@ def compare_params(true_params, fit_params, rtol, fitter):
         #     plt.show()
         assert np.isclose(v, true_params[p], rtol=rtol), p
 
+
 def compare_counts(fitter):
     data_counts = np.sum(fitter.y_roi)
     model_counts = np.sum(
-        fitter.eval(fitter.x_roi, **fitter.result.best_values) *
-        fitter.dx_roi)
+        fitter.eval(fitter.x_roi, **fitter.result.best_values) * fitter.dx_roi
+    )
     assert np.allclose(data_counts, model_counts, atol=1e-2)
 
+
 # -----------------------------------------------------------------------------
 # Simulated data generation
 # TODO: discuss this 20% tol, maybe smaller tol for gauss?
 # -----------------------------------------------------------------------------
 
 HIGH_STAT_SIM_PARAMS = {
-    'base_model_params': {
-        'gauss': {
-            'amp': 1e5,
-            'mu': 100.,
-            'sigma': 5.,
-        },
-        'gausserf': {
-            'ampgauss': 1e5,
-            'amperf': 1e4,
-            'mu': 100.,
-            'sigma': 5.,
+    "base_model_params": {
+        "gauss": {
+            "amp": 1e5,
+            "mu": 100.0,
+            "sigma": 5.0,
         },
-        'exp': {
-            'lam': -5e1,
-            'amp': 1e4,
+        "gausserf": {
+            "ampgauss": 1e5,
+            "amperf": 1e4,
+            "mu": 100.0,
+            "sigma": 5.0,
         },
-        'erf': {
-            'amp': 1e4,
-            'mu': 100.,
-            'sigma': 5.,
+        "exp": {
+            "lam": -5e1,
+            "amp": 1e4,
         },
-        'line': {
-            'm': -10.,
-            'b': 1e4,
+        "erf": {
+            "amp": 1e4,
+            "mu": 100.0,
+            "sigma": 5.0,
         },
-        'expgauss': {
-            'amp': 1e5,
-            'mu': 100,
-            'sigma': 5.,
-            'gamma': 0.25
+        "line": {
+            "m": -10.0,
+            "b": 1e4,
         },
+        "expgauss": {"amp": 1e5, "mu": 100, "sigma": 5.0, "gamma": 0.25},
     },
-    'setup': {
-        'roi': (25, 175),
-        'rtol': 40e-2,
-        'sim_data_kwargs': {
-            'x_min': 10.0,
-            'x_max': 190.0,
-            'num_x': 180,
+    "setup": {
+        "roi": (25, 175),
+        "rtol": 40e-2,
+        "sim_data_kwargs": {
+            "x_min": 10.0,
+            "x_max": 190.0,
+            "num_x": 180,
         },
     },
-    'models': [
-        'gauss',
-        ['gauss', 'line'],
-        'gausserf',
-        ['gauss', 'exp'],
-        ['gausserf', 'line'],
+    "models": [
+        "gauss",
+        ["gauss", "line"],
+        "gausserf",
+        ["gauss", "exp"],
+        ["gausserf", "line"],
         # ['gauss', 'line', 'exp'],
-        ['gausserf', 'exp'],
-        'expgauss',
+        ["gausserf", "exp"],
+        "expgauss",
     ],
-    'fixture': {
-        'params': [],
-        'ids': [],
+    "fixture": {
+        "params": [],
+        "ids": [],
     },
-    'methods': ['lmfit', 'lmfit-pml'],
-    'binnings': ['linear', 'sqrt']
+    "methods": ["lmfit", "lmfit-pml"],
+    "binnings": ["linear", "sqrt"],
 }
 
-for _e in HIGH_STAT_SIM_PARAMS['methods']:
-    for _m in HIGH_STAT_SIM_PARAMS['models']:
-        for _b in HIGH_STAT_SIM_PARAMS['binnings']:
-            _p = deepcopy(HIGH_STAT_SIM_PARAMS['setup'])
-            _p['model'] = deepcopy(_m)
-            _p['params'] = {}
-            _p['method'] = _e
-            _p['binning'] = _b
-            if bq.utils.isstring(_m):
+for _e in HIGH_STAT_SIM_PARAMS["methods"]:
+    for _m in HIGH_STAT_SIM_PARAMS["models"]:
+        for _b in HIGH_STAT_SIM_PARAMS["binnings"]:
+            _p = deepcopy(HIGH_STAT_SIM_PARAMS["setup"])
+            _p["model"] = deepcopy(_m)
+            _p["params"] = {}
+            _p["method"] = _e
+            _p["binning"] = _b
+            if isinstance(_m, str):
                 _i = deepcopy(_m)
                 _m = [_m]
             else:
-                _i = ''.join([_bm.capitalize() for _bm in _m])
+                _i = "".join([_bm.capitalize() for _bm in _m])
             for _bm in _m:
-                for _bmp, _v in HIGH_STAT_SIM_PARAMS['base_model_params'][_bm].items():
-                    _p['params']['{}_{}'.format(_bm, _bmp)] = _v
-            HIGH_STAT_SIM_PARAMS['fixture']['params'].append(_p)
-            HIGH_STAT_SIM_PARAMS['fixture']['ids'].append(_i)
+                for _bmp, _v in HIGH_STAT_SIM_PARAMS["base_model_params"][_bm].items():
+                    _p["params"]["{}_{}".format(_bm, _bmp)] = _v
+            HIGH_STAT_SIM_PARAMS["fixture"]["params"].append(_p)
+            HIGH_STAT_SIM_PARAMS["fixture"]["ids"].append(_i)
 
 
 # HIGH_STAT_GAUSS_GAUSS_LINE = dict(
@@ -182,16 +178,16 @@ def compare_counts(fitter):
 #     name='FitterGaussGaussExp')
 
 
-@pytest.fixture(**HIGH_STAT_SIM_PARAMS['fixture'])
+@pytest.fixture(**HIGH_STAT_SIM_PARAMS["fixture"])
 def sim_high_stat(request):
     """Fake data with high count statistics"""
     out = deepcopy(request.param)
-    out['fitter'] = bq.Fitter(out['model'])
-    sim_data_kwargs = out['sim_data_kwargs'].copy()
-    sim_data_kwargs.update(out['params'])
-    out['data'] = sim_data(y_func=out['fitter'].eval,
-                           binning=out['binning'],
-                           **sim_data_kwargs)
+    out["fitter"] = bq.Fitter(out["model"])
+    sim_data_kwargs = out["sim_data_kwargs"].copy()
+    sim_data_kwargs.update(out["params"])
+    out["data"] = sim_data(
+        y_func=out["fitter"].eval, binning=out["binning"], **sim_data_kwargs
+    )
     return out
 
 
@@ -203,44 +199,54 @@ class TestFittingHighStatSimData(object):
     @pytest.mark.filterwarnings("ignore")
     def test_with_init(self, sim_high_stat):
         fitter = bq.Fitter(
-            sim_high_stat['model'],
-            x=sim_high_stat['data']['x'],
-            y=sim_high_stat['data']['y'],
-            dx=sim_high_stat['data']['dx'],
-            y_unc=sim_high_stat['data']['y_unc'],
-            roi=sim_high_stat['roi'])
-        fitter.fit(sim_high_stat['method'])
-        compare_params(true_params=sim_high_stat['params'],
-                       fit_params=fitter.result.best_values,
-                       rtol=sim_high_stat['rtol'], fitter=fitter)
-        if sim_high_stat['method'] == 'lmfit-pml':
+            sim_high_stat["model"],
+            x=sim_high_stat["data"]["x"],
+            y=sim_high_stat["data"]["y"],
+            dx=sim_high_stat["data"]["dx"],
+            y_unc=sim_high_stat["data"]["y_unc"],
+            roi=sim_high_stat["roi"],
+        )
+        fitter.fit(sim_high_stat["method"])
+        compare_params(
+            true_params=sim_high_stat["params"],
+            fit_params=fitter.result.best_values,
+            rtol=sim_high_stat["rtol"],
+            fitter=fitter,
+        )
+        if sim_high_stat["method"] == "lmfit-pml":
             compare_counts(fitter)
         # fitter.custom_plot()
         # plt.show()
 
     @pytest.mark.filterwarnings("ignore")
     def test_no_roi(self, sim_high_stat):
-        fitter = bq.Fitter(sim_high_stat['model'])
-        fitter.set_data(**sim_high_stat['data'])
-        fitter.fit(sim_high_stat['method'])
-        compare_params(true_params=sim_high_stat['params'],
-                       fit_params=fitter.result.best_values,
-                       rtol=sim_high_stat['rtol'], fitter=fitter)
-        if sim_high_stat['method'] == 'lmfit-pml':
+        fitter = bq.Fitter(sim_high_stat["model"])
+        fitter.set_data(**sim_high_stat["data"])
+        fitter.fit(sim_high_stat["method"])
+        compare_params(
+            true_params=sim_high_stat["params"],
+            fit_params=fitter.result.best_values,
+            rtol=sim_high_stat["rtol"],
+            fitter=fitter,
+        )
+        if sim_high_stat["method"] == "lmfit-pml":
             compare_counts(fitter)
         # fitter.custom_plot()
         # plt.show()
 
     @pytest.mark.filterwarnings("ignore")
     def test_with_roi(self, sim_high_stat):
-        fitter = bq.Fitter(sim_high_stat['model'])
-        fitter.set_data(**sim_high_stat['data'])
-        fitter.set_roi(*sim_high_stat['roi'])
-        fitter.fit(sim_high_stat['method'])
-        compare_params(true_params=sim_high_stat['params'],
-                       fit_params=fitter.result.best_values,
-                       rtol=sim_high_stat['rtol'], fitter=fitter)
-        if sim_high_stat['method'] == 'lmfit-pml':
+        fitter = bq.Fitter(sim_high_stat["model"])
+        fitter.set_data(**sim_high_stat["data"])
+        fitter.set_roi(*sim_high_stat["roi"])
+        fitter.fit(sim_high_stat["method"])
+        compare_params(
+            true_params=sim_high_stat["params"],
+            fit_params=fitter.result.best_values,
+            rtol=sim_high_stat["rtol"],
+            fitter=fitter,
+        )
+        if sim_high_stat["method"] == "lmfit-pml":
             compare_counts(fitter)
         # fitter.custom_plot()
         # plt.show()
diff --git a/tests/isotope_qty_test.py b/tests/isotope_qty_test.py
index df7bb849..368e98cf 100644
--- a/tests/isotope_qty_test.py
+++ b/tests/isotope_qty_test.py
@@ -1,5 +1,3 @@
-
-from __future__ import print_function
 import datetime
 from dateutil.parser import parse as dateutil_parse
 import copy
@@ -25,50 +23,54 @@ def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
 #               IsotopeQuantity class
 # ----------------------------------------------------
 
-@pytest.fixture(params=(
-    'Bi-212',
-    'Cs-137',
-    'K-40',
-    'Cs-134',
-    'N-13',
-    'Rn-220',
-    'Tc-99m',
-    'Tl-208',
-    'Th-232',
-))
+
+@pytest.fixture(
+    params=(
+        "Bi-212",
+        "Cs-137",
+        "K-40",
+        "Cs-134",
+        "N-13",
+        "Rn-220",
+        "Tc-99m",
+        "Tl-208",
+        "Th-232",
+    )
+)
 def radioisotope(request):
     return Isotope(request.param)
 
 
-@pytest.fixture(params=(
-    'H-2',
-    'Cs-133',
-    'Pb-208',
-    'La-138',
-    'Eu-151',
-    'Ge-76',
-))
+@pytest.fixture(
+    params=(
+        "H-2",
+        "Cs-133",
+        "Pb-208",
+        "La-138",
+        "Eu-151",
+        "Ge-76",
+    )
+)
 def stable_isotope(request):
     return Isotope(request.param)
 
 
-@pytest.fixture(params=[
-    {'bq': 10.047 * UCI_TO_BQ},
-    {'uci': 10.047},
-    {'uci': ufloat(10.047, 0.025)},
-    {'atoms': 1e24},
-    {'g': 1e-5}
-])
+@pytest.fixture(
+    params=[
+        {"bq": 10.047 * UCI_TO_BQ},
+        {"uci": 10.047},
+        {"uci": ufloat(10.047, 0.025)},
+        {"atoms": 1e24},
+        {"g": 1e-5},
+    ]
+)
 def iq_kwargs(request):
     return request.param
 
 
-@pytest.fixture(params=[
-    datetime.datetime.now(),
-    datetime.date.today(),
-    '2015-01-08 00:00:00',
-    None
-])
+@pytest.fixture(
+    params=[datetime.datetime.now(), datetime.date.today(), "2015-01-08 00:00:00", None]
+)
 def iq_date(request):
     return request.param
 
@@ -91,7 +93,7 @@ def test_isotopequantity_init_rad(radioisotope, iq_date, iq_kwargs):
 def test_isotopequantity_init_stable(stable_isotope, iq_date, iq_kwargs):
     """Test IsotopeQuantity.__init__() for radioactive isotopes"""
 
-    if 'bq' in iq_kwargs or 'uci' in iq_kwargs:
+    if "bq" in iq_kwargs or "uci" in iq_kwargs:
         with pytest.raises(IsotopeQuantityError):
             iq = IsotopeQuantity(stable_isotope, date=iq_date, **iq_kwargs)
         return None
@@ -113,16 +115,15 @@ def test_isotopequantity_ref_atoms_rad(radioisotope, iq_kwargs):
     """Test IsotopeQuantity.ref_atoms for a radioactive isotope"""
 
     iq = IsotopeQuantity(radioisotope, **iq_kwargs)
-    if 'atoms' in iq_kwargs:
-        assert isclose(iq.ref_atoms, iq_kwargs['atoms'])
-    elif 'g' in iq_kwargs:
-        assert isclose(iq.ref_atoms, (iq_kwargs['g'] / radioisotope.A * N_AV))
-    elif 'bq' in iq_kwargs:
-        assert isclose(iq.ref_atoms, iq_kwargs['bq'] / radioisotope.decay_const)
+    if "atoms" in iq_kwargs:
+        assert isclose(iq.ref_atoms, iq_kwargs["atoms"])
+    elif "g" in iq_kwargs:
+        assert isclose(iq.ref_atoms, (iq_kwargs["g"] / radioisotope.A * N_AV))
+    elif "bq" in iq_kwargs:
+        assert isclose(iq.ref_atoms, iq_kwargs["bq"] / radioisotope.decay_const)
     else:
         assert isclose(
-            iq.ref_atoms,
-            iq_kwargs['uci'] * UCI_TO_BQ / radioisotope.decay_const
+            iq.ref_atoms, iq_kwargs["uci"] * UCI_TO_BQ / radioisotope.decay_const
         )
 
 
@@ -146,7 +147,7 @@ def test_isotopequantity_ref_date_rad(radioisotope, iq_date):
         assert iq.ref_date == iq_date
     elif isinstance(iq_date, datetime.date):
         assert iq.ref_date.date() == iq_date
-    elif bq.core.utils.isstring(iq_date):
+    elif isinstance(iq_date, str):
         assert iq.ref_date == dateutil_parse(iq_date)
     else:
         assert (datetime.datetime.now() - iq.ref_date).total_seconds() < 5
@@ -160,25 +161,28 @@ def test_isotopequantity_ref_date_stable(stable_isotope, iq_date):
         assert iq.ref_date == iq_date
     elif isinstance(iq_date, datetime.date):
         assert iq.ref_date.date() == iq_date
-    elif bq.core.utils.isstring(iq_date):
+    elif isinstance(iq_date, str):
         assert iq.ref_date == dateutil_parse(iq_date)
     else:
         assert (datetime.datetime.now() - iq.ref_date).total_seconds() < 5
 
 
-@pytest.mark.parametrize('date_in', [
-    '2021-2-20 0:00:00', '2021-2-20', datetime.date(2021, 2, 20)
-])
+@pytest.mark.parametrize(
+    "date_in", ["2021-2-20 0:00:00", "2021-2-20", datetime.date(2021, 2, 20)]
+)
 def test_handle_datetime(date_in):
     assert bq.utils.handle_datetime(date_in) == datetime.datetime(2021, 2, 20, 0, 0, 0)
 
 
-@pytest.mark.parametrize('iso, date, kwargs, error', [
-    (['Cs-137'], None, {'atoms': 1e24}, TypeError),
-    ('Cs-137', 123, {'bq': 456}, TypeError),
-    ('Cs-137', datetime.datetime.now(), {'asdf': 3}, IsotopeQuantityError),
-    ('Cs-137', None, {'bq': -13.3}, ValueError)
-])
+@pytest.mark.parametrize(
+    "iso, date, kwargs, error",
+    [
+        (["Cs-137"], None, {"atoms": 1e24}, TypeError),
+        ("Cs-137", 123, {"bq": 456}, TypeError),
+        ("Cs-137", datetime.datetime.now(), {"asdf": 3}, IsotopeQuantityError),
+        ("Cs-137", None, {"bq": -13.3}, ValueError),
+    ],
+)
 def test_isotopequantity_bad_init(iso, date, kwargs, error):
     """Test errors from Isotope.__init__()"""
 
@@ -191,7 +195,7 @@ def iq(radioisotope):
     """An IsotopeQuantity object"""
 
     date = datetime.datetime.now()
-    kwargs = {'uci': 10.047}
+    kwargs = {"uci": 10.047}
     return IsotopeQuantity(radioisotope, date=date, **kwargs)
 
 
@@ -203,10 +207,9 @@ def test_isotopequantity_at_methods(iq):
     assert iq.atoms_at(iq.ref_date) == iq.ref_atoms
     assert np.isclose(iq.g_at(iq.ref_date), iq.ref_atoms * iq.isotope.A / N_AV)
     assert np.isclose(iq.bq_at(iq.ref_date), iq.ref_atoms * iq.decay_const)
-    assert np.isclose(
-        iq.uci_at(iq.ref_date), iq.ref_atoms * iq.decay_const / UCI_TO_BQ)
+    assert np.isclose(iq.uci_at(iq.ref_date), iq.ref_atoms * iq.decay_const / UCI_TO_BQ)
 
-    if iq.half_life < 3.156e7 * 1000:   # OverflowError or year out of range
+    if iq.half_life < 3.156e7 * 1000:  # OverflowError or year out of range
         dt1 = iq.ref_date + datetime.timedelta(seconds=half_life)
         dt2 = iq.ref_date - datetime.timedelta(seconds=half_life)
         dt3 = iq.ref_date + datetime.timedelta(seconds=half_life * 50)
@@ -218,11 +221,11 @@ def test_isotopequantity_at_methods(iq):
         assert np.isclose(iq.bq_at(dt4), iq.bq_at(iq.ref_date), rtol=1e-2)
 
 
-@pytest.mark.parametrize('kw', ('atoms', 'g', 'bq', 'uci'))
+@pytest.mark.parametrize("kw", ("atoms", "g", "bq", "uci"))
 def test_isotopequantity_time_when(iq, kw):
     """Test IsotopeQuantity.time_when()"""
 
-    ref_qty = getattr(iq, kw + '_at')(iq.ref_date)
+    ref_qty = getattr(iq, kw + "_at")(iq.ref_date)
 
     kwarg = {kw: ref_qty}
     assert iq.time_when(**kwarg) == iq.ref_date
@@ -298,8 +301,9 @@ def test_isotopequantity_decays_from(iq):
 
         assert np.isclose(iq.bq_from(t0, t1), iq.atoms_at(t1) / iq.half_life)
 
-        assert np.isclose(iq.uci_from(t0, t1),
-                          iq.atoms_at(t1) / iq.half_life / UCI_TO_BQ)
+        assert np.isclose(
+            iq.uci_from(t0, t1), iq.atoms_at(t1) / iq.half_life / UCI_TO_BQ
+        )
 
 
 def test_isotopequantity_decays_during(iq):
@@ -331,7 +335,7 @@ def test_isotopequantity_eq(iq):
     assert iq == iq3
 
 
-@pytest.mark.parametrize('f', [2, 0.5, 3.14])
+@pytest.mark.parametrize("f", [2, 0.5, 3.14])
 def test_isotopequantity_mul_div(iq, f):
     """Test IsotopeQuantity multiplication and division magic methods"""
 
@@ -354,13 +358,15 @@ def test_isotopequantity_from_decays(iq):
 
     # qualitative
     IsotopeQuantity.from_decays(
-        iq.isotope, n_decays=1000, start_time=start, stop_time=stop)
+        iq.isotope, n_decays=1000, start_time=start, stop_time=stop
+    )
 
     # quantitative
     if iq.half_life < 1000 * 3.156e7:  # floating point precision #65
         n = iq.decays_from(start, stop)
         iq2 = IsotopeQuantity.from_decays(
-            iq.isotope, n_decays=n, start_time=start, stop_time=stop)
+            iq.isotope, n_decays=n, start_time=start, stop_time=stop
+        )
         assert np.isclose(iq.atoms_at(start), iq2.atoms_at(start))
 
 
@@ -371,35 +377,31 @@ def test_isotopequantity_from_comparison(iq):
     start = iq.ref_date
     stop = start + datetime.timedelta(seconds=3600)
     interval1 = (start, stop)
-    iq2 = IsotopeQuantity.from_comparison(
-        iq, counts1, interval1,
-        counts1, interval1)
+    iq2 = IsotopeQuantity.from_comparison(iq, counts1, interval1, counts1, interval1)
     assert iq2 == iq
 
     f = 3.1
     counts2 = counts1 * f
-    iq3 = IsotopeQuantity.from_comparison(
-        iq, counts1, interval1,
-        counts2, interval1)
+    iq3 = IsotopeQuantity.from_comparison(iq, counts1, interval1, counts2, interval1)
     assert iq3 == iq * f
 
     if iq.half_life < 1000 * 3.156e7:
         dt = datetime.timedelta(seconds=iq.half_life)
         interval2 = (start + dt, stop + dt)
         iq4 = IsotopeQuantity.from_comparison(
-            iq, counts1, interval1,
-            counts1, interval2)
+            iq, counts1, interval1, counts1, interval2
+        )
         assert iq4 == iq * 2
 
         iq5 = IsotopeQuantity.from_comparison(
-            iq, counts1, interval1,
-            counts2, interval2)
+            iq, counts1, interval1, counts2, interval2
+        )
         assert iq5 == iq * 2 * f
 
         interval3 = (start - dt, stop - dt)
         iq6 = IsotopeQuantity.from_comparison(
-            iq, counts1, interval1,
-            counts1, interval3)
+            iq, counts1, interval1, counts1, interval3
+        )
         assert iq6 == iq / 2
 
 
@@ -407,22 +409,29 @@ def test_isotopequantity_from_comparison(iq):
 #               NeutronIrradiation class
 # ----------------------------------------------------
 
-@pytest.mark.parametrize('start, stop, n_cm2, n_cm2_s', [
-    ('2017-01-01 00:00:00', '2017-01-01 12:00:00', None, 1e12),
-    ('2017-01-01 00:00:00', '2017-01-01 12:00:00', 1e15, None),
-    ('2017-01-01 00:00:00', '2017-01-01 00:00:00', 1e15, None)
-])
+
+@pytest.mark.parametrize(
+    "start, stop, n_cm2, n_cm2_s",
+    [
+        ("2017-01-01 00:00:00", "2017-01-01 12:00:00", None, 1e12),
+        ("2017-01-01 00:00:00", "2017-01-01 12:00:00", 1e15, None),
+        ("2017-01-01 00:00:00", "2017-01-01 00:00:00", 1e15, None),
+    ],
+)
 def test_irradiation_init(start, stop, n_cm2, n_cm2_s):
     """Test valid inits for NeutronIrradiation"""
 
     ni = NeutronIrradiation(start, stop, n_cm2=n_cm2, n_cm2_s=n_cm2_s)
-    assert hasattr(ni, 'n_cm2')
+    assert hasattr(ni, "n_cm2")
 
 
-@pytest.mark.parametrize('start, stop, n_cm2, n_cm2_s, error', [
-    ('2017-01-01 00:00:00', '2017-01-01 12:00:00', 1e15, 1e12, ValueError),
-    ('2017-01-01 12:00:00', '2017-01-01 00:00:00', None, 1e12, ValueError)
-])
+@pytest.mark.parametrize(
+    "start, stop, n_cm2, n_cm2_s, error",
+    [
+        ("2017-01-01 00:00:00", "2017-01-01 12:00:00", 1e15, 1e12, ValueError),
+        ("2017-01-01 12:00:00", "2017-01-01 00:00:00", None, 1e12, ValueError),
+    ],
+)
 def test_irradiation_bad_init(start, stop, n_cm2, n_cm2_s, error):
     """Test invalid inits for NeutronIrradiation"""
 
@@ -430,11 +439,7 @@ def test_irradiation_bad_init(start, stop, n_cm2, n_cm2_s, error):
         NeutronIrradiation(start, stop, n_cm2=n_cm2, n_cm2_s=n_cm2_s)
 
 
-@pytest.fixture(params=[
-    ('Cs-133', 'Cs-134'),
-    ('Hg-202', 'Hg-203'),
-    ('Na-23', 'Na-24')
-])
+@pytest.fixture(params=[("Cs-133", "Cs-134"), ("Hg-202", "Hg-203"), ("Na-23", "Na-24")])
 def activation_pair(request):
     return Isotope(request.param[0]), Isotope(request.param[1])
 
@@ -477,8 +482,13 @@ def test_irradiation_activate_extended(activation_pair):
     barns = 1
 
     iq0 = IsotopeQuantity(iso0, date=start, atoms=1e24)
-    expected_atoms = n_cm2_s * barns * 1e-24 * iq0.ref_atoms * (
-        1 - np.exp(-iso1.decay_const * t_irr))
+    expected_atoms = (
+        n_cm2_s
+        * barns
+        * 1e-24
+        * iq0.ref_atoms
+        * (1 - np.exp(-iso1.decay_const * t_irr))
+    )
 
     ni = NeutronIrradiation(start, stop, n_cm2_s=n_cm2_s)
 
@@ -496,8 +506,8 @@ def test_irradiation_activate_extended(activation_pair):
 def test_irradiation_activate_errors():
     """Test NeutronIrradiation.activate() bad args"""
 
-    iso0 = Isotope('Na-23')
-    iso1 = Isotope('Na-24')
+    iso0 = Isotope("Na-23")
+    iso1 = Isotope("Na-24")
     iq0 = IsotopeQuantity(iso0, atoms=1e24)
     iq1 = IsotopeQuantity(iso1, atoms=1e24)
 
@@ -513,18 +523,21 @@ def test_irradiation_activate_errors():
     with pytest.raises(NeutronIrradiationError):
         ni.activate(barns, initial=iso0, activated=iso1)
     with pytest.raises(TypeError):
-        ni.activate(barns, initial=iq0, activated='asdf')
+        ni.activate(barns, initial=iq0, activated="asdf")
 
-    iso2 = Isotope('Na-25')
+    iso2 = Isotope("Na-25")
     with pytest.raises(NotImplementedError):
         ni.activate(barns, initial=iq1, activated=iso2)
 
 
-@pytest.mark.parametrize('start, stop, n_cm2, n_cm2_s', [
-    ('2017-01-01 00:00:00', '2017-01-01 12:00:00', None, 1e12),
-    ('2017-01-01 00:00:00', '2017-01-01 12:00:00', 1e15, None),
-    ('2017-01-01 00:00:00', '2017-01-01 00:00:00', 1e15, None)
-])
+@pytest.mark.parametrize(
+    "start, stop, n_cm2, n_cm2_s",
+    [
+        ("2017-01-01 00:00:00", "2017-01-01 12:00:00", None, 1e12),
+        ("2017-01-01 00:00:00", "2017-01-01 12:00:00", 1e15, None),
+        ("2017-01-01 00:00:00", "2017-01-01 00:00:00", 1e15, None),
+    ],
+)
 def test_irradiation_str(start, stop, n_cm2, n_cm2_s):
     """Test NeutronIrradiation string representation"""
 
@@ -536,6 +549,7 @@ def test_irradiation_str(start, stop, n_cm2, n_cm2_s):
 #               decay_normalize
 # ----------------------------------------------------
 
+
 def test_decay_normalize(radioisotope):
     """Test decay_normalize()"""
 
@@ -547,8 +561,10 @@ def test_decay_normalize(radioisotope):
     # and underflow with small half-lives
     # note: 2^(-(1 day) / (85 seconds)) ~ sys.float_info.min
     if 90 < radioisotope.half_life < 1000 * 3.156e7:
-        interval2 = (now + datetime.timedelta(days=1),
-                     now + datetime.timedelta(days=1, hours=1))
+        interval2 = (
+            now + datetime.timedelta(days=1),
+            now + datetime.timedelta(days=1, hours=1),
+        )
         assert decay_normalize(radioisotope, interval1, interval2) > 1
         assert decay_normalize(radioisotope, interval2, interval1) < 1
 
@@ -558,8 +574,10 @@ def test_decay_normalize_errors(radioisotope):
 
     now = datetime.datetime.now()
     interval1 = (now, now + datetime.timedelta(hours=1))
-    interval2 = (now + datetime.timedelta(days=1),
-                 now + datetime.timedelta(days=1, hours=1))
+    interval2 = (
+        now + datetime.timedelta(days=1),
+        now + datetime.timedelta(days=1, hours=1),
+    )
     with pytest.raises(IsotopeQuantityError):
         decay_normalize(radioisotope, [now], interval2)
     with pytest.raises(IsotopeQuantityError):
diff --git a/tests/isotope_test.py b/tests/isotope_test.py
index 5416bb29..8c27564e 100644
--- a/tests/isotope_test.py
+++ b/tests/isotope_test.py
@@ -1,26 +1,25 @@
 """Test isotope.py classes."""
 
-from __future__ import print_function
 import numpy as np
 from becquerel.tools import element
 from becquerel.tools import isotope
 import pytest
 
 TEST_ISOTOPES = [
-    ('H-3', 'H', 3, ''),
-    ('He-4', 'He', 4, ''),
-    ('K-40', 'K', 40, ''),
-    ('Co-60', 'Co', 60, ''),
-    ('U-238', 'U', 238, ''),
-    ('Pu-244', 'Pu', 244, ''),
-    ('Tc-99m', 'Tc', 99, 'm'),
-    ('Tc-99m', 'Tc', '99', 'm'),
-    ('Tc-99m', 'Tc', 99, 1),
-    ('Pa-234m', 'Pa', 234, 'm'),
-    ('Hf-178m1', 'Hf', 178, 'm1'),
-    ('Hf-178m2', 'Hf', 178, 'm2'),
-    ('Hf-178m3', 'Hf', 178, 'm3'),
-    ('Hf-178m3', 'Hf', 178, 3),
+    ("H-3", "H", 3, ""),
+    ("He-4", "He", 4, ""),
+    ("K-40", "K", 40, ""),
+    ("Co-60", "Co", 60, ""),
+    ("U-238", "U", 238, ""),
+    ("Pu-244", "Pu", 244, ""),
+    ("Tc-99m", "Tc", 99, "m"),
+    ("Tc-99m", "Tc", "99", "m"),
+    ("Tc-99m", "Tc", 99, 1),
+    ("Pa-234m", "Pa", 234, "m"),
+    ("Hf-178m1", "Hf", 178, "m1"),
+    ("Hf-178m2", "Hf", 178, "m2"),
+    ("Hf-178m3", "Hf", 178, "m3"),
+    ("Hf-178m3", "Hf", 178, 3),
 ]
 
 
@@ -28,39 +27,37 @@
 #                   Isotope class
 # ----------------------------------------------------
 
-@pytest.mark.parametrize('iso_str, sym, A, m', TEST_ISOTOPES)
+
+@pytest.mark.parametrize("iso_str, sym, A, m", TEST_ISOTOPES)
 def test_isotope_init_args(iso_str, sym, A, m):
     """Test Isotope init with 2 or 3 args, depending on whether m is None."""
     sym = str(sym)
     name = element.element_name(sym)
     Z = element.element_z(sym)
     A = int(A)
-    elems = [
-        sym, sym.lower(), sym.upper(),
-        name, name.lower(), name.upper(),
-        Z, str(Z)]
+    elems = [sym, sym.lower(), sym.upper(), name, name.lower(), name.upper(), Z, str(Z)]
     mass_numbers = [A, str(A)]
     if isinstance(m, int):
         if m == 0:
-            m = ''
+            m = ""
         else:
-            m = 'm{}'.format(m)
-    if m == '':
-        isomer_levels = [None, '', 0]
-    elif m.lower() == 'm':
-        isomer_levels = ['m', 'M', 1]
-    elif m.lower() == 'm1':
-        isomer_levels = ['m1', 'M1']
+            m = "m{}".format(m)
+    if m == "":
+        isomer_levels = [None, "", 0]
+    elif m.lower() == "m":
+        isomer_levels = ["m", "M", 1]
+    elif m.lower() == "m1":
+        isomer_levels = ["m1", "M1"]
     else:
         isomer_levels = [m.lower(), m.upper(), int(m[1:])]
     for elem in elems:
         for mass in mass_numbers:
             for isomer in isomer_levels:
                 args_list = [(elem, mass, isomer)]
-                if isomer == '' or isomer is None:
+                if isomer == "" or isomer is None:
                     args_list.append((elem, mass))
                 for args in args_list:
-                    print('')
+                    print("")
                     print(args)
                     i = isotope.Isotope(*args)
                     print(i)
@@ -69,17 +66,20 @@ def test_isotope_init_args(iso_str, sym, A, m):
                     assert i.m == m
 
 
-@pytest.mark.parametrize('elem, A, m', [
-    ('Xx', 45, ''),     # invalid element symbol
-    (119, 250, ''),     # invalid Z (not in range 1..118)
-    ('H', -1, ''),      # invalid A (negative)
-    ('H', 'AA', ''),    # invalid A (string that cannot be converted to int)
-    ('Ge', 30, ''),     # invalid N (negative)
-    ('Tc', 99, 'n'),    # invalid m (does not start with 'm')
-    ('Tc', 99, 'm-1'),  # invalid m (negative, str)
-    ('Tc', 99, -1),     # invalid m (negative, int)
-    ('Tc', 99, 1.0),    # invalid m (floating point)
-])
+@pytest.mark.parametrize(
+    "elem, A, m",
+    [
+        ("Xx", 45, ""),  # invalid element symbol
+        (119, 250, ""),  # invalid Z (not in range 1..118)
+        ("H", -1, ""),  # invalid A (negative)
+        ("H", "AA", ""),  # invalid A (string that cannot be converted to int)
+        ("Ge", 30, ""),  # invalid N (negative)
+        ("Tc", 99, "n"),  # invalid m (does not start with 'm')
+        ("Tc", 99, "m-1"),  # invalid m (negative, str)
+        ("Tc", 99, -1),  # invalid m (negative, int)
+        ("Tc", 99, 1.0),  # invalid m (floating point)
+    ],
+)
 def test_isotope_init_args_exceptions(elem, A, m):
     """Test Isotope init raises exceptions in some cases."""
     with pytest.raises(isotope.IsotopeError):
@@ -101,18 +101,21 @@ def test_isotope_init_args_exception_1arg_nonstring():
 def test_isotope_init_args_exception_4args():
     """Test Isotope init raises exception if four arguments given."""
     with pytest.raises(isotope.IsotopeError):
-        isotope.Isotope('Tc', 99, 'm', 0)
-
-
-@pytest.mark.parametrize('sym1, A1, m1, sym2, A2, m2, equal', [
-    ('Tc', 99, '', 'Tc', 99, '', True),       # symbol and A only
-    ('Tc', 99, 'm', 'Tc', 99, 'm', True),     # symbol, A, and m
-    ('Hf', 178, 'm', 'Hf', 178, 'm1', True),  # m and m1 are equivalent
-    ('Ge', 68, '', 'Ga', 68, '', False),      # symbols differ
-    ('Ge', 68, '', 'Ge', 69, '', False),      # masses differ
-    ('Ge', 68, '', 'Ge', 69, 'm', False),     # metastable states differ
-    ('Ge', 68, 'm1', 'Ge', 69, 'm2', False),  # metastable states differ
-])
+        isotope.Isotope("Tc", 99, "m", 0)
+
+
+@pytest.mark.parametrize(
+    "sym1, A1, m1, sym2, A2, m2, equal",
+    [
+        ("Tc", 99, "", "Tc", 99, "", True),  # symbol and A only
+        ("Tc", 99, "m", "Tc", 99, "m", True),  # symbol, A, and m
+        ("Hf", 178, "m", "Hf", 178, "m1", True),  # m and m1 are equivalent
+        ("Ge", 68, "", "Ga", 68, "", False),  # symbols differ
+        ("Ge", 68, "", "Ge", 69, "", False),  # masses differ
+        ("Ge", 68, "", "Ge", 69, "m", False),  # metastable states differ
+        ("Ge", 68, "m1", "Ge", 69, "m2", False),  # metastable states differ
+    ],
+)
 def test_isotope_equality(sym1, A1, m1, sym2, A2, m2, equal):
     """Test Isotope equality and inequality."""
     i1 = isotope.Isotope(sym1, A1, m1)
@@ -125,12 +128,12 @@ def test_isotope_equality(sym1, A1, m1, sym2, A2, m2, equal):
 
 def test_isotope_equality_exception():
     """Test TypeError is raised when comparing an Isotope to a non-Isotope."""
-    i1 = isotope.Isotope('Tc', 99, 'm')
+    i1 = isotope.Isotope("Tc", 99, "m")
     with pytest.raises(TypeError):
-        assert i1 == 'Tc-99m'
+        assert i1 == "Tc-99m"
 
 
-@pytest.mark.parametrize('iso_str, sym, A, m', TEST_ISOTOPES)
+@pytest.mark.parametrize("iso_str, sym, A, m", TEST_ISOTOPES)
 def test_isotope_init_str(iso_str, sym, A, m):
     """Test Isotope init with one (string) argument.
 
@@ -143,46 +146,49 @@ def test_isotope_init_str(iso_str, sym, A, m):
     mass_number = str(A)
     if m is not None:
         if isinstance(m, int):
-            mass_number += 'm{}'.format(m)
+            mass_number += "m{}".format(m)
         else:
             mass_number += m
     expected = isotope.Isotope(sym, A, m)
     for name in [sym, element.element_name(sym)]:
         iso_tests = [
-            name + '-' + mass_number,
+            name + "-" + mass_number,
             name + mass_number,
-            mass_number + '-' + name,
+            mass_number + "-" + name,
             mass_number + name,
         ]
         for iso in iso_tests:
             for iso2 in [iso, iso.upper(), iso.lower()]:
-                print('')
-                print('{}-{}: {}'.format(sym, mass_number, iso2))
+                print("")
+                print("{}-{}: {}".format(sym, mass_number, iso2))
                 i = isotope.Isotope(iso2)
                 print(i)
                 assert i == expected
 
 
-@pytest.mark.parametrize('iso_str, raises', [
-    ('He_4', True),       # underscore
-    ('He--4', True),      # hyphens
-    ('4399', True),       # all numbers
-    ('abdc', True),       # all letters
-    ('Tc-99m3m', True),   # too many ms
-    ('55mN', False),      # ambiguous but valid (returns Mn-55, not N-55m)
-    ('55m2N', False),     # like previous but unambiguous (returns N-55m2)
-    ('24Mg', False),      # unambiguous because G is not an element
-    ('24m2G', True),      # like previous but invalid
-    ('2He4', True),       # invalid mass number
-    ('2He-4', True),      # invalid mass number
-    ('He2-4', True),      # invalid mass number
-    ('Xz-90', True),      # invalid element
-    ('H-AA', True),       # invalid A (string that cannot be converted to int)
-    ('H-20Xm1', True),    # invalid A (string that cannot be converted to int)
-    ('Tc-99n', True),     # invalid m (does not start with 'm')
-    ('Hf-178n3', True),   # invalid m (does not start with 'm')
-    ('Tc-99m1.0', True),  # invalid m (floating point)
-])
+@pytest.mark.parametrize(
+    "iso_str, raises",
+    [
+        ("He_4", True),  # underscore
+        ("He--4", True),  # hyphens
+        ("4399", True),  # all numbers
+        ("abdc", True),  # all letters
+        ("Tc-99m3m", True),  # too many ms
+        ("55mN", False),  # ambiguous but valid (returns Mn-55, not N-55m)
+        ("55m2N", False),  # like previous but unambiguous (returns N-55m2)
+        ("24Mg", False),  # unambiguous because G is not an element
+        ("24m2G", True),  # like previous but invalid
+        ("2He4", True),  # invalid mass number
+        ("2He-4", True),  # invalid mass number
+        ("He2-4", True),  # invalid mass number
+        ("Xz-90", True),  # invalid element
+        ("H-AA", True),  # invalid A (string that cannot be converted to int)
+        ("H-20Xm1", True),  # invalid A (string that cannot be converted to int)
+        ("Tc-99n", True),  # invalid m (does not start with 'm')
+        ("Hf-178n3", True),  # invalid m (does not start with 'm')
+        ("Tc-99m1.0", True),  # invalid m (floating point)
+    ],
+)
 def test_isotope_init_str_exceptions(iso_str, raises):
     """Test Isotope init exceptions with one (string) argument."""
     if raises:
@@ -192,7 +198,7 @@ def test_isotope_init_str_exceptions(iso_str, raises):
         isotope.Isotope(iso_str)
 
 
-@pytest.mark.parametrize('iso_str, sym, A, m', TEST_ISOTOPES)
+@pytest.mark.parametrize("iso_str, sym, A, m", TEST_ISOTOPES)
 def test_isotope_str(iso_str, sym, A, m):
     """Test Isotope.__str__()."""
     i = isotope.Isotope(sym, A, m)
@@ -201,27 +207,60 @@ def test_isotope_str(iso_str, sym, A, m):
 
 
 ISOTOPE_PROPERTIES = [
-    ('H-3', (3.888E8, False, None, '1/2+', 0., 14.94980957, [['B-'], [100.]],)),
-    ('He-4', (np.inf, True, 99.999866, '0+', 0., 2.4249, [[], []])),
-    ('K-40', (3.938E16, False, 0.0117, '4-', 0., -33.53540, [['B-', 'EC'], [89.28, 10.72]])),
-    ('Co-60', (1.663E8, False, None, '5+', 0., -61.6503, [['B-'], [100.]])),
-    ('U-238', (1.41E17, False, 99.2742, '0+', 0., 47.3077, [['A', 'SF'], [100.00, 5.4E-5]])),
-    ('Pu-244', (2.525E15, False, None, '0+', 0., 59.8060, [['A', 'SF'], [99.88, 0.12]])),
-    ('Tc-99m', (21624.12, False, None, '1/2-', 0.1427, -87.1851, [['IT', 'B-'], [100., 3.7E-3]])),
-    ('Pa-234m', (69.54, False, None, '(0-)', 0.0739, 40.413, [['IT', 'B-'], [0.16, 99.84]])),
-    ('Hf-178', (np.inf, True, 27.28, '0+', 0., -52.4352, [[], []])),
-    ('Hf-178m1', (4., False, None, '8-', 1.1474, -51.2878, [['IT'], [100.]])),
-    ('Hf-178m2', (9.783E8, False, None, '16+', 2.4461, -49.9891, [['IT'], [100.]])),
+    (
+        "H-3",
+        (
+            3.888e8,
+            False,
+            None,
+            "1/2+",
+            0.0,
+            14.94980957,
+            [["B-"], [100.0]],
+        ),
+    ),
+    ("He-4", (np.inf, True, 99.999866, "0+", 0.0, 2.4249, [[], []])),
+    (
+        "K-40",
+        (3.938e16, False, 0.0117, "4-", 0.0, -33.53540, [["B-", "EC"], [89.28, 10.72]]),
+    ),
+    ("Co-60", (1.663e8, False, None, "5+", 0.0, -61.6503, [["B-"], [100.0]])),
+    (
+        "U-238",
+        (1.41e17, False, 99.2742, "0+", 0.0, 47.3077, [["A", "SF"], [100.00, 5.4e-5]]),
+    ),
+    (
+        "Pu-244",
+        (2.525e15, False, None, "0+", 0.0, 59.8060, [["A", "SF"], [99.88, 0.12]]),
+    ),
+    (
+        "Tc-99m",
+        (
+            21624.12,
+            False,
+            None,
+            "1/2-",
+            0.1427,
+            -87.1851,
+            [["IT", "B-"], [100.0, 3.7e-3]],
+        ),
+    ),
+    (
+        "Pa-234m",
+        (69.54, False, None, "(0-)", 0.0739, 40.413, [["IT", "B-"], [0.16, 99.84]]),
+    ),
+    ("Hf-178", (np.inf, True, 27.28, "0+", 0.0, -52.4352, [[], []])),
+    ("Hf-178m1", (4.0, False, None, "8-", 1.1474, -51.2878, [["IT"], [100.0]])),
+    ("Hf-178m2", (9.783e8, False, None, "16+", 2.4461, -49.9891, [["IT"], [100.0]])),
 ]
 
 
 @pytest.mark.webtest
-@pytest.mark.parametrize('iso_str, props', ISOTOPE_PROPERTIES)
+@pytest.mark.parametrize("iso_str, props", ISOTOPE_PROPERTIES)
 def test_isotope_properties(iso_str, props):
     """Test that isotope properties are correct."""
     i = isotope.Isotope(iso_str)
-    half_life, is_stable, abundance, j_pi, energy_level, mass_excess, modes = \
-        props
+    half_life, is_stable, abundance, j_pi, energy_level, mass_excess, modes = props
     if not np.isinf(half_life):
         assert np.isclose(i.half_life, half_life)
     else:
@@ -233,12 +272,12 @@ def test_isotope_properties(iso_str, props):
         assert np.isclose(i.abundance.nominal_value, abundance)
     assert i.j_pi == j_pi
     assert np.isclose(i.energy_level, energy_level)
-    print('mass excess:', i.mass_excess, type(i.mass_excess))
+    print("mass excess:", i.mass_excess, type(i.mass_excess))
     if mass_excess is None:
         assert i.mass_excess is None
     else:
-        print('mass excess:', i.mass_excess.nominal_value)
-        print('mass excess:', mass_excess)
+        print("mass excess:", i.mass_excess.nominal_value)
+        print("mass excess:", mass_excess)
         print(np.isclose(mass_excess, mass_excess))
         print(np.isclose(mass_excess, i.mass_excess.nominal_value))
         print(np.isclose(mass_excess, (i.mass_excess).nominal_value))
diff --git a/tests/materials_test.py b/tests/materials_test.py
index 8e2b0c8f..00e86222 100644
--- a/tests/materials_test.py
+++ b/tests/materials_test.py
@@ -1,11 +1,8 @@
 """Test NIST material data queries."""
 
-from __future__ import print_function
 from becquerel.tools import materials
 import pytest
 
-# pylint: disable=no-self-use
-
 
 @pytest.mark.webtest
 class TestConvertComposition(object):
diff --git a/tests/nndc_test.py b/tests/nndc_test.py
index f664dd41..1192ca92 100644
--- a/tests/nndc_test.py
+++ b/tests/nndc_test.py
@@ -1,14 +1,10 @@
 """Test NNDC data queries."""
 
-from __future__ import print_function
 import numpy as np
 import pandas as pd
 from becquerel.tools import nndc
 import pytest
 
-# pylint: disable=protected-access,no-self-use,too-many-public-methods
-# pylint: disable=attribute-defined-outside-init,missing-docstring
-
 
 def ufloats_overlap_range(ufloats, vmin, vmax):
     """Return whether the +/- 1 sigma range overlaps the value range."""
@@ -31,114 +27,114 @@ class TestParseFloatUncertainty(object):
 
     def test_01(self):
         """Test _parse_float_uncertainty('257.123', '0.005')..............."""
-        answer = nndc._parse_float_uncertainty('257.123', '0.005')
+        answer = nndc._parse_float_uncertainty("257.123", "0.005")
         assert np.isclose(answer.nominal_value, 257.123)
         assert np.isclose(answer.std_dev, 0.005)
 
     def test_02(self):
         """Test _parse_float_uncertainty('257.123', '0.015')..............."""
-        answer = nndc._parse_float_uncertainty('257.123', '0.015')
+        answer = nndc._parse_float_uncertainty("257.123", "0.015")
         assert np.isclose(answer.nominal_value, 257.123)
         assert np.isclose(answer.std_dev, 0.015)
 
     def test_03(self):
         """Test _parse_float_uncertainty('257.123E0', '0.005')............."""
-        answer = nndc._parse_float_uncertainty('257.123E0', '0.005')
+        answer = nndc._parse_float_uncertainty("257.123E0", "0.005")
         assert np.isclose(answer.nominal_value, 257.123)
         assert np.isclose(answer.std_dev, 0.005)
 
     def test_04(self):
         """Test _parse_float_uncertainty('257.123E+0', '0.005')............"""
-        answer = nndc._parse_float_uncertainty('257.123E+0', '0.005')
+        answer = nndc._parse_float_uncertainty("257.123E+0", "0.005")
         assert np.isclose(answer.nominal_value, 257.123)
         assert np.isclose(answer.std_dev, 0.005)
 
     def test_05(self):
         """Test _parse_float_uncertainty('257.123E4', '5E3')..............."""
-        answer = nndc._parse_float_uncertainty('257.123E4', '5E3')
-        assert np.isclose(answer.nominal_value, 2571230.)
-        assert np.isclose(answer.std_dev, 5000.)
+        answer = nndc._parse_float_uncertainty("257.123E4", "5E3")
+        assert np.isclose(answer.nominal_value, 2571230.0)
+        assert np.isclose(answer.std_dev, 5000.0)
 
     def test_06(self):
         """Test _parse_float_uncertainty('257.123E+4', '5E-3')............."""
-        answer = nndc._parse_float_uncertainty('257.123E+4', '5E-3')
-        assert np.isclose(answer.nominal_value, 2571230.)
+        answer = nndc._parse_float_uncertainty("257.123E+4", "5E-3")
+        assert np.isclose(answer.nominal_value, 2571230.0)
         assert np.isclose(answer.std_dev, 0.005)
 
     def test_07(self):
         """Test _parse_float_uncertainty('257.123E+4', '5E-1E-2').........."""
-        answer = nndc._parse_float_uncertainty('257.123E+4', '5E-1E-2')
-        assert np.isclose(answer.nominal_value, 2571230.)
+        answer = nndc._parse_float_uncertainty("257.123E+4", "5E-1E-2")
+        assert np.isclose(answer.nominal_value, 2571230.0)
         assert np.isclose(answer.std_dev, 0.005)
 
     def test_08(self):
         """Test _parse_float_uncertainty('257.123', '')...................."""
-        answer = nndc._parse_float_uncertainty('257.123', '')
+        answer = nndc._parse_float_uncertainty("257.123", "")
         assert isinstance(answer, float)
         assert np.isclose(answer, 257.123)
 
     def test_09(self):
         """Test _parse_float_uncertainty('8', '2')........................."""
-        answer = nndc._parse_float_uncertainty('8', '2')
-        assert np.isclose(answer.nominal_value, 8.)
-        assert np.isclose(answer.std_dev, 2.)
+        answer = nndc._parse_float_uncertainty("8", "2")
+        assert np.isclose(answer.nominal_value, 8.0)
+        assert np.isclose(answer.std_dev, 2.0)
 
     def test_10(self):
         """Test _parse_float_uncertainty('8', '').........................."""
-        answer = nndc._parse_float_uncertainty('8', '')
+        answer = nndc._parse_float_uncertainty("8", "")
         assert isinstance(answer, float)
-        assert np.isclose(answer, 8.)
+        assert np.isclose(answer, 8.0)
 
     def test_11(self):
         """Test _parse_float_uncertainty('100.0%', '')....................."""
-        answer = nndc._parse_float_uncertainty('100.0%', '')
-        assert np.isclose(answer, 100.)
+        answer = nndc._parse_float_uncertainty("100.0%", "")
+        assert np.isclose(answer, 100.0)
 
     def test_12(self):
         """Test _parse_float_uncertainty('73.92+X', '')...................."""
-        answer = nndc._parse_float_uncertainty('73.92+X', '')
+        answer = nndc._parse_float_uncertainty("73.92+X", "")
         assert np.isclose(answer, 73.92)
 
     def test_13(self):
         """Test _parse_float_uncertainty('Y', '').........................."""
-        answer = nndc._parse_float_uncertainty('Y', '')
-        assert answer == 0.
+        answer = nndc._parse_float_uncertainty("Y", "")
+        assert answer == 0.0
 
     def test_14(self):
         """Test _parse_float_uncertainty('', '')..........................."""
-        answer = nndc._parse_float_uncertainty('', '')
+        answer = nndc._parse_float_uncertainty("", "")
         assert answer is None
 
     def test_15(self):
         """Test _parse_float_uncertainty('****', '')......................."""
-        answer = nndc._parse_float_uncertainty('****', '')
+        answer = nndc._parse_float_uncertainty("****", "")
         assert answer is None
 
     def test_16(self):
         """Test _parse_float_uncertainty('~7', '1')........................"""
-        answer = nndc._parse_float_uncertainty('~7', '1')
-        assert np.isclose(answer.nominal_value, 7.)
-        assert np.isclose(answer.std_dev, 1.)
+        answer = nndc._parse_float_uncertainty("~7", "1")
+        assert np.isclose(answer.nominal_value, 7.0)
+        assert np.isclose(answer.std_dev, 1.0)
 
     def test_17(self):
         """Test _parse_float_uncertainty('1', '****')......................"""
-        answer = nndc._parse_float_uncertainty('1', '****')
-        assert np.isclose(answer, 1.)
+        answer = nndc._parse_float_uncertainty("1", "****")
+        assert np.isclose(answer, 1.0)
 
     def test_18(self):
         """Test _parse_float_uncertainty('73.92', 'AP')...................."""
-        answer = nndc._parse_float_uncertainty('73.92', 'AP')
+        answer = nndc._parse_float_uncertainty("73.92", "AP")
         assert np.isclose(answer, 73.92)
 
     def test_19(self):
         """Test _parse_float_uncertainty('73.92', 'CA')...................."""
-        answer = nndc._parse_float_uncertainty('73.92', 'CA')
+        answer = nndc._parse_float_uncertainty("73.92", "CA")
         assert np.isclose(answer, 73.92)
 
     def test_20(self):
         """Test _parse_float_uncertainty('7', '@') raises NNDCRequestError."""
         with pytest.raises(nndc.NNDCRequestError):
-            nndc._parse_float_uncertainty('7', '@')
+            nndc._parse_float_uncertainty("7", "@")
 
 
 class NNDCQueryTests(object):
@@ -147,7 +143,7 @@ class NNDCQueryTests(object):
     def setup_method(self):
         self.cls = nndc._NNDCQuery
 
-        def fetch_dummy(**kwargs):  # pylint: disable=unused-argument
+        def fetch_dummy(**kwargs):
             """Dummy fetch_ function for self.fetch."""
             return pd.DataFrame()
 
@@ -159,42 +155,42 @@ def stable_isotope_condition(self, df):
 
     def test_query_nuc_Co60(self):
         """Test NNDCQuery: nuc='Co-60'....................................."""
-        d = self.fetch(nuc='Co-60')
+        d = self.fetch(nuc="Co-60")
         assert len(d) > 0
         assert (d.Z == 27).all()
         assert (d.A == 60).all()
 
     def test_query_nuc_He4(self):
         """Test NNDCQuery: nuc='He-4'......................................"""
-        d = self.fetch(nuc='He-4')
+        d = self.fetch(nuc="He-4")
         assert self.stable_isotope_condition(d)
         assert (d.Z == 2).all()
         assert (d.A == 4).all()
 
     def test_query_nuc_V50(self):
         """Test NNDCQuery: nuc='V-50'......................................"""
-        d = self.fetch(nuc='V-50')
+        d = self.fetch(nuc="V-50")
         assert len(d) > 0
         assert (d.Z == 23).all()
         assert (d.A == 50).all()
 
     def test_query_nuc_Ge70(self):
         """Test NNDCQuery: nuc='Ge-70'....................................."""
-        d = self.fetch(nuc='Ge-70')
+        d = self.fetch(nuc="Ge-70")
         assert self.stable_isotope_condition(d)
         assert (d.Z == 32).all()
         assert (d.A == 70).all()
 
     def test_query_nuc_U238(self):
         """Test NNDCQuery: nuc='U-238'....................................."""
-        d = self.fetch(nuc='U-238')
+        d = self.fetch(nuc="U-238")
         assert len(d) > 0
         assert (d.Z == 92).all()
         assert (d.A == 238).all()
 
     def test_query_nuc_Pa234(self):
         """Test NNDCQuery: nuc='Pa-234'...................................."""
-        d = self.fetch(nuc='Pa-234')
+        d = self.fetch(nuc="Pa-234")
         assert len(d) > 0
         assert (d.Z == 91).all()
         assert (d.A == 234).all()
@@ -349,8 +345,7 @@ def test_query_nrange_9_10_n_odd(self):
 
     def test_query_zrange_9_10_z_even_a_even_n_any(self):
         """Test NNDCQuery: z_range=(9, 10), z_even, a_even, n_any.........."""
-        d = self.fetch(
-            z_range=(9, 10), z_even=True, a_even=True, n_any=True)
+        d = self.fetch(z_range=(9, 10), z_even=True, a_even=True, n_any=True)
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
         assert (d.Z % 2 == 0).all()
@@ -366,17 +361,17 @@ def test_query_z_6_arange_11_13(self):
     def test_query_exception_not_found(self):
         """Test NNDCQuery exception if website not found..................."""
         _URL_ORIG = self.cls._URL
-        self.cls._URL = 'http://httpbin.org/status/404'
+        self.cls._URL = "http://httpbin.org/status/404"
         with pytest.raises(nndc.NNDCRequestError):
-            self.fetch(nuc='Co-60')
+            self.fetch(nuc="Co-60")
         self.cls._URL = _URL_ORIG
 
     def test_query_exception_empty(self):
         """Test NNDCQuery exception if website is empty...................."""
         _URL_ORIG = self.cls._URL
-        self.cls._URL = 'http://httpbin.org/post'
+        self.cls._URL = "http://httpbin.org/post"
         with pytest.raises(nndc.NNDCRequestError):
-            self.fetch(nuc='Co-60')
+            self.fetch(nuc="Co-60")
         self.cls._URL = _URL_ORIG
 
     def test_query_exception_range_None(self):
@@ -392,7 +387,7 @@ def test_query_exception_range_empty(self):
     def test_query_exception_range_len_1(self):
         """Test NNDCQuery exception if a range kwarg has length 1.........."""
         with pytest.raises(nndc.NNDCInputError):
-            self.fetch(n_range=(0, ))
+            self.fetch(n_range=(0,))
 
     def test_query_exception_range_len_3(self):
         """Test NNDCQuery exception if a range kwarg has length 3.........."""
@@ -416,42 +411,41 @@ def test_query_zrange_9_10_trange_None_1E4(self):
         d = self.fetch(z_range=(9, 10), t_range=(None, 1e4))
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
-        assert (d['T1/2 (s)'] <= 1e4).all()
+        assert (d["T1/2 (s)"] <= 1e4).all()
 
     def test_query_zrange_9_10_trange_0_1E4(self):
         """Test NNDCQuery: z_range=(9, 10), t_range=(0, 1e4)..............."""
         d = self.fetch(z_range=(9, 10), t_range=(0, 1e4))
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
-        assert (d['T1/2 (s)'] <= 1e4).all()
+        assert (d["T1/2 (s)"] <= 1e4).all()
 
     def test_query_zrange_9_10_trange_1E3_1E9(self):
         """Test NNDCQuery: z_range=(9, 10), t_range=(1e3, 1e9)............."""
         d = self.fetch(z_range=(9, 10), t_range=(1e3, 1e9))
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
-        assert ((1e3 <= d['T1/2 (s)']) & (d['T1/2 (s)'] <= 1e9)).all()
+        assert ((1e3 <= d["T1/2 (s)"]) & (d["T1/2 (s)"] <= 1e9)).all()
 
     def test_query_zrange_5_10_trange_1E9_None(self):
         """Test NNDCQuery: z_range=(5, 10), t_range=(1e9, None)............"""
         d = self.fetch(z_range=(5, 10), t_range=(1e9, None))
         assert len(d) > 0
         assert ((5 <= d.Z) & (d.Z <= 10)).all()
-        assert (d['T1/2 (s)'] >= 1e9).all()
+        assert (d["T1/2 (s)"] >= 1e9).all()
 
     def test_query_zrange_5_10_trange_1E9_inf(self):
         """Test NNDCQuery: z_range=(5, 10), t_range=(1e9, np.inf).........."""
         d = self.fetch(z_range=(5, 10), t_range=(1e9, np.inf))
         assert len(d) > 0
         assert ((5 <= d.Z) & (d.Z <= 10)).all()
-        assert (d['T1/2 (s)'] >= 1e9).all()
+        assert (d["T1/2 (s)"] >= 1e9).all()
 
     def test_query_nuc_Pu239_decay_ANY(self):
         """Test NNDCQuery: nuc='Pu-239', decay='ANY'......................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'ANY']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "ANY"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Pu-239', decay=x) for x in kwargs]
+        ds = [self.fetch(nuc="Pu-239", decay=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 94).all()
@@ -460,10 +454,9 @@ def test_query_nuc_Pu239_decay_ANY(self):
 
     def test_query_nuc_Pu239_decay_A(self):
         """Test NNDCQuery: nuc='Pu-239', decay='Alpha'....................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'A']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "A"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Pu-239', decay=x) for x in kwargs]
+        ds = [self.fetch(nuc="Pu-239", decay=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 94).all()
@@ -472,10 +465,9 @@ def test_query_nuc_Pu239_decay_A(self):
 
     def test_query_nuc_Co60_decay_BM(self):
         """Test NNDCQuery: nuc='Co-60', decay='B-'........................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'B-']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "B-"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Co-60', decay=x) for x in kwargs]
+        ds = [self.fetch(nuc="Co-60", decay=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 27).all()
@@ -484,10 +476,9 @@ def test_query_nuc_Co60_decay_BM(self):
 
     def test_query_nuc_Na22_decay_ECBP(self):
         """Test NNDCQuery: nuc='Na-22', decay='EC+B+'......................"""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'ECBP']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "ECBP"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Na-22', decay=x) for x in kwargs]
+        ds = [self.fetch(nuc="Na-22", decay=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 11).all()
@@ -496,10 +487,9 @@ def test_query_nuc_Na22_decay_ECBP(self):
 
     def test_query_nuc_Tc99_decay_IT(self):
         """Test NNDCQuery: nuc='Tc-99', decay='IT'........................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'IT']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "IT"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Tc-99', decay=x) for x in kwargs]
+        ds = [self.fetch(nuc="Tc-99", decay=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 43).all()
@@ -508,8 +498,7 @@ def test_query_nuc_Tc99_decay_IT(self):
 
     def test_query_nuc_zrange_1_50_decay_N(self):
         """Test NNDCQuery: z_range=(1, 50), decay='N'......................"""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'N']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "N"]
         assert len(kwargs) > 0
         ds = [self.fetch(z_range=(1, 50), decay=x) for x in kwargs]
         for d in ds:
@@ -519,8 +508,7 @@ def test_query_nuc_zrange_1_50_decay_N(self):
 
     def test_query_nuc_zrange_1_50_decay_P(self):
         """Test NNDCQuery: z_range=(1, 50), decay='P'......................"""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'P']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "P"]
         assert len(kwargs) > 0
         ds = [self.fetch(z_range=(1, 50), decay=x) for x in kwargs]
         for d in ds:
@@ -531,42 +519,42 @@ def test_query_nuc_zrange_1_50_decay_P(self):
     def test_query_exception_bad_decay(self):
         """Test NNDCQuery raises exception for an invalid decay mode......."""
         with pytest.raises(nndc.NNDCInputError):
-            self.fetch(nuc='Pu-239', decay='InvalidMode')
+            self.fetch(nuc="Pu-239", decay="InvalidMode")
 
     def test_query_zrange_9_10_elevelrange_0_01(self):
         """Test NNDCQuery: z_range=(9, 10), elevel_range=(0, 0.1).........."""
         d = self.fetch(z_range=(9, 10), elevel_range=(0, 0.1))
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
-        assert ufloats_overlap_range(d['Energy Level (MeV)'], 0, 0.1)
+        assert ufloats_overlap_range(d["Energy Level (MeV)"], 0, 0.1)
 
     def test_query_zrange_9_10_elevelrange_None_01(self):
         """Test NNDCQuery: z_range=(9, 10), elevel_range=(None, 0.1)......."""
         d = self.fetch(z_range=(9, 10), elevel_range=(None, 0.1))
         assert len(d) > 0
         assert ((9 <= d.Z) & (d.Z <= 10)).all()
-        assert ufloats_overlap_range(d['Energy Level (MeV)'], 0, 0.1)
+        assert ufloats_overlap_range(d["Energy Level (MeV)"], 0, 0.1)
 
     def test_query_zrange_7_20_elevelrange_01_10(self):
         """Test NNDCQuery: z_range=(7, 20), elevel_range=(0.1, 10)........."""
         d = self.fetch(z_range=(7, 20), elevel_range=(0.1, 10))
         assert len(d) > 0
         assert ((7 <= d.Z) & (d.Z <= 20)).all()
-        assert ufloats_overlap_range(d['Energy Level (MeV)'], 0.1, 10)
+        assert ufloats_overlap_range(d["Energy Level (MeV)"], 0.1, 10)
 
     def test_query_zrange_7_20_elevelrange_01_None(self):
         """Test NNDCQuery: z_range=(7, 20), elevel_range=(0.1, None)......."""
         d = self.fetch(z_range=(7, 20), elevel_range=(0.1, None))
         assert len(d) > 0
         assert ((7 <= d.Z) & (d.Z <= 20)).all()
-        assert ufloats_overlap_range(d['Energy Level (MeV)'], 0.1, 1e9)
+        assert ufloats_overlap_range(d["Energy Level (MeV)"], 0.1, 1e9)
 
     def test_query_zrange_7_20_elevelrange_01_inf(self):
         """Test NNDCQuery: z_range=(7, 20), elevel_range=(0.1, np.inf)....."""
         d = self.fetch(z_range=(7, 20), elevel_range=(0.1, np.inf))
         assert len(d) > 0
         assert ((7 <= d.Z) & (d.Z <= 20)).all()
-        assert ufloats_overlap_range(d['Energy Level (MeV)'], 0.1, 1e9)
+        assert ufloats_overlap_range(d["Energy Level (MeV)"], 0.1, 1e9)
 
 
 @pytest.mark.webtest
@@ -580,7 +568,7 @@ def setup_method(self):
     def test_wallet_nuc_Pa234m(self):
         """Test fetch_wallet_card: nuc='Pa-234m' raises exception.........."""
         with pytest.raises(nndc.NNDCRequestError):
-            self.fetch(nuc='Pa-234m')
+            self.fetch(nuc="Pa-234m")
 
     def test_wallet_zrange_230_250(self):
         """Test fetch_wallet_card: z_range=(230, 250) raises exception....."""
@@ -590,18 +578,18 @@ def test_wallet_zrange_230_250(self):
     def test_wallet_zrange_9_10_j_0(self):
         """Test fetch_wallet_card: z_range=(9, 10), j='0'.................."""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(z_range=(9, 10), j='0')
+            d = self.fetch(z_range=(9, 10), j="0")
             assert len(d) > 0
 
     def test_wallet_zrange_9_10_j_3_2(self):
         """Test fetch_wallet_card: z_range=(9, 10), j='3/2'................"""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(z_range=(9, 10), j='3/2')
+            d = self.fetch(z_range=(9, 10), j="3/2")
             assert len(d) > 0
 
     def test_wallet_zrange_9_10_parity_any(self):
         """Test fetch_wallet_card: z_range=(9, 10), parity='any'..........."""
-        kwargs = ['any', 'ANY']
+        kwargs = ["any", "ANY"]
         ds = [self.fetch(z_range=(9, 10), parity=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
@@ -611,40 +599,41 @@ def test_wallet_zrange_9_10_parity_any(self):
     def test_wallet_zrange_9_10_parity_p(self):
         """Test fetch_wallet_card: z_range=(9, 10), parity='+'............."""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(z_range=(9, 10), parity='+')
+            d = self.fetch(z_range=(9, 10), parity="+")
             assert len(d) > 0
 
     def test_wallet_zrange_9_10_parity_m(self):
         """Test fetch_wallet_card: z_range=(9, 10), parity='-'............."""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(z_range=(9, 10), parity='-')
+            d = self.fetch(z_range=(9, 10), parity="-")
             assert len(d) > 0
 
     def test_wallet_zrange_9_10_parity_bad(self):
         """Test fetch_wallet_card raises except. for invalid parity........"""
         with pytest.raises(nndc.NNDCInputError):
-            self.fetch(z_range=(9, 10), parity='InvalidParity')
+            self.fetch(z_range=(9, 10), parity="InvalidParity")
 
     def test_wallet_zrange_9_10_j_2_parity_p(self):
         """Test fetch_wallet_card: z_range=(9, 10), j='2', parity='+'......"""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(z_range=(9, 10), j='2', parity='+')
+            d = self.fetch(z_range=(9, 10), j="2", parity="+")
             assert len(d) > 0
 
     def test_wallet_j_10(self):
         """Test fetch_wallet_card: j='10'.................................."""
         with pytest.raises(nndc.NNDCRequestError):
-            d = self.fetch(j='10')
+            d = self.fetch(j="10")
             assert len(d) > 0
 
-    @pytest.mark.skip(reason='query kwarg "decay" seems to not be working ' +
-                             'on NNDC, and as a result too many results ' +
-                             'are returned for this test, causing an ' +
-                             'NNDCRequestError')
+    @pytest.mark.skip(
+        reason='query kwarg "decay" seems to not be working '
+        + "on NNDC, and as a result too many results "
+        + "are returned for this test, causing an "
+        + "NNDCRequestError"
+    )
     def test_wallet_decay_SF(self):
         """Test fetch_wallet_card: decay='SF'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'SF']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "SF"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -653,8 +642,7 @@ def test_wallet_decay_SF(self):
 
     def test_wallet_decay_cluster(self):
         """Test fetch_wallet_card: decay='Cluster'........................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'C']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "C"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -663,8 +651,7 @@ def test_wallet_decay_cluster(self):
 
     def test_wallet_decay_DB(self):
         """Test fetch_wallet_card: decay='DB'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'DB']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "DB"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -673,8 +660,7 @@ def test_wallet_decay_DB(self):
 
     def test_wallet_decay_DN(self):
         """Test fetch_wallet_card: decay='DN'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'DN']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "DN"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -683,8 +669,7 @@ def test_wallet_decay_DN(self):
 
     def test_wallet_decay_DP(self):
         """Test fetch_wallet_card: decay='DP'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'DP']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "DP"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -693,8 +678,7 @@ def test_wallet_decay_DP(self):
 
     def test_wallet_decay_DA(self):
         """Test fetch_wallet_card: decay='DA'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'DA']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "DA"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -703,8 +687,7 @@ def test_wallet_decay_DA(self):
 
     def test_wallet_decay_DF(self):
         """Test fetch_wallet_card: decay='DF'.............................."""
-        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items()
-                  if val == 'DF']
+        kwargs = [kw for kw, val in nndc.WALLET_DECAY_MODE.items() if val == "DF"]
         assert len(kwargs) > 0
         ds = [self.fetch(decay=x) for x in kwargs]
         for d in ds:
@@ -731,11 +714,11 @@ def stable_isotope_condition(self, df):
 
     def test_decay_nuc_Pa234m(self):
         """Test fetch_decay_radiation: no exception for nuc='Pa-234m'......"""
-        d = self.fetch(nuc='Pa-234m')
+        d = self.fetch(nuc="Pa-234m")
         assert len(d) == 0
         assert (d.Z == 91).all()
         assert (d.A == 234).all()
-        assert (d['Energy Level (MeV)'] > 0).all()
+        assert (d["Energy Level (MeV)"] > 0).all()
 
     def test_decay_zrange_230_250(self):
         """Test fetch_decay_radiation: z_range=(230, 250) dataframe empty.."""
@@ -744,10 +727,9 @@ def test_decay_zrange_230_250(self):
 
     def test_decay_nuc_Pu239_type_G(self):
         """Test fetch_decay_radiation: nuc='Pu-239', type='Gamma'.........."""
-        kwargs = [kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items()
-                  if val == 'G']
+        kwargs = [kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items() if val == "G"]
         assert len(kwargs) > 0
-        ds = [self.fetch(nuc='Pu-239', type=x) for x in kwargs]
+        ds = [self.fetch(nuc="Pu-239", type=x) for x in kwargs]
         for d in ds:
             assert len(d) > 0
             assert (d.Z == 94).all()
@@ -757,12 +739,13 @@ def test_decay_nuc_Pu239_type_G(self):
     def test_decay_exception_bad_type(self):
         """Test fetch_decay_radiation raises except. for invalid rad type.."""
         with pytest.raises(nndc.NNDCInputError):
-            self.fetch(nuc='Pu-239', type='InvalidType')
+            self.fetch(nuc="Pu-239", type="InvalidType")
 
     def test_decay_zrange_100_102_type_ANY(self):
         """Test fetch_decay_radiation: z_range=(100, 102), type='ANY'......"""
-        kwargs = [kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items()
-                  if val == 'ANY']
+        kwargs = [
+            kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items() if val == "ANY"
+        ]
         assert len(kwargs) > 0
         ds = [self.fetch(z_range=(100, 102), type=x) for x in kwargs]
         for d in ds:
@@ -772,8 +755,7 @@ def test_decay_zrange_100_102_type_ANY(self):
 
     def test_decay_zrange_100_102_type_G(self):
         """Test fetch_decay_radiation: z_range=(100, 102), type='Gamma'...."""
-        kwargs = [kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items()
-                  if val == 'G']
+        kwargs = [kw for kw, val in nndc.DECAYRAD_RADIATION_TYPE.items() if val == "G"]
         assert len(kwargs) > 0
         ds = [self.fetch(z_range=(100, 102), type=x) for x in kwargs]
         for d in ds:
@@ -783,57 +765,57 @@ def test_decay_zrange_100_102_type_G(self):
 
     def test_decay_nuc_Ba133_erange_300_400(self):
         """Test fetch_decay_radiation: nuc='Ba-133', e_range=(300, 400)...."""
-        d = self.fetch(nuc='Ba-133', e_range=(300, 400))
+        d = self.fetch(nuc="Ba-133", e_range=(300, 400))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Energy (keV)'], 300, 400)
+        assert ufloats_overlap_range(d["Radiation Energy (keV)"], 300, 400)
 
     def test_decay_nuc_Ba133_erange_300_None(self):
         """Test fetch_decay_radiation: nuc='Ba-133', e_range=(300, None)..."""
-        d = self.fetch(nuc='Ba-133', e_range=(300, None))
+        d = self.fetch(nuc="Ba-133", e_range=(300, None))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Energy (keV)'], 300, 1e9)
+        assert ufloats_overlap_range(d["Radiation Energy (keV)"], 300, 1e9)
 
     def test_decay_nuc_Ba133_erange_None_400(self):
         """Test fetch_decay_radiation: nuc='Ba-133', e_range=(None, 400)..."""
-        d = self.fetch(nuc='Ba-133', e_range=(None, 400))
+        d = self.fetch(nuc="Ba-133", e_range=(None, 400))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Energy (keV)'], 0, 400)
+        assert ufloats_overlap_range(d["Radiation Energy (keV)"], 0, 400)
 
     def test_decay_nuc_Ba133_irange_10_100(self):
         """Test fetch_decay_radiation: nuc='Ba-133', i_range=(10, 100)....."""
-        d = self.fetch(nuc='Ba-133', i_range=(10, 100))
+        d = self.fetch(nuc="Ba-133", i_range=(10, 100))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Intensity (%)'], 10, 100)
+        assert ufloats_overlap_range(d["Radiation Intensity (%)"], 10, 100)
 
     def test_decay_nuc_Ba133_irange_40_None(self):
         """Test fetch_decay_radiation: nuc='Ba-133', i_range=(40, None)...."""
-        d = self.fetch(nuc='Ba-133', i_range=(40, None))
+        d = self.fetch(nuc="Ba-133", i_range=(40, None))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Intensity (%)'], 40, 100)
+        assert ufloats_overlap_range(d["Radiation Intensity (%)"], 40, 100)
 
     def test_decay_nuc_Ba133_irange_None_40(self):
         """Test fetch_decay_radiation: nuc='Ba-133', i_range=(None, 40)...."""
-        d = self.fetch(nuc='Ba-133', i_range=(None, 40))
+        d = self.fetch(nuc="Ba-133", i_range=(None, 40))
         assert len(d) > 0
         assert (d.Z == 56).all()
         assert (d.A == 133).all()
-        assert ufloats_overlap_range(d['Radiation Intensity (%)'], 0, 40)
+        assert ufloats_overlap_range(d["Radiation Intensity (%)"], 0, 40)
 
     def test_decay_erange_661_662(self):
         """Test fetch_decay_radiation: e_range=(661.5, 661.9).............."""
         d = self.fetch(e_range=(661.5, 661.9))
         assert len(d) > 0
-        assert ufloats_overlap_range(d['Radiation Energy (keV)'], 661.5, 661.9)
+        assert ufloats_overlap_range(d["Radiation Energy (keV)"], 661.5, 661.9)
 
     def test_decay_exception_too_many(self):
         """Test fetch_decay_radiation: exception if too many results......."""
diff --git a/tests/parsers_test.py b/tests/parsers_test.py
index d576b836..8614da67 100644
--- a/tests/parsers_test.py
+++ b/tests/parsers_test.py
@@ -1,6 +1,5 @@
 """Test becquerel spectrum file parsers."""
 
-from __future__ import print_function
 import glob
 import os
 import pytest
@@ -8,10 +7,10 @@
 import becquerel as bq
 
 
-SAMPLES_PATH = os.path.join(os.path.dirname(__file__), 'samples')
+SAMPLES_PATH = os.path.join(os.path.dirname(__file__), "samples")
 SAMPLES = {}
-for extension in ['.spe', '.spc', '.cnf']:
-    filenames = glob.glob(os.path.join(SAMPLES_PATH + '*', '*.*'))
+for extension in [".spe", ".spc", ".cnf"]:
+    filenames = glob.glob(os.path.join(SAMPLES_PATH + "*", "*.*"))
     filenames_filtered = []
     for filename in filenames:
         fname, ext = os.path.splitext(filename)
@@ -30,27 +29,27 @@ def run_parser(self, cls, extension, write=False):
         for filename in filenames:
             fname, ext = os.path.splitext(filename)
             path, fname = os.path.split(fname)
-            print('')
+            print("")
             print(filename)
             spec = cls(filename)
             print(spec)
             if write:
-                writename = os.path.join('.', fname + '_copy' + ext)
+                writename = os.path.join(".", fname + "_copy" + ext)
                 spec.write(writename)
                 os.remove(writename)
 
     def test_spe(self):
         """Test parsers.SpeFile............................................"""
         with pytest.warns(bq.parsers.SpectrumFileParsingWarning):
-            self.run_parser(bq.parsers.SpeFile, '.spe', write=True)
+            self.run_parser(bq.parsers.SpeFile, ".spe", write=True)
 
     def test_spc(self):
         """Test parsers.SpcFile............................................"""
-        self.run_parser(bq.parsers.SpcFile, '.spc', write=False)
+        self.run_parser(bq.parsers.SpcFile, ".spc", write=False)
 
     def test_cnf(self):
         """Test parsers.CnfFile............................................"""
-        self.run_parser(bq.parsers.CnfFile, '.cnf', write=False)
+        self.run_parser(bq.parsers.CnfFile, ".cnf", write=False)
 
 
 @pytest.mark.plottest
@@ -61,42 +60,43 @@ def run_parser(self, cls, extension, write=False):
         """Run the test for the given class and file extension."""
         try:
             plt.figure()
-        except:
-            # TclError on Travis bc no display. skip the test
+        except Exception:
+            # TclError on CI bc no display. skip the test
             return
-        plt.title('Testing ' + cls.__name__)
+        plt.title("Testing " + cls.__name__)
         filenames = SAMPLES.get(extension, [])
         assert len(filenames) >= 1
         for filename in filenames:
             fname, ext = os.path.splitext(filename)
             path, fname = os.path.split(fname)
-            print('')
+            print("")
             print(filename)
             spec = cls(filename)
             print(spec)
             plt.semilogy(
                 spec.energies,
                 spec.data / spec.energy_bin_widths / spec.livetime,
-                label=fname)
-            plt.xlabel('Energy (keV)')
-            plt.ylabel('Counts/keV/sec')
+                label=fname,
+            )
+            plt.xlabel("Energy (keV)")
+            plt.ylabel("Counts/keV/sec")
             plt.xlim(0, 2800)
             if write:
-                writename = os.path.join('.', fname + '_copy' + ext)
+                writename = os.path.join(".", fname + "_copy" + ext)
                 spec.write(writename)
                 os.remove(writename)
-        plt.legend(prop={'size': 8})
+        plt.legend(prop={"size": 8})
         plt.show()
 
     def test_spe(self):
         """Test parsers.SpeFile............................................"""
         with pytest.warns(bq.parsers.SpectrumFileParsingWarning):
-            self.run_parser(bq.parsers.SpeFile, '.spe', write=True)
+            self.run_parser(bq.parsers.SpeFile, ".spe", write=True)
 
     def test_spc(self):
         """Test parsers.SpcFile............................................"""
-        self.run_parser(bq.parsers.SpcFile, '.spc', write=False)
+        self.run_parser(bq.parsers.SpcFile, ".spc", write=False)
 
     def test_cnf(self):
         """Test parsers.CnfFile............................................"""
-        self.run_parser(bq.parsers.CnfFile, '.cnf', write=False)
+        self.run_parser(bq.parsers.CnfFile, ".cnf", write=False)
diff --git a/tests/plotting_test.py b/tests/plotting_test.py
index cad4e5f9..68129849 100644
--- a/tests/plotting_test.py
+++ b/tests/plotting_test.py
@@ -1,11 +1,11 @@
 """Test core.plotting"""
 
-from __future__ import print_function
 import pytest
 import numpy as np
 import matplotlib
 import matplotlib.pyplot as plt
-plt.switch_backend('Agg')
+
+plt.switch_backend("Agg")
 
 import becquerel as bq
 from becquerel import SpectrumPlotter as sp
@@ -57,8 +57,9 @@ def y_counts_spec(request):
     floatdata = np.random.poisson(lam=request.param, size=TEST_DATA_LENGTH)
     return bq.Spectrum(floatdata.astype(np.int))
 
-#@pytest.fixture
-#def neg_spec(spec_data):
+
+# @pytest.fixture
+# def neg_spec(spec_data):
 #    """Generate an uncalibrated spectrum."""
 #
 #    return bq.Spectrum(-spec_data)
@@ -68,6 +69,7 @@ def y_counts_spec(request):
 #                Very basic tests
 # ----------------------------------------------
 
+
 def test_plot_uncal_counts(uncal_spec):
     """Plot an uncalibrated spectrum"""
 
@@ -85,7 +87,7 @@ def test_plot_uncal_counts(cal_spec):
     assert plt.gca().get_xlabel() == "Energy [keV]"
     assert plt.gca().get_ylabel() == "Counts"
     assert plt.gca().get_title() == ""
-    #assert np.allclose(plt.gca().get_lines()[0].get_xdata(), TEST_EDGES_KEV)
+    # assert np.allclose(plt.gca().get_lines()[0].get_xdata(), TEST_EDGES_KEV)
     plt.close("all")
 
 
@@ -93,10 +95,11 @@ def test_plot_uncal_counts(cal_spec):
 #             Check each mode
 # ----------------------------------------------
 
+
 def test_channel_mode(uncal_spec):
     """Test xmode='channel'"""
 
-    uncal_spec.plot(xmode='channel')
+    uncal_spec.plot(xmode="channel")
     assert plt.gca().get_xlabel() == "Channel"
     plt.close("all")
 
@@ -104,7 +107,7 @@ def test_channel_mode(uncal_spec):
 def test_energy_mode(cal_spec):
     """Test xmode='energy'"""
 
-    cal_spec.plot(xmode='energy')
+    cal_spec.plot(xmode="energy")
     assert plt.gca().get_xlabel() == "Energy [keV]"
     plt.close("all")
 
@@ -112,7 +115,7 @@ def test_energy_mode(cal_spec):
 def test_counts_mode(cal_spec):
     """Test ymode='counts'"""
 
-    cal_spec.plot(ymode='counts')
+    cal_spec.plot(ymode="counts")
     assert plt.gca().get_ylabel() == "Counts"
     plt.close("all")
 
@@ -120,7 +123,7 @@ def test_counts_mode(cal_spec):
 def test_cps_mode(cal_spec_cps):
     """Test ymode='cps'"""
 
-    cal_spec_cps.plot(ymode='cps')
+    cal_spec_cps.plot(ymode="cps")
     assert plt.gca().get_ylabel() == "Countrate [1/s]"
     plt.close("all")
 
@@ -128,14 +131,16 @@ def test_cps_mode(cal_spec_cps):
 def test_cpskev_mode(cal_spec_cps):
     """Test ymode='cpskev'"""
 
-    cal_spec_cps.plot(ymode='cpskev')
+    cal_spec_cps.plot(ymode="cpskev")
     assert plt.gca().get_ylabel() == "Countrate [1/s/keV]"
     plt.close("all")
 
+
 # ----------------------------------------------
 #                Check axes labels
 # ----------------------------------------------
 
+
 def test_plot_uncal_counts_labels(uncal_spec):
     """Default xlabel, ylabel for uncal counts spec"""
 
@@ -166,7 +171,7 @@ def test_plot_countrate_label(uncal_spec_cps):
 def test_plot_countrate_density_label(cal_spec_cps):
     """Default ylabel for cal cpskev spec"""
 
-    cal_spec_cps.plot(ymode='cpskev')
+    cal_spec_cps.plot(ymode="cpskev")
     assert plt.gca().get_xlabel() == "Energy [keV]"
     assert plt.gca().get_ylabel() == "Countrate [1/s/keV]"
     plt.close("all")
@@ -187,6 +192,7 @@ def test_custom_labels(cal_spec):
 #                Check title
 # ----------------------------------------------
 
+
 def test_plot_default_title(uncal_spec):
     """Default title of a inprogram spec"""
 
@@ -209,7 +215,7 @@ def test_plot_default_custom_title(uncal_spec):
     """Default title of a file spec"""
 
     title = "My custom title"
-    uncal_spec.plot(title = title)
+    uncal_spec.plot(title=title)
     assert plt.gca().get_title() == title
     plt.close("all")
 
@@ -218,6 +224,7 @@ def test_plot_default_custom_title(uncal_spec):
 #                Check y scale
 # ----------------------------------------------
 
+
 def test_yscale(y_counts_spec):
     """Default yscale"""
 
@@ -228,7 +235,7 @@ def test_yscale(y_counts_spec):
 def test_yscale_linear(y_counts_spec):
     """Linear yscale"""
 
-    y_counts_spec.plot(yscale='linear')
+    y_counts_spec.plot(yscale="linear")
     assert plt.gca().get_yscale() == "linear"
     plt.close("all")
 
@@ -236,14 +243,14 @@ def test_yscale_linear(y_counts_spec):
 def test_yscale_log(y_counts_spec):
     """Log yscale"""
 
-    y_counts_spec.plot(yscale='log')
+    y_counts_spec.plot(yscale="log")
     assert plt.gca().get_yscale() == "log"
 
 
 def test_yscale_symlog(y_counts_spec):
     """Symlog yscale"""
 
-    y_counts_spec.plot(yscale='symlog')
+    y_counts_spec.plot(yscale="symlog")
     assert plt.gca().get_yscale() == "symlog"
     plt.close("all")
 
@@ -251,16 +258,15 @@ def test_yscale_symlog(y_counts_spec):
 def test_yscale_linear_scale(y_counts_spec):
     """Linear yscale with default scale"""
 
-    y_counts_spec.plot(yscale='linear', ylim="default")
+    y_counts_spec.plot(yscale="linear", ylim="default")
     assert plt.gca().get_yscale() == "linear"
     plt.close("all")
 
 
-
 def test_yscale_log_scale(y_counts_spec):
     """Log yscale with default scale"""
 
-    y_counts_spec.plot(yscale='log', ylim="default")
+    y_counts_spec.plot(yscale="log", ylim="default")
     assert plt.gca().get_yscale() == "log"
     plt.close("all")
 
@@ -268,7 +274,7 @@ def test_yscale_log_scale(y_counts_spec):
 def test_yscale_symlog_scale(y_counts_spec):
     """Symlog yscale with default scale"""
 
-    y_counts_spec.plot(yscale='symlog', ylim="default")
+    y_counts_spec.plot(yscale="symlog", ylim="default")
     assert plt.gca().get_yscale() == "symlog"
     plt.close("all")
 
@@ -277,6 +283,7 @@ def test_yscale_symlog_scale(y_counts_spec):
 #                Check x and y limits
 # ----------------------------------------------
 
+
 def test_xlim(uncal_spec):
     """Custom x limits"""
 
@@ -294,10 +301,12 @@ def test_ylim(uncal_spec):
     assert plt.gca().get_ylim() == ylim
     plt.close("all")
 
+
 # ----------------------------------------------
 #                Axes
 # ----------------------------------------------
 
+
 def test_axes(uncal_spec):
     """Custom y limits"""
 
@@ -311,26 +320,30 @@ def test_axes(uncal_spec):
 #                Kwargs
 # ----------------------------------------------
 
+
 def test_kwargs(uncal_spec):
     """Test kwargs, color and fmt in this case"""
 
-    uncal_spec.plot('--', color = '#ffffff')
-    assert plt.gca().get_lines()[0].get_linestyle() == '--'
-    assert plt.gca().get_lines()[0].get_color() == '#ffffff'
+    uncal_spec.plot("--", color="#ffffff")
+    assert plt.gca().get_lines()[0].get_linestyle() == "--"
+    assert plt.gca().get_lines()[0].get_color() == "#ffffff"
     plt.close("all")
 
+
 def test_kwargs_SpectrumPlotter(uncal_spec):
     """Test kwargs, color and fmt in this case"""
 
-    sp(uncal_spec).plot('--', color = '#ffffff')
-    assert plt.gca().get_lines()[0].get_linestyle() == '--'
-    assert plt.gca().get_lines()[0].get_color() == '#ffffff'
+    sp(uncal_spec).plot("--", color="#ffffff")
+    assert plt.gca().get_lines()[0].get_linestyle() == "--"
+    assert plt.gca().get_lines()[0].get_color() == "#ffffff"
     plt.close("all")
 
+
 # ----------------------------------------------
 #                multi line plot
 # ----------------------------------------------
 
+
 def test_multi(uncal_spec, cal_spec):
     """Test multiple plots"""
 
@@ -341,107 +354,114 @@ def test_multi(uncal_spec, cal_spec):
     assert len(plt.gca().get_lines()) == 2
     plt.close("all")
 
+
 # ----------------------------------------------
 #                check errors
 # ----------------------------------------------
 
+
 def test_error_positional_parameters(cal_spec):
     """Test errors when to many positional parameters are provided"""
 
     with pytest.raises(bq.PlottingError):
-        cal_spec.plot('x','x')
+        cal_spec.plot("x", "x")
     with pytest.raises(bq.PlottingError):
-        sp(cal_spec).plot('x','x')
+        sp(cal_spec).plot("x", "x")
 
 
 def test_uncal_as_cal(uncal_spec):
     """Test errors for calibrated reqested for an uncalibrated spectrum"""
-    
+
     with pytest.raises(bq.PlottingError):
-        uncal_spec.plot(xmode='energy')
+        uncal_spec.plot(xmode="energy")
 
 
 def test_unknown_xmode(uncal_spec):
     """Test errors for unknown xmode"""
 
     with pytest.raises(bq.PlottingError):
-        uncal_spec.plot(xmode='unknown')
+        uncal_spec.plot(xmode="unknown")
 
 
 def test_unknown_ymode(uncal_spec):
     """Test errors for unknown ymode"""
 
     with pytest.raises(bq.PlottingError):
-        uncal_spec.plot(ymode='unknown')
+        uncal_spec.plot(ymode="unknown")
 
 
 def test_cps_without_cps(uncal_spec):
     """Test errors for cps requested but not provided, causes a SpectrumError"""
 
     with pytest.raises(bq.SpectrumError):
-        uncal_spec.plot(ymode='cps')
+        uncal_spec.plot(ymode="cps")
 
 
 def test_cnts_without_cnts(uncal_spec_cps):
     """Test errors for cnts requested but not provided"""
 
     with pytest.raises(bq.PlottingError):
-        uncal_spec_cps.plot(ymode='cnts')
+        uncal_spec_cps.plot(ymode="cnts")
 
-#def test_call_ylim_default_without_any_input(neg_spec):
+
+# def test_call_ylim_default_without_any_input(neg_spec):
 #    """Test errors for cnts requested but not provided"""
 #
 #    with pytest.raises(bq.PlottingError):
 #        neg_spec.plot(yscale='log', ylim='default')
 
+
 def test_wrong_xlim(cal_spec):
     """Test errors wrong xlim structure"""
 
     with pytest.raises(bq.PlottingError):
         cal_spec.plot(xlim=0)
 
+
 def test_wrong_ylim(cal_spec):
     """Test errors for wrong ylim structure"""
 
     with pytest.raises(bq.PlottingError):
         cal_spec.plot(ylim=0)
 
+
 # ----------------------------------------------
 #                check getters
 # ----------------------------------------------
 
+
 def test_get_xmode(cal_spec_cps):
     """Test get xmode function"""
 
     tsp = sp(cal_spec_cps)
-    assert tsp.xmode == 'energy'
+    assert tsp.xmode == "energy"
 
 
 def test_get_ymode(cal_spec_cps):
     """Test get ymode function"""
 
     tsp = sp(cal_spec_cps)
-    assert tsp.ymode == 'cps'
+    assert tsp.ymode == "cps"
 
 
 def test_get_xlabel(cal_spec_cps):
     """Test get xlabel function"""
 
     tsp = sp(cal_spec_cps)
-    assert tsp.xlabel == 'Energy [keV]'
+    assert tsp.xlabel == "Energy [keV]"
 
 
 def test_get_ylabel(cal_spec_cps):
     """Test get ylabel function"""
 
     tsp = sp(cal_spec_cps)
-    assert tsp.ylabel == 'Countrate [1/s]'
+    assert tsp.ylabel == "Countrate [1/s]"
 
 
 def test_get_xlim(cal_spec_cps):
     """Test get xlim function"""
 
-    tsp = sp(cal_spec_cps, xlim='default')
+    tsp = sp(cal_spec_cps, xlim="default")
     assert tsp.xlim == (cal_spec_cps.bin_edges_kev[0], cal_spec_cps.bin_edges_kev[-1])
 
 
@@ -451,14 +471,16 @@ def test_get_linthreshy(cal_spec_cps):
     tsp = sp(cal_spec_cps, linthreshy=1)
     assert tsp.linthreshy == 1
 
+
 # ----------------------------------------------
 #                check error modes
 # ----------------------------------------------
 
+
 def test_error_modes_counts(uncal_spec):
     """Test error mode for counts mode"""
 
-    tsp = sp(uncal_spec, ymode='counts')
+    tsp = sp(uncal_spec, ymode="counts")
     assert np.allclose(tsp.yerror, uncal_spec.counts_uncs)
 
 
@@ -466,7 +488,7 @@ def test_error_modes_cps(cal_spec):
     """Test error mode for cps mode"""
 
     cal_spec.livetime = 200
-    tsp = sp(cal_spec, ymode='cps')
+    tsp = sp(cal_spec, ymode="cps")
     assert np.allclose(tsp.yerror, cal_spec.cps_uncs)
 
 
@@ -474,29 +496,31 @@ def test_error_modes_cpskev(cal_spec):
     """Test error mode for cps mode"""
 
     cal_spec.livetime = 200
-    tsp = sp(cal_spec, ymode='cpskev')
+    tsp = sp(cal_spec, ymode="cpskev")
     assert np.allclose(tsp.yerror, cal_spec.cpskev_uncs)
 
+
 # ----------------------------------------------
 #                check error plots
 # ----------------------------------------------
 
+
 def test_errornone(uncal_spec):
     """Test error mode none"""
 
-    ax = uncal_spec.plot(ymode='counts', emode='none')
+    ax = uncal_spec.plot(ymode="counts", emode="none")
 
-    colls=0
-    polys=0
-    lines=0
+    colls = 0
+    polys = 0
+    lines = 0
     for i in ax.get_children():
         if type(i) is matplotlib.collections.LineCollection:
-            colls=colls+1
+            colls = colls + 1
         if type(i) is matplotlib.collections.PolyCollection:
-            polys=polys+1
+            polys = polys + 1
 
         if type(i) is matplotlib.lines.Line2D:
-            lines=lines+1
+            lines = lines + 1
     assert colls == 0
     assert polys == 0
     assert lines == 1
@@ -506,31 +530,32 @@ def test_errornone(uncal_spec):
 def test_errorbars(uncal_spec):
     """Test error bar plot"""
 
-    ax = uncal_spec.plot(ymode='counts', emode='bars')
+    ax = uncal_spec.plot(ymode="counts", emode="bars")
 
-    colls=0
-    lines=0
+    colls = 0
+    lines = 0
     for i in ax.get_children():
         if type(i) is matplotlib.collections.LineCollection:
-            colls=colls+1
+            colls = colls + 1
         if type(i) is matplotlib.lines.Line2D:
-           lines=lines+1
+            lines = lines + 1
     assert colls == 1
     assert lines >= 1
     plt.close("all")
 
+
 def test_errorband(uncal_spec):
     """Test error band mode"""
 
-    ax = uncal_spec.plot(ymode='counts', emode='band')
+    ax = uncal_spec.plot(ymode="counts", emode="band")
 
-    colls=0
-    lines=0
+    colls = 0
+    lines = 0
     for i in ax.get_children():
         if type(i) is matplotlib.collections.PolyCollection:
-            colls=colls+1
+            colls = colls + 1
         if type(i) is matplotlib.lines.Line2D:
-            lines=lines+1
+            lines = lines + 1
     assert colls == 1
     assert lines == 1
     plt.close("all")
@@ -539,4 +564,4 @@ def test_errorband(uncal_spec):
 def test_unknown_emode(uncal_spec):
     """Test error mode unknown"""
     with pytest.raises(bq.SpectrumError):
-        ax = uncal_spec.plot(ymode='counts', emode='unknown')
+        ax = uncal_spec.plot(ymode="counts", emode="unknown")
diff --git a/tests/rebin_test.py b/tests/rebin_test.py
index bca10a68..a61d411b 100644
--- a/tests/rebin_test.py
+++ b/tests/rebin_test.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
 import pytest
 import numpy as np
 import matplotlib.pyplot as plt
@@ -10,32 +9,43 @@
 # ----------------------------------------------
 
 
-@pytest.fixture(params=[np.linspace(0, 30, 31),
-                        np.linspace(-1, 30, 31),
-                        np.linspace(0, 30, 11),
-                        np.linspace(0, 30, 40)],
-                ids=["1 keV bins",
-                     "1 keV bins with negative starting edge",
-                     "3 keV bins",
-                     ".75 keV bins"])
+@pytest.fixture(
+    params=[
+        np.linspace(0, 30, 31),
+        np.linspace(-1, 30, 31),
+        np.linspace(0, 30, 11),
+        np.linspace(0, 30, 40),
+    ],
+    ids=[
+        "1 keV bins",
+        "1 keV bins with negative starting edge",
+        "3 keV bins",
+        ".75 keV bins",
+    ],
+)
 def old_edges(request):
     return request.param.astype(np.float)
 
 
-@pytest.fixture(params=[np.linspace(0, 30, 31),
-                        np.linspace(-1, 30, 31),
-                        np.linspace(0, 30, 7),
-                        np.linspace(0, 30, 45)],
-                ids=["1 keV bins",
-                     "1 keV bins with negative starting edge",
-                     "5 keV bins",
-                     ".66 keV bins"])
+@pytest.fixture(
+    params=[
+        np.linspace(0, 30, 31),
+        np.linspace(-1, 30, 31),
+        np.linspace(0, 30, 7),
+        np.linspace(0, 30, 45),
+    ],
+    ids=[
+        "1 keV bins",
+        "1 keV bins with negative starting edge",
+        "5 keV bins",
+        ".66 keV bins",
+    ],
+)
 def new_edges(request):
     return request.param.astype(np.float)
 
 
-@pytest.fixture(params=[1, 5, 25],
-                ids=["sparse counts", "~5 counts", "~25 counts"])
+@pytest.fixture(params=[1, 5, 25], ids=["sparse counts", "~5 counts", "~25 counts"])
 def lam(request):
     """
     NB listmode is too slow, so the original test of 10k+ counts is too big
@@ -67,25 +77,28 @@ def test_total_counts(self, lam, old_edges, new_edges):
 
         interpolation and listmode are separate as the latter requires ints
         """
-        self._test_total_counts(lam, old_edges, new_edges,
-                                "interpolation", float)
-        self._test_total_counts(lam, old_edges, new_edges,
-                                "listmode", int)
+        self._test_total_counts(lam, old_edges, new_edges, "interpolation", float)
+        self._test_total_counts(lam, old_edges, new_edges, "listmode", int)
 
     def test_total_counts_listmode_float(self, lam, old_edges, new_edges):
         """Check that listmode rebinning raises a warning if counts are floats
         and have non-zero decimal residuals"""
-        old_counts = make_fake_spec_array(lam, len(old_edges) - 1,
-                                          dtype=float)
+        old_counts = make_fake_spec_array(lam, len(old_edges) - 1, dtype=float)
         old_counts += 0.1
         with pytest.warns(bq.RebinWarning):
-            bq.core.rebin.rebin(old_counts, old_edges, new_edges,
-                                method="listmode", zero_pad_warnings=False)
+            bq.core.rebin.rebin(
+                old_counts,
+                old_edges,
+                new_edges,
+                method="listmode",
+                zero_pad_warnings=False,
+            )
 
     def _test_total_counts(self, lam, old_edges, new_edges, method, dtype):
         """Check total counts in spectrum data before and after rebin"""
         old_counts, new_counts = self._gen_old_new_counts(
-            lam, old_edges, new_edges, method, dtype)
+            lam, old_edges, new_edges, method, dtype
+        )
         assert np.isclose(old_counts.sum(), new_counts.sum())
 
     def test_rebin2d_counts(self, lam, old_edges, new_edges):
@@ -93,25 +106,25 @@ def test_rebin2d_counts(self, lam, old_edges, new_edges):
 
         interpolation and listmode are separate as the latter requires ints
         """
-        self._test_total_counts_2d(lam, old_edges, new_edges,
-                                   "interpolation", "float")
-        self._test_total_counts_2d(lam, old_edges, new_edges,
-                                   "listmode", "int")
+        self._test_total_counts_2d(lam, old_edges, new_edges, "interpolation", "float")
+        self._test_total_counts_2d(lam, old_edges, new_edges, "listmode", "int")
 
     def _test_total_counts_2d(self, lam, old_edges, new_edges, method, dtype):
         """Check total counts in spectra data before and after rebin"""
 
         nspectra = 20
         old_counts_2d = make_fake_spec_array(
-            lam=lam, size=(nspectra, len(old_edges) - 1), dtype=dtype)
+            lam=lam, size=(nspectra, len(old_edges) - 1), dtype=dtype
+        )
         old_edges_2d = np.repeat(old_edges[np.newaxis, :], nspectra, axis=0)
-        new_counts_2d = bq.core.rebin.rebin(old_counts_2d,
-                                            old_edges_2d,
-                                            new_edges,
-                                            method=method,
-                                            zero_pad_warnings=False)
-        assert np.allclose(old_counts_2d.sum(axis=1),
-                           new_counts_2d.sum(axis=1))
+        new_counts_2d = bq.core.rebin.rebin(
+            old_counts_2d,
+            old_edges_2d,
+            new_edges,
+            method=method,
+            zero_pad_warnings=False,
+        )
+        assert np.allclose(old_counts_2d.sum(axis=1), new_counts_2d.sum(axis=1))
 
     def test_unchanged_binning(self, lam, old_edges):
         """Check counts in spectrum is the same after a identity/null rebin
@@ -124,47 +137,46 @@ def test_unchanged_binning(self, lam, old_edges):
 
     def _test_unchanged_binning(self, lam, edges, method, dtype):
         old_counts, new_counts = self._gen_old_new_counts(
-            lam, edges, edges, method, dtype)
+            lam, edges, edges, method, dtype
+        )
         assert np.allclose(old_counts, new_counts)
 
     def _gen_old_new_counts(self, lam, old_edges, new_edges, method, dtype):
         """ generate old and new counts (1D)"""
         old_counts = make_fake_spec_array(lam, len(old_edges) - 1, dtype=dtype)
-        new_counts = bq.core.rebin.rebin(old_counts, old_edges, new_edges,
-                                         method=method,
-                                         zero_pad_warnings=False)
+        new_counts = bq.core.rebin.rebin(
+            old_counts, old_edges, new_edges, method=method, zero_pad_warnings=False
+        )
         return (old_counts, new_counts)
 
     def test_subset_bin_edges(self, lam, old_edges):
-        old_counts = make_fake_spec_array(lam, len(old_edges) - 1,
-                                          dtype=float)
-        new_counts = bq.core.rebin.rebin(old_counts, old_edges,
-                                         old_edges[1:-1],
-                                         method="interpolation")
+        old_counts = make_fake_spec_array(lam, len(old_edges) - 1, dtype=float)
+        new_counts = bq.core.rebin.rebin(
+            old_counts, old_edges, old_edges[1:-1], method="interpolation"
+        )
         assert np.isclose(np.sum(old_counts), np.sum(new_counts))
         assert np.allclose(old_counts[2:-2], new_counts[1:-1])
 
     def test_overlap_warnings(self, old_spec_float):
         old_edges, old_counts = old_spec_float
-        new_edges_left = np.linspace(old_edges[0] - 1., old_edges[-1], 10)
+        new_edges_left = np.linspace(old_edges[0] - 1.0, old_edges[-1], 10)
         new_edges_right = np.linspace(old_edges[0], old_edges[-1] + 1, 10)
-        new_edges_both = np.linspace(old_edges[0] - 1., old_edges[-1] + 1, 10)
+        new_edges_both = np.linspace(old_edges[0] - 1.0, old_edges[-1] + 1, 10)
         with pytest.warns(bq.RebinWarning):
-            bq.rebin(old_counts, old_edges, new_edges_left,
-                     zero_pad_warnings=True)
+            bq.rebin(old_counts, old_edges, new_edges_left, zero_pad_warnings=True)
         with pytest.warns(bq.RebinWarning):
-            bq.rebin(old_counts, old_edges, new_edges_right,
-                     zero_pad_warnings=True)
+            bq.rebin(old_counts, old_edges, new_edges_right, zero_pad_warnings=True)
         with pytest.warns(bq.RebinWarning):
-            bq.rebin(old_counts, old_edges, new_edges_both,
-                     zero_pad_warnings=True)
+            bq.rebin(old_counts, old_edges, new_edges_both, zero_pad_warnings=True)
 
     def test_overlap_errors(self, old_spec_float):
         old_edges, old_counts = old_spec_float
-        new_edges_left = np.linspace(old_edges[0] - 10, old_edges[0] - 1, 10,
-                                     dtype=float)
-        new_edges_right = np.linspace(old_edges[-1] + 1, old_edges[-1] + 10,
-                                      10, dtype=float)
+        new_edges_left = np.linspace(
+            old_edges[0] - 10, old_edges[0] - 1, 10, dtype=float
+        )
+        new_edges_right = np.linspace(
+            old_edges[-1] + 1, old_edges[-1] + 10, 10, dtype=float
+        )
         with pytest.raises(bq.RebinError):
             bq.rebin(old_counts, old_edges, new_edges_left)
         with pytest.raises(bq.RebinError):
@@ -174,8 +186,13 @@ def test_negative_input_listmode(self, old_spec_int, new_edges):
         old_edges, old_counts = old_spec_int
         old_counts[0] = -1
         with pytest.raises(bq.RebinError):
-            bq.rebin(old_counts, old_edges, new_edges,
-                     method='listmode', zero_pad_warnings=False)
+            bq.rebin(
+                old_counts,
+                old_edges,
+                new_edges,
+                method="listmode",
+                zero_pad_warnings=False,
+            )
 
     @pytest.mark.plottest
     def test_uncal_spectrum_counts(self, old_spec_float):
@@ -183,15 +200,20 @@ def test_uncal_spectrum_counts(self, old_spec_float):
 
         old_edges, old_counts = old_spec_float
         new_edges = old_edges + 0.3
-        new_counts = bq.rebin(old_counts, old_edges, new_edges,
-                              zero_pad_warnings=False)
+        new_counts = bq.rebin(old_counts, old_edges, new_edges, zero_pad_warnings=False)
         plt.figure()
         plt.plot(
             *bq.core.plotting.SpectrumPlotter.bin_edges_and_heights_to_steps(
-                old_edges, old_counts),
-            color='dodgerblue', label='original')
+                old_edges, old_counts
+            ),
+            color="dodgerblue",
+            label="original",
+        )
         plt.plot(
             *bq.core.plotting.SpectrumPlotter.bin_edges_and_heights_to_steps(
-                new_edges, new_counts),
-            color='firebrick', label='rebinned')
+                new_edges, new_counts
+            ),
+            color="firebrick",
+            label="rebinned",
+        )
         plt.show()
diff --git a/tests/spectrum_test.py b/tests/spectrum_test.py
index 46adccce..0fb508d4 100644
--- a/tests/spectrum_test.py
+++ b/tests/spectrum_test.py
@@ -1,6 +1,5 @@
 """Test becquerel's Spectrum."""
 
-from __future__ import print_function
 import pytest
 import datetime
 import numpy as np
@@ -38,24 +37,27 @@ def test_add(spec1, spec2):
 
     """
 
-    if t == 'uncal':
+    if t == "uncal":
         return bq.Spectrum(make_data(lam=lam), livetime=lt)
-    elif t == 'cal':
+    elif t == "cal":
         return bq.Spectrum(
-            make_data(lam=lam), bin_edges_kev=TEST_EDGES_KEV, livetime=lt)
-    elif t == 'cal_new':
+            make_data(lam=lam), bin_edges_kev=TEST_EDGES_KEV, livetime=lt
+        )
+    elif t == "cal_new":
         return bq.Spectrum(
-            make_data(lam=lam), livetime=lt,
-            bin_edges_kev=np.arange(TEST_DATA_LENGTH + 1) * 0.67)
-    elif t == 'cal_cps':
+            make_data(lam=lam),
+            livetime=lt,
+            bin_edges_kev=np.arange(TEST_DATA_LENGTH + 1) * 0.67,
+        )
+    elif t == "cal_cps":
         return bq.Spectrum(
-            cps=make_data(lam=lam), bin_edges_kev=TEST_EDGES_KEV, livetime=lt)
-    elif t == 'uncal_long':
-        return bq.Spectrum(
-            make_data(lam=lam, size=TEST_DATA_LENGTH * 2), livetime=lt)
-    elif t == 'uncal_cps':
+            cps=make_data(lam=lam), bin_edges_kev=TEST_EDGES_KEV, livetime=lt
+        )
+    elif t == "uncal_long":
+        return bq.Spectrum(make_data(lam=lam, size=TEST_DATA_LENGTH * 2), livetime=lt)
+    elif t == "uncal_cps":
         return bq.Spectrum(cps=make_data(lam=lam), livetime=lt)
-    elif t == 'data':
+    elif t == "data":
         return make_data()
     else:
         return t
@@ -72,42 +74,42 @@ def spec_data():
 def uncal_spec(spec_data):
     """Generate an uncalibrated spectrum."""
 
-    return make_spec('uncal')
+    return make_spec("uncal")
 
 
 @pytest.fixture
 def uncal_spec_2(spec_data):
     """Generate an uncalibrated spectrum (2nd instance)."""
 
-    return make_spec('uncal')
+    return make_spec("uncal")
 
 
 @pytest.fixture
 def uncal_spec_cps(spec_data):
     """Generate an uncalibrated spectrum with cps data."""
 
-    return make_spec('uncal_cps')
+    return make_spec("uncal_cps")
 
 
 @pytest.fixture
 def uncal_spec_long(spec_data):
     """Generate an uncalibrated spectrum, of longer length."""
 
-    return make_spec('uncal_long')
+    return make_spec("uncal_long")
 
 
 @pytest.fixture
 def cal_spec(spec_data):
     """Generate a calibrated spectrum."""
 
-    return make_spec('cal')
+    return make_spec("cal")
 
 
 @pytest.fixture
 def cal_spec_2(spec_data):
     """Generate a calibrated spectrum (2nd instance)."""
 
-    return make_spec('cal')
+    return make_spec("cal")
 
 
 # -----------------------------------------------------------------------------
@@ -136,29 +138,30 @@ def run_from_file(self, extension):
     def test_spe(self):
         """Test Spectrum.from_file for SPE file........................."""
         with pytest.warns(bq.parsers.SpectrumFileParsingWarning):
-            self.run_from_file('.spe')
+            self.run_from_file(".spe")
 
     def test_spc(self):
         """Test Spectrum.from_file for SPC file........................."""
 
-        self.run_from_file('.spc')
+        self.run_from_file(".spc")
 
     def test_cnf(self):
         """Test Spectrum.from_file for CNF file........................."""
 
-        self.run_from_file('.cnf')
+        self.run_from_file(".cnf")
 
     def test_error(self):
         """Test _get_file_object() raises error for bad file type"""
 
         with pytest.raises(NotImplementedError):
-            bq.Spectrum.from_file('foo.bar')
+            bq.Spectrum.from_file("foo.bar")
 
 
 # ----------------------------------------------
 #         Test Spectrum.__init__()
 # ----------------------------------------------
 
+
 def test_uncal(uncal_spec):
     """Test simple uncalibrated construction."""
 
@@ -215,7 +218,7 @@ def test_uncalibrated_exception(uncal_spec):
 
 def test_negative_input(spec_data):
     """Make sure negative values in counts throw an exception,
-       and exception is not raised if uncs are provided."""
+    and exception is not raised if uncs are provided."""
 
     neg_spec = spec_data[:]
     neg_spec[::2] *= -1
@@ -236,33 +239,33 @@ def test_negative_input(spec_data):
 
 NBINS = 100
 NEDGES = NBINS + 1
-MEAN = 1000.
-STDDEV = 50.
+MEAN = 1000.0
+STDDEV = 50.0
 NSAMPLES = 10000
-XMIN, XMAX = 0., 2000.
+XMIN, XMAX = 0.0, 2000.0
 BW = (XMAX - XMIN) / (1.0 * NBINS)
 lmd = np.random.normal(MEAN, STDDEV, NSAMPLES)
 log_bins = np.logspace(1, 4, num=NEDGES, base=10.0)
 
 
 def make_spec_listmode(t, use_cal=False):
-    if t == 'uniform':
+    if t == "uniform":
         spec = bq.Spectrum.from_listmode(lmd, bins=NBINS, xmin=XMIN, xmax=XMAX)
-    elif t == 'log':
+    elif t == "log":
         spec = bq.Spectrum.from_listmode(lmd, bins=log_bins)
-    elif t == 'default':
+    elif t == "default":
         spec = bq.Spectrum.from_listmode(lmd)
     else:
         return t
 
     if use_cal:
-        cal = bq.LinearEnergyCal.from_coeffs({'m': TEST_GAIN, 'b': 0.0})
+        cal = bq.LinearEnergyCal.from_coeffs({"m": TEST_GAIN, "b": 0.0})
         spec.apply_calibration(cal)
         assert spec.energy_cal is not None
     return spec
 
 
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_listmode_uniform(use_cal):
     """Test listmode spectra with uniform binning.
 
@@ -270,7 +273,7 @@ def test_listmode_uniform(use_cal):
     so run quite a few sanity checks here and in the following tests.
     """
 
-    spec = make_spec_listmode('uniform', use_cal)
+    spec = make_spec_listmode("uniform", use_cal)
 
     xmin, xmax, bw = XMIN, XMAX, BW
     if spec.is_calibrated:
@@ -288,23 +291,23 @@ def test_listmode_uniform(use_cal):
     assert spec.has_uniform_bins()
 
 
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_listmode_non_uniform(use_cal):
     """Test listmode spectra with non-uniform bins."""
-    spec = make_spec_listmode('log', use_cal)
+    spec = make_spec_listmode("log", use_cal)
     assert len(spec) == NBINS
     assert spec.has_uniform_bins() is False
 
 
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_listmode_no_args(use_cal):
     """Test listmode spectra without args."""
-    spec = make_spec_listmode('default', use_cal)
+    spec = make_spec_listmode("default", use_cal)
     assert len(spec) == int(np.ceil(max(lmd)))
 
 
-@pytest.mark.parametrize('spec_str', ['uniform', 'log'])
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("spec_str", ["uniform", "log"])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_find_bin_index(spec_str, use_cal):
     """Test that find_bin_index works for various spectrum objects."""
 
@@ -314,13 +317,13 @@ def test_find_bin_index(spec_str, use_cal):
     xmin, xmax = edges[0], edges[-1]
 
     assert spec.find_bin_index(xmin) == 0
-    assert spec.find_bin_index(xmin + widths[0]/4.0) == 0
-    assert spec.find_bin_index(xmax - widths[-1]/4.0) == len(spec) - 1
+    assert spec.find_bin_index(xmin + widths[0] / 4.0) == 0
+    assert spec.find_bin_index(xmax - widths[-1] / 4.0) == len(spec) - 1
     assert np.all(spec.find_bin_index(edges[:-1]) == np.arange(len(spec)))
 
 
-@pytest.mark.parametrize('spec_str', ['uniform', 'default', 'log'])
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("spec_str", ["uniform", "default", "log"])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_index_out_of_bounds(spec_str, use_cal):
     """Raise a SpectrumError when we look for a bin index out of bounds, or an
     UncalibratedError when we ask to search bin_edges_kev in an uncal spectrum.
@@ -334,7 +337,7 @@ def test_index_out_of_bounds(spec_str, use_cal):
     with pytest.raises(bq.SpectrumError):
         spec.find_bin_index(xmax)
     with pytest.raises(bq.SpectrumError):
-        spec.find_bin_index(xmin - widths[0]/4.0)
+        spec.find_bin_index(xmin - widths[0] / 4.0)
 
     # UncalibratedError if not calibrated and we ask for calibrated
     if not spec.is_calibrated:
@@ -342,10 +345,10 @@ def test_index_out_of_bounds(spec_str, use_cal):
             spec.find_bin_index(xmin, use_kev=True)
 
 
-@pytest.mark.parametrize('use_cal', [None, False, True])
+@pytest.mark.parametrize("use_cal", [None, False, True])
 def test_bin_index_types(use_cal):
     """Additional bin index type checking."""
-    spec = make_spec_listmode('uniform', use_cal=use_cal)
+    spec = make_spec_listmode("uniform", use_cal=use_cal)
     assert isinstance(spec.find_bin_index(XMIN), (int, np.integer))
     assert isinstance(spec.find_bin_index([XMIN]), np.ndarray)
 
@@ -354,6 +357,7 @@ def test_bin_index_types(use_cal):
 #      Test Spectrum repr behavior
 # ----------------------------------------------
 
+
 def test_repr(cal_spec):
     repr(cal_spec)
 
@@ -366,6 +370,7 @@ def test_str(cal_spec):
 #      Test Spectrum livetime properties
 # ----------------------------------------------
 
+
 @pytest.fixture(params=[86400, 300.6, 0.88])
 def livetime(request):
     return request.param
@@ -400,13 +405,19 @@ def test_no_livetime(spec_data):
 #     Test start_time, stop_time, realtime
 # ----------------------------------------------
 
-@pytest.mark.parametrize('start, stop', [
-    (datetime.datetime(2017, 1, 1, 17, 0, 3),
-     datetime.datetime(2017, 1, 1, 18, 0, 3)),
-    ('2017-01-19 17:21:00', '2017-01-20 14:19:32'),
-    (datetime.datetime(2017, 1, 1, 0, 30, 0, 385), '2017-01-01 12:44:22')
-])
-@pytest.mark.parametrize('rt', [3600, 2345.6])
+
+@pytest.mark.parametrize(
+    "start, stop",
+    [
+        (
+            datetime.datetime(2017, 1, 1, 17, 0, 3),
+            datetime.datetime(2017, 1, 1, 18, 0, 3),
+        ),
+        ("2017-01-19 17:21:00", "2017-01-20 14:19:32"),
+        (datetime.datetime(2017, 1, 1, 0, 30, 0, 385), "2017-01-01 12:44:22"),
+    ],
+)
+@pytest.mark.parametrize("rt", [3600, 2345.6])
 def test_acqtime_construction(spec_data, start, stop, rt):
     """Test construction with 2 out of 3 of start, stop, and realtime."""
 
@@ -415,10 +426,13 @@ def test_acqtime_construction(spec_data, start, stop, rt):
     bq.Spectrum(spec_data, realtime=rt, stop_time=stop)
 
 
-@pytest.mark.parametrize('start, stop, rt, expected_err', [
-    ('2017-01-19 17:21:00', '2017-01-20 17:21:00', 86400, bq.SpectrumError),
-    ('2017-01-19 17:21:00', '2017-01-18 17:21:00', None, ValueError),
-])
+@pytest.mark.parametrize(
+    "start, stop, rt, expected_err",
+    [
+        ("2017-01-19 17:21:00", "2017-01-20 17:21:00", 86400, bq.SpectrumError),
+        ("2017-01-19 17:21:00", "2017-01-18 17:21:00", None, ValueError),
+    ],
+)
 def test_bad_acqtime_construction(spec_data, start, stop, rt, expected_err):
     """Test bad construction of a spectrum with start, stop, or realtimes."""
 
@@ -437,6 +451,7 @@ def test_bad_realtime_livetime(spec_data):
 #         Test uncertainties in Spectrum
 # ----------------------------------------------
 
+
 def test_construct_float_int(spec_data):
     """Construct spectrum with non-UFloats (float and int)."""
 
@@ -503,6 +518,7 @@ def test_properties(spec_data):
 #         Test Spectrum.bin_widths
 # ----------------------------------------------
 
+
 def test_bin_widths_kev(cal_spec):
     """Test Spectrum.bin_widths_kev"""
 
@@ -522,11 +538,14 @@ def test_bin_widths_uncal(uncal_spec):
 #         Test Spectrum CPS and CPS/keV
 # ----------------------------------------------
 
-@pytest.mark.parametrize('construction_kwargs', [
-    {'livetime': 300.0},
-    {'livetime': 300.0,
-     'bin_edges_kev': TEST_EDGES_KEV},
-])
+
+@pytest.mark.parametrize(
+    "construction_kwargs",
+    [
+        {"livetime": 300.0},
+        {"livetime": 300.0, "bin_edges_kev": TEST_EDGES_KEV},
+    ],
+)
 def test_cps(spec_data, construction_kwargs):
     """Test cps property and uncertainties on uncal and cal spectrum."""
 
@@ -542,16 +561,16 @@ def test_cps(spec_data, construction_kwargs):
 def test_cpskev(spec_data, livetime):
     """Test cpskev property and uncertainties"""
 
-    spec = bq.Spectrum(spec_data, livetime=livetime,
-                       bin_edges_kev=TEST_EDGES_KEV)
+    spec = bq.Spectrum(spec_data, livetime=livetime, bin_edges_kev=TEST_EDGES_KEV)
     spec.cpskev
     spec.cpskev_vals
     spec.cpskev_uncs
     assert np.allclose(
-        spec.cpskev_vals, spec_data / spec.bin_widths_kev / float(livetime))
+        spec.cpskev_vals, spec_data / spec.bin_widths_kev / float(livetime)
+    )
     assert np.allclose(
-        spec.cpskev_uncs,
-        spec.counts_uncs / spec.bin_widths_kev / float(livetime))
+        spec.cpskev_uncs, spec.counts_uncs / spec.bin_widths_kev / float(livetime)
+    )
 
 
 def test_cps_cpsspec(spec_data, livetime):
@@ -588,20 +607,15 @@ def test_cpskev_errors(spec_data):
 #   Test addition and subtraction of spectra
 # ----------------------------------------------
 
-@pytest.mark.parametrize('lt1, lt2', [
-    (300, 600),
-    (12.6, 0.88),
-    (300, 12.6),
-    (12.6, None),
-    (None, None)])
-@pytest.mark.parametrize('type1, type2', [
-    ('uncal', 'uncal'),
-    ('cal', 'cal')])
+
+@pytest.mark.parametrize(
+    "lt1, lt2", [(300, 600), (12.6, 0.88), (300, 12.6), (12.6, None), (None, None)]
+)
+@pytest.mark.parametrize("type1, type2", [("uncal", "uncal"), ("cal", "cal")])
 def test_add(type1, type2, lt1, lt2):
     """Test addition of spectra"""
 
-    spec1, spec2 = (make_spec(type1, lt=lt1),
-                    make_spec(type2, lt=lt2))
+    spec1, spec2 = (make_spec(type1, lt=lt1), make_spec(type2, lt=lt2))
 
     if lt1 and lt2:
         tot = spec1 + spec2
@@ -611,22 +625,24 @@ def test_add(type1, type2, lt1, lt2):
             tot = spec1 + spec2
         assert tot.livetime is None
     assert np.all(tot.counts == spec1.counts + spec2.counts)
-    assert np.all(tot.counts_vals ==
-                  spec1.counts_vals + spec2.counts_vals)
-
-
-@pytest.mark.parametrize('type1, type2, expected_error', [
-    ('uncal', 'cal', bq.SpectrumError),
-    ('uncal', 'uncal_long', bq.SpectrumError),
-    ('uncal', 'data', TypeError),
-    ('data', 'uncal', TypeError),
-    ('uncal', 5, TypeError),
-    (5, 'cal', TypeError),
-    ('cal', 'asdf', TypeError),
-    ('asdf', 'uncal', TypeError),
-    ('uncal', 'data', TypeError),
-    ('cal', 'cal_new', NotImplementedError)
-])
+    assert np.all(tot.counts_vals == spec1.counts_vals + spec2.counts_vals)
+
+
+@pytest.mark.parametrize(
+    "type1, type2, expected_error",
+    [
+        ("uncal", "cal", bq.SpectrumError),
+        ("uncal", "uncal_long", bq.SpectrumError),
+        ("uncal", "data", TypeError),
+        ("data", "uncal", TypeError),
+        ("uncal", 5, TypeError),
+        (5, "cal", TypeError),
+        ("cal", "asdf", TypeError),
+        ("asdf", "uncal", TypeError),
+        ("uncal", "data", TypeError),
+        ("cal", "cal_new", NotImplementedError),
+    ],
+)
 def test_add_sub_errors(type1, type2, expected_error):
     """Test addition and subtraction that causes errors"""
 
@@ -637,9 +653,7 @@ def test_add_sub_errors(type1, type2, expected_error):
         spec1 - spec2
 
 
-@pytest.mark.parametrize('type1, type2', [
-    ('uncal', 'uncal'),
-    ('cal', 'cal')])
+@pytest.mark.parametrize("type1, type2", [("uncal", "uncal"), ("cal", "cal")])
 def test_add_uncs(type1, type2):
     """Test uncertainties on addition of uncal spectra"""
 
@@ -648,19 +662,22 @@ def test_add_uncs(type1, type2):
     with pytest.warns(bq.SpectrumWarning):
         tot = spec1 + spec2
 
-    uncs = np.sqrt(spec1.counts_uncs**2 + spec2.counts_uncs**2)
+    uncs = np.sqrt(spec1.counts_uncs ** 2 + spec2.counts_uncs ** 2)
     assert np.allclose(tot.counts_uncs, uncs)
 
 
-@pytest.mark.parametrize('type1, type2, lt1, lt2', [
-    ('uncal_cps', 'uncal_cps', 300, 12.6),
-    ('uncal_cps', 'uncal_cps', None, 12.6),
-    ('uncal_cps', 'uncal_cps', None, None)])
+@pytest.mark.parametrize(
+    "type1, type2, lt1, lt2",
+    [
+        ("uncal_cps", "uncal_cps", 300, 12.6),
+        ("uncal_cps", "uncal_cps", None, 12.6),
+        ("uncal_cps", "uncal_cps", None, None),
+    ],
+)
 def test_add_sub_cps(type1, type2, lt1, lt2):
     """Test addition and subtraction of CPS spectra"""
 
-    spec1, spec2 = (make_spec(type1, lt=lt1),
-                    make_spec(type2, lt=lt2))
+    spec1, spec2 = (make_spec(type1, lt=lt1), make_spec(type2, lt=lt2))
 
     tot = spec1 + spec2
     assert np.all(tot.cps_vals == spec1.cps_vals + spec2.cps_vals)
@@ -671,35 +688,32 @@ def test_add_sub_cps(type1, type2, lt1, lt2):
     assert np.all(diff.cps_vals == spec1.cps_vals - spec2.cps_vals)
 
 
-@pytest.mark.parametrize('type1, type2, lt1, lt2', [
-    ('uncal', 'uncal_cps', None, None),
-    ('uncal_cps', 'uncal', None, None),
-    ('uncal', 'uncal_cps', 300, None),
-    ('uncal_cps', 'uncal', None, 300),
-    ('uncal', 'uncal_cps', 300, 600),
-    ('uncal_cps', 'uncal', 600, 300)])
+@pytest.mark.parametrize(
+    "type1, type2, lt1, lt2",
+    [
+        ("uncal", "uncal_cps", None, None),
+        ("uncal_cps", "uncal", None, None),
+        ("uncal", "uncal_cps", 300, None),
+        ("uncal_cps", "uncal", None, 300),
+        ("uncal", "uncal_cps", 300, 600),
+        ("uncal_cps", "uncal", 600, 300),
+    ],
+)
 def test_adddition_errors(type1, type2, lt1, lt2):
     """Test errors during addition of mixed spectra"""
 
-    spec1, spec2 = (make_spec(type1, lt=lt1),
-                    make_spec(type2, lt=lt2))
+    spec1, spec2 = (make_spec(type1, lt=lt1), make_spec(type2, lt=lt2))
 
     with pytest.raises(bq.SpectrumError):
         spec1 + spec2
 
 
-@pytest.mark.parametrize('lt1, lt2', [
-    (300, 600),
-    (12.6, 0.88),
-    (300, 12.6)])
-@pytest.mark.parametrize('type1, type2', [
-    ('uncal', 'uncal'),
-    ('cal', 'cal')])
+@pytest.mark.parametrize("lt1, lt2", [(300, 600), (12.6, 0.88), (300, 12.6)])
+@pytest.mark.parametrize("type1, type2", [("uncal", "uncal"), ("cal", "cal")])
 def test_subtract_counts(type1, type2, lt1, lt2):
     """Test Spectrum subtraction with counts"""
 
-    spec1, spec2 = (make_spec(type1, lt=lt1),
-                    make_spec(type2, lt=lt2))
+    spec1, spec2 = (make_spec(type1, lt=lt1), make_spec(type2, lt=lt2))
     with pytest.warns(bq.SpectrumWarning):
         diff = spec1 - spec2
     assert diff.livetime is None
@@ -708,18 +722,21 @@ def test_subtract_counts(type1, type2, lt1, lt2):
     assert np.all(diff.cps_uncs > spec2.cps_uncs)
 
 
-@pytest.mark.parametrize('type1, type2, lt1, lt2', [
-    ('uncal', 'uncal_cps', None, None),
-    ('uncal_cps', 'uncal', None, None),
-    ('uncal', 'uncal_cps', None, 300),
-    ('uncal_cps', 'uncal', 300, None),
-    ('uncal', 'uncal_cps', 300, None),
-    ('uncal_cps', 'uncal', None, 300)])
+@pytest.mark.parametrize(
+    "type1, type2, lt1, lt2",
+    [
+        ("uncal", "uncal_cps", None, None),
+        ("uncal_cps", "uncal", None, None),
+        ("uncal", "uncal_cps", None, 300),
+        ("uncal_cps", "uncal", 300, None),
+        ("uncal", "uncal_cps", 300, None),
+        ("uncal_cps", "uncal", None, 300),
+    ],
+)
 def test_subtract_errors(type1, type2, lt1, lt2):
     """Test errors/warnings during subtraction of mixed spectra"""
 
-    spec1, spec2 = (make_spec(type1, lt=lt1),
-                    make_spec(type2, lt=lt2))
+    spec1, spec2 = (make_spec(type1, lt=lt1), make_spec(type2, lt=lt2))
     if lt1 is None and lt2 is None:
         with pytest.raises(bq.SpectrumError):
             diff = spec1 - spec2
@@ -733,8 +750,9 @@ def test_subtract_errors(type1, type2, lt1, lt2):
 #  Test multiplication and division of spectra
 # ----------------------------------------------
 
-@pytest.mark.parametrize('factor', [0.88, 1, 2, 43.6])
-@pytest.mark.parametrize('spectype', ['uncal', 'cal'])
+
+@pytest.mark.parametrize("factor", [0.88, 1, 2, 43.6])
+@pytest.mark.parametrize("spectype", ["uncal", "cal"])
 def test_basic_mul_div(spectype, factor):
     """
     Basic multiplication/division of uncalibrated spectrum by a scalar.
@@ -758,7 +776,7 @@ def test_basic_mul_div(spectype, factor):
     assert div.livetime is None
 
 
-@pytest.mark.parametrize('factor', [0.88, 1, 2, 43.6])
+@pytest.mark.parametrize("factor", [0.88, 1, 2, 43.6])
 def test_cps_mul_div(uncal_spec_cps, factor):
     """Multiplication/division of a CPS spectrum."""
 
@@ -775,11 +793,8 @@ def test_cps_mul_div(uncal_spec_cps, factor):
     assert div.livetime is None
 
 
-@pytest.mark.parametrize('factor', [
-    ufloat(0.88, 0.01),
-    ufloat(1, 0.1),
-    ufloat(43, 1)])
-@pytest.mark.parametrize('spectype', ['uncal', 'cal'])
+@pytest.mark.parametrize("factor", [ufloat(0.88, 0.01), ufloat(1, 0.1), ufloat(43, 1)])
+@pytest.mark.parametrize("spectype", ["uncal", "cal"])
 def test_uncal_mul_div_uncertainties(spectype, factor):
     """
     Multiplication/division of uncal spectrum by a scalar with uncertainty.
@@ -788,39 +803,43 @@ def test_uncal_mul_div_uncertainties(spectype, factor):
     spec = make_spec(spectype)
 
     mult_left = spec * factor
-    assert np.allclose(
-        mult_left.counts_vals, factor.nominal_value * spec.counts_vals)
+    assert np.allclose(mult_left.counts_vals, factor.nominal_value * spec.counts_vals)
     assert np.all(
-        (mult_left.counts_uncs > factor.nominal_value * spec.counts_uncs) |
-        (spec.counts_vals == 0))
+        (mult_left.counts_uncs > factor.nominal_value * spec.counts_uncs)
+        | (spec.counts_vals == 0)
+    )
     assert mult_left.livetime is None
 
     mult_right = factor * spec
-    assert np.allclose(
-        mult_right.counts_vals, factor.nominal_value * spec.counts_vals)
+    assert np.allclose(mult_right.counts_vals, factor.nominal_value * spec.counts_vals)
     assert np.all(
-        (mult_right.counts_uncs > factor.nominal_value * spec.counts_uncs) |
-        (spec.counts_vals == 0))
+        (mult_right.counts_uncs > factor.nominal_value * spec.counts_uncs)
+        | (spec.counts_vals == 0)
+    )
     assert mult_right.livetime is None
 
     div = spec / factor
-    assert np.allclose(
-        div.counts_vals, spec.counts_vals / factor.nominal_value)
+    assert np.allclose(div.counts_vals, spec.counts_vals / factor.nominal_value)
     assert np.all(
-        (div.counts_uncs > spec.counts_uncs / factor.nominal_value) |
-        (spec.counts_vals == 0))
+        (div.counts_uncs > spec.counts_uncs / factor.nominal_value)
+        | (spec.counts_vals == 0)
+    )
     assert div.livetime is None
 
 
-@pytest.mark.parametrize('type1, type2, error', [
-    ('uncal', 'uncal', TypeError),
-    ('uncal', 'asdf', TypeError),
-    ('uncal', 'data', TypeError),
-    ('uncal', 0, ValueError),
-    ('uncal', np.inf, ValueError),
-    ('uncal', np.nan, ValueError),
-    ('uncal', ufloat(0, 1), ValueError),
-    ('uncal', ufloat(np.inf, np.nan), ValueError)])
+@pytest.mark.parametrize(
+    "type1, type2, error",
+    [
+        ("uncal", "uncal", TypeError),
+        ("uncal", "asdf", TypeError),
+        ("uncal", "data", TypeError),
+        ("uncal", 0, ValueError),
+        ("uncal", np.inf, ValueError),
+        ("uncal", np.nan, ValueError),
+        ("uncal", ufloat(0, 1), ValueError),
+        ("uncal", ufloat(np.inf, np.nan), ValueError),
+    ],
+)
 def test_mul_div_errors(type1, type2, error):
     """Multiplication/division errors."""
 
@@ -840,6 +859,7 @@ def test_mul_div_errors(type1, type2, error):
 #         Test Spectrum.calibrate_like
 # ----------------------------------------------
 
+
 def test_calibrate_like(uncal_spec, cal_spec):
     """Test calibrate_like with an uncalibrated spectrum."""
 
@@ -851,7 +871,7 @@ def test_calibrate_like(uncal_spec, cal_spec):
 def test_recalibrate_like(cal_spec):
     """Test calibrate_like with an already calibrated spectrum."""
 
-    cal_new = make_spec('cal_new')
+    cal_new = make_spec("cal_new")
     edges1 = cal_spec.bin_edges_kev
     cal_spec.calibrate_like(cal_new)
     assert cal_spec.is_calibrated
@@ -879,7 +899,8 @@ def test_calibrate_like_copy(uncal_spec, cal_spec):
 #         Test Spectrum.combine_bins
 # ----------------------------------------------
 
-@pytest.mark.parametrize('spectype', ['uncal', 'cal', 'uncal_cps'])
+
+@pytest.mark.parametrize("spectype", ["uncal", "cal", "uncal_cps"])
 def test_combine_bins(spectype):
     """Test combine_bins with no padding."""
 
@@ -897,7 +918,7 @@ def test_combine_bins(spectype):
         assert np.sum(combined.cps_vals) == np.sum(spec.cps_vals)
 
 
-@pytest.mark.parametrize('spectype', ['uncal', 'cal', 'uncal_cps'])
+@pytest.mark.parametrize("spectype", ["uncal", "cal", "uncal_cps"])
 def test_combine_bins_padding(spectype):
     """Test combine_bins with padding (an uneven factor)."""
 
@@ -920,8 +941,9 @@ def test_combine_bins_padding(spectype):
 #         Test Spectrum.downsample
 # ----------------------------------------------
 
-@pytest.mark.parametrize('spectype', ['uncal', 'cal'])
-@pytest.mark.parametrize('f', [2, 1.5, 999.99])
+
+@pytest.mark.parametrize("spectype", ["uncal", "cal"])
+@pytest.mark.parametrize("f", [2, 1.5, 999.99])
 def test_downsample(spectype, f):
     """Test Spectrum.downsample on uncalibrated and calibrated spectra"""
 
@@ -947,7 +969,7 @@ def test_no_downsample(cal_spec):
 def test_zero_downsample(cal_spec):
     """Test that downsample(very large number) gives 0"""
 
-    spec2 = cal_spec.downsample(10**10)
+    spec2 = cal_spec.downsample(10 ** 10)
     s2 = np.sum(spec2.counts_vals)
     assert s2 == 0
 
@@ -962,10 +984,10 @@ def test_downsample_handle_livetime(cal_spec):
     spec2 = cal_spec.downsample(f)
     assert spec2.livetime is None
 
-    spec3 = cal_spec.downsample(f, handle_livetime='preserve')
+    spec3 = cal_spec.downsample(f, handle_livetime="preserve")
     assert spec3.livetime == cal_spec.livetime
 
-    spec4 = cal_spec.downsample(f, handle_livetime='reduce')
+    spec4 = cal_spec.downsample(f, handle_livetime="reduce")
     assert spec4.livetime == cal_spec.livetime / f
 
 
@@ -987,7 +1009,7 @@ def test_downsample_handle_livetime_error(uncal_spec):
     """Test bad value of handle_livetime"""
 
     with pytest.raises(ValueError):
-        uncal_spec.downsample(5, handle_livetime='asdf')
+        uncal_spec.downsample(5, handle_livetime="asdf")
 
 
 # ----------------------------------------------
@@ -1020,6 +1042,7 @@ def test_len_cps(length, livetime):
 #         Test Spectrum.copy
 # ----------------------------------------------
 
+
 def test_copy_uncal(uncal_spec):
     """Test copy method on uncal spectrum"""
 
@@ -1048,78 +1071,70 @@ def test_copy_cal(cal_spec):
 #         Test Spectrum.rebin
 # ----------------------------------------------
 
+
 @pytest.fixture(
     params=[
         TEST_EDGES_KEV.copy(),
         TEST_EDGES_KEV.copy()[1:-2],
         np.linspace(
-            TEST_EDGES_KEV.min(),
-            TEST_EDGES_KEV.max(),
-            len(TEST_EDGES_KEV) + 10)],
-    ids=[
-        "same edges",
-        "subset of edges",
-        "same bounds more bins"])
+            TEST_EDGES_KEV.min(), TEST_EDGES_KEV.max(), len(TEST_EDGES_KEV) + 10
+        ),
+    ],
+    ids=["same edges", "subset of edges", "same bounds more bins"],
+)
 def rebin_new_edges(request):
     return request.param.astype(np.float)
 
 
 @pytest.fixture(
-    params=[
-        'interpolation',
-        'listmode'],
-    ids=[
-        "interpolation method",
-        "listmode method"])
+    params=["interpolation", "listmode"],
+    ids=["interpolation method", "listmode method"],
+)
 def rebin_method(request):
     return request.param
 
 
 @pytest.fixture(
-    params=[
-        ('uncal', 300),
-        ('uncal', None),
-        ('cal_cps', None)],
+    params=[("uncal", 300), ("uncal", None), ("cal_cps", None)],
     ids=[
         "uncalibrated spectrum with livetime",
         "uncalibrated spectrum without livetime",
-        "calibrated spectrum with cps"])
+        "calibrated spectrum with cps",
+    ],
+)
 def rebin_spectrum_failure(request):
     return make_spec(request.param[0], lt=request.param[1])
 
 
-def test_spectrum_rebin_failure(rebin_spectrum_failure, rebin_new_edges,
-                                rebin_method):
+def test_spectrum_rebin_failure(rebin_spectrum_failure, rebin_new_edges, rebin_method):
     with pytest.raises(bq.SpectrumError):
-        rebin_spectrum_failure.rebin(rebin_new_edges, method=rebin_method,
-                                     zero_pad_warnings=False)
+        rebin_spectrum_failure.rebin(
+            rebin_new_edges, method=rebin_method, zero_pad_warnings=False
+        )
 
 
 @pytest.fixture(
-    params=[
-        ('cal', 300),
-        ('cal', None),
-        ('cal_cps', 300)],
+    params=[("cal", 300), ("cal", None), ("cal_cps", 300)],
     ids=[
         "calibrated spectrum with livetime",
         "calibrated spectrum without livetime",
-        "calibrated spectrum with cps and livetime"])
+        "calibrated spectrum with cps and livetime",
+    ],
+)
 def rebin_spectrum_success(request):
     return make_spec(request.param[0], lt=request.param[1])
 
 
-def test_spectrum_rebin_success(rebin_spectrum_success, rebin_new_edges,
-                                rebin_method):
-    kwargs = dict(out_edges=rebin_new_edges, method=rebin_method,
-                  zero_pad_warnings=False)
-    if ((rebin_spectrum_success._counts is None) and
-            (rebin_method == 'listmode')):
+def test_spectrum_rebin_success(rebin_spectrum_success, rebin_new_edges, rebin_method):
+    kwargs = dict(
+        out_edges=rebin_new_edges, method=rebin_method, zero_pad_warnings=False
+    )
+    if (rebin_spectrum_success._counts is None) and (rebin_method == "listmode"):
         with pytest.warns(bq.SpectrumWarning):
             spec = rebin_spectrum_success.rebin(**kwargs)
     else:
         spec = rebin_spectrum_success.rebin(**kwargs)
-    assert np.isclose(rebin_spectrum_success.counts_vals.sum(),
-                      spec.counts_vals.sum())
+    assert np.isclose(rebin_spectrum_success.counts_vals.sum(), spec.counts_vals.sum())
     if rebin_spectrum_success.livetime is None:
         assert spec.livetime is None
     else:
@@ -1132,8 +1147,8 @@ def test_spectrum_rebin_success(rebin_spectrum_success, rebin_new_edges,
 
 
 def test_spectrum_rebin_like():
-    spec1 = make_spec('cal')
-    spec2 = make_spec('cal_new')
+    spec1 = make_spec("cal")
+    spec2 = make_spec("cal_new")
     assert np.any(~np.isclose(spec1.bin_edges_kev, spec2.bin_edges_kev))
     spec2_rebin = spec2.rebin_like(spec1)
     assert np.all(np.isclose(spec1.bin_edges_kev, spec2_rebin.bin_edges_kev))
diff --git a/tests/wallet_cache_test.py b/tests/wallet_cache_test.py
index bc7513ae..050861d9 100644
--- a/tests/wallet_cache_test.py
+++ b/tests/wallet_cache_test.py
@@ -5,16 +5,17 @@
 import becquerel.tools.wallet_cache as wallet_cache
 import pytest
 
-# pylint: disable=no-self-use
 
-
-@pytest.mark.parametrize('arg, result', [
-    ('', None),
-    ('NaN', np.nan),
-    ('nan', np.nan),
-    ('5.0+/-1.0', uncertainties.ufloat(5., 1.)),
-    ('5.0', 5.),
-])
+@pytest.mark.parametrize(
+    "arg, result",
+    [
+        ("", None),
+        ("NaN", np.nan),
+        ("nan", np.nan),
+        ("5.0+/-1.0", uncertainties.ufloat(5.0, 1.0)),
+        ("5.0", 5.0),
+    ],
+)
 def test_convert_float_ufloat(arg, result):
     """Convert string to a float or a ufloat, including None ('') and NaN."""
     answer = wallet_cache.convert_float_ufloat(arg)
@@ -31,12 +32,15 @@ def test_convert_float_ufloat(arg, result):
             assert np.isclose(answer, result)
 
 
-@pytest.mark.parametrize('arg, result', [
-    (None, ''),
-    (np.nan, 'nan'),
-    (uncertainties.ufloat(5., 1.), '5.000000000000+/-1.000000000000'),
-    (5., '5.000000000000'),
-])
+@pytest.mark.parametrize(
+    "arg, result",
+    [
+        (None, ""),
+        (np.nan, "nan"),
+        (uncertainties.ufloat(5.0, 1.0), "5.000000000000+/-1.000000000000"),
+        (5.0, "5.000000000000"),
+    ],
+)
 def test_format_ufloat(arg, result):
     """Convert ufloat to a string, including None ('') and NaN."""
     answer = wallet_cache.format_ufloat(arg)
diff --git a/tests/xcom_test.py b/tests/xcom_test.py
index c59dcc3e..f39833eb 100644
--- a/tests/xcom_test.py
+++ b/tests/xcom_test.py
@@ -1,33 +1,123 @@
 """Test XCOM data queries."""
 
-from __future__ import print_function
 import numpy as np
 from becquerel.tools import xcom
 import pytest
 
-# pylint: disable=protected-access,too-many-public-methods
 XCOM_URL_ORIG = xcom._URL
 
 
 # energies to query using energies keyword
-ENERGIES_3 = [60., 662., 1460.]
+ENERGIES_3 = [60.0, 662.0, 1460.0]
 
 
 # standard grid energies for Germanium from 1 keV to 10 MeV
 GE_GRID_ENERGIES = [
-    1, 1.103, 1.217, 1.217, 1.232, 1.248, 1.248, 1.328, 1.414, 1.414, 1.500,
-    2, 3, 4, 5, 6, 8, 10, 11.100, 11.100, 15, 20, 30, 40, 50, 60, 80, 100,
-    150, 200, 300, 400, 500, 600, 800, 1000, 1022, 1250, 1500, 2000, 2044,
-    3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
+    1,
+    1.103,
+    1.217,
+    1.217,
+    1.232,
+    1.248,
+    1.248,
+    1.328,
+    1.414,
+    1.414,
+    1.500,
+    2,
+    3,
+    4,
+    5,
+    6,
+    8,
+    10,
+    11.100,
+    11.100,
+    15,
+    20,
+    30,
+    40,
+    50,
+    60,
+    80,
+    100,
+    150,
+    200,
+    300,
+    400,
+    500,
+    600,
+    800,
+    1000,
+    1022,
+    1250,
+    1500,
+    2000,
+    2044,
+    3000,
+    4000,
+    5000,
+    6000,
+    7000,
+    8000,
+    9000,
+    10000,
 ]
 
 
 # standard grid energies for Germanium, plus 60, 662, and 1460
 GE_GRID_ENERGIES_PLUS_3 = [
-    1, 1.103, 1.217, 1.217, 1.232, 1.248, 1.248, 1.328, 1.414, 1.414, 1.500,
-    2, 3, 4, 5, 6, 8, 10, 11.100, 11.100, 15, 20, 30, 40, 50, 60, 80, 100,
-    150, 200, 300, 400, 500, 600, 662, 800, 1000, 1022, 1250, 1460, 1500,
-    2000, 2044, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
+    1,
+    1.103,
+    1.217,
+    1.217,
+    1.232,
+    1.248,
+    1.248,
+    1.328,
+    1.414,
+    1.414,
+    1.500,
+    2,
+    3,
+    4,
+    5,
+    6,
+    8,
+    10,
+    11.100,
+    11.100,
+    15,
+    20,
+    30,
+    40,
+    50,
+    60,
+    80,
+    100,
+    150,
+    200,
+    300,
+    400,
+    500,
+    600,
+    662,
+    800,
+    1000,
+    1022,
+    1250,
+    1460,
+    1500,
+    2000,
+    2044,
+    3000,
+    4000,
+    5000,
+    6000,
+    7000,
+    8000,
+    9000,
+    10000,
 ]
 
 
@@ -37,26 +127,26 @@ class TestFetchXCOMData(object):
 
     def test_sym_energy(self):
         """Test fetch_xcom_data with symbol and one energy."""
-        energies = [1460.]
-        xd = xcom.fetch_xcom_data('Ge', energies_kev=energies)
+        energies = [1460.0]
+        xd = xcom.fetch_xcom_data("Ge", energies_kev=energies)
         assert len(xd) == len(energies)
         assert np.allclose(xd.energy, energies)
 
     def test_sym_energies(self):
         """Test fetch_xcom_data with symbol and three energies."""
-        xd = xcom.fetch_xcom_data('Ge', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("Ge", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_sym_upper(self):
         """Test fetch_xcom_data with uppercase symbol and three energies."""
-        xd = xcom.fetch_xcom_data('GE', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("GE", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_sym_lower(self):
         """Test fetch_xcom_data with lowercase symbol and three energies."""
-        xd = xcom.fetch_xcom_data('ge', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("ge", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
@@ -68,48 +158,47 @@ def test_z_int(self):
 
     def test_z_str(self):
         """Test fetch_xcom_data with z (string) and three energies."""
-        xd = xcom.fetch_xcom_data('32', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("32", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_compound1(self):
         """Test fetch_xcom_data with compound (H2O) and three energies."""
-        xd = xcom.fetch_xcom_data('H2O', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("H2O", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_compound2(self):
         """Test fetch_xcom_data with compound (NaCl) and three energies."""
-        xd = xcom.fetch_xcom_data('NaCl', energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data("NaCl", energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_mixture(self):
         """Test fetch_xcom_data with mixture and three energies."""
-        xd = xcom.fetch_xcom_data(
-            ['H2O 0.9', 'NaCl 0.1'], energies_kev=ENERGIES_3)
+        xd = xcom.fetch_xcom_data(["H2O 0.9", "NaCl 0.1"], energies_kev=ENERGIES_3)
         assert len(xd) == len(ENERGIES_3)
         assert np.allclose(xd.energy, ENERGIES_3)
 
     def test_standard_grid(self):
         """Test fetch_xcom_data with standard grid."""
-        xd = xcom.fetch_xcom_data('Ge', e_range_kev=[1., 10000.])
+        xd = xcom.fetch_xcom_data("Ge", e_range_kev=[1.0, 10000.0])
         assert len(xd) == len(GE_GRID_ENERGIES)
         assert np.allclose(xd.energy, GE_GRID_ENERGIES)
 
     def test_standard_grid_energies(self):
         """Test fetch_xcom_data with three energies and standard grid."""
         xd = xcom.fetch_xcom_data(
-            'Ge', energies_kev=ENERGIES_3, e_range_kev=[1., 10000.])
+            "Ge", energies_kev=ENERGIES_3, e_range_kev=[1.0, 10000.0]
+        )
         assert len(xd) == len(GE_GRID_ENERGIES_PLUS_3)
         assert np.allclose(xd.energy, GE_GRID_ENERGIES_PLUS_3)
 
     def test_mixtures_predefined(self):
         """Test fetch_xcom_data for predefined mixtures."""
-        mixtures = [key for key in dir(xcom) if key.startswith('MIXTURE')]
+        mixtures = [key for key in dir(xcom) if key.startswith("MIXTURE")]
         for mixture in mixtures:
-            xd = xcom.fetch_xcom_data(
-                getattr(xcom, mixture), energies_kev=ENERGIES_3)
+            xd = xcom.fetch_xcom_data(getattr(xcom, mixture), energies_kev=ENERGIES_3)
             assert len(xd) == len(ENERGIES_3)
             assert np.allclose(xd.energy, ENERGIES_3)
 
@@ -121,38 +210,32 @@ def test_except_z_range(self):
     def test_except_bad_compound(self):
         """Test fetch_xcom_data raises except for bad compound formula."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                'H2O++', energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data("H2O++", energies_kev=ENERGIES_3)
 
     def test_except_bad_mixture1(self):
         """Test fetch_xcom_data raises except for badly formed mixture (1)."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                ['H2O 0.9', 'NaCl'], energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data(["H2O 0.9", "NaCl"], energies_kev=ENERGIES_3)
 
     def test_except_bad_mixture2(self):
         """Test fetch_xcom_data raises except for badly formed mixture (2)."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                ['H2O 1 1', 'NaCl 1'], energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data(["H2O 1 1", "NaCl 1"], energies_kev=ENERGIES_3)
 
     def test_except_bad_mixture3(self):
         """Test fetch_xcom_data raises except for badly formed mixture (3)."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                {'H2O': '1', 'NaCl': '1'}, energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data({"H2O": "1", "NaCl": "1"}, energies_kev=ENERGIES_3)
 
     def test_except_bad_mixture4(self):
         """Test fetch_xcom_data raises except for badly formed mixture (4)."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                ['H2O 0.9', ['NaCl', '0.1']], energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data(["H2O 0.9", ["NaCl", "0.1"]], energies_kev=ENERGIES_3)
 
     def test_except_bad_mixture5(self):
         """Test fetch_xcom_data raises except for badly formed mixture (5)."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data(
-                ['H2O 0.9', 'NaCl $0.1'], energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data(["H2O 0.9", "NaCl $0.1"], energies_kev=ENERGIES_3)
 
     def test_except_bad_arg(self):
         """Test fetch_xcom_data raises exception if given bad argument."""
@@ -162,63 +245,63 @@ def test_except_bad_arg(self):
     def test_except_no_energies(self):
         """Test fetch_xcom_data raises except if no energies are requested."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge')
+            xcom.fetch_xcom_data("Ge")
 
     def test_except_status_404(self):
         """Test fetch_xcom_data raises exception if website not found."""
-        xcom._URL = 'http://httpbin.org/status/404'
+        xcom._URL = "http://httpbin.org/status/404"
         with pytest.raises(xcom.XCOMRequestError):
-            xcom.fetch_xcom_data('Ge', energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data("Ge", energies_kev=ENERGIES_3)
         xcom._URL = XCOM_URL_ORIG
 
     def test_except_website_empty(self):
         """Test fetch_xcom_data raises except if data from website is empty."""
-        xcom._URL = 'http://httpbin.org/post'
+        xcom._URL = "http://httpbin.org/post"
         with pytest.raises(xcom.XCOMRequestError):
-            xcom.fetch_xcom_data('Ge', energies_kev=ENERGIES_3)
+            xcom.fetch_xcom_data("Ge", energies_kev=ENERGIES_3)
         xcom._URL = XCOM_URL_ORIG
 
     def test_except_energies_kev_float(self):
         """Test fetch_xcom_data raises except if energies_kev not iterable."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', energies_kev=1460.)
+            xcom.fetch_xcom_data("Ge", energies_kev=1460.0)
 
     def test_except_energies_kev_low(self):
         """Test fetch_xcom_data raises exception if energies_kev too low."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', energies_kev=[60., 662., 1460., 0.001])
+            xcom.fetch_xcom_data("Ge", energies_kev=[60.0, 662.0, 1460.0, 0.001])
 
     def test_except_energies_kev_high(self):
         """Test fetch_xcom_data raises exception if energies_kev too high."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', energies_kev=[60., 662., 1460., 1e9])
+            xcom.fetch_xcom_data("Ge", energies_kev=[60.0, 662.0, 1460.0, 1e9])
 
     def test_except_e_range_kev_float(self):
         """Test fetch_xcom_data raises except if e_range_kev not iterable."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=100.)
+            xcom.fetch_xcom_data("Ge", e_range_kev=100.0)
 
     def test_except_e_range_kev_len(self):
         """Test fetch_xcom_data raises exception if len(e_range_kev) != 2."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=[1., 10000., 100000.])
+            xcom.fetch_xcom_data("Ge", e_range_kev=[1.0, 10000.0, 100000.0])
 
     def test_except_e_range_kev_0_range(self):
         """Test fetch_xcom_data exception if e_range_kev[0] out of range."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=[0.1, 10000.])
+            xcom.fetch_xcom_data("Ge", e_range_kev=[0.1, 10000.0])
 
     def test_except_e_range_kev_1_range(self):
         """Test fetch_xcom_data exception if e_range_kev[1] out of range."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=[0.1, 1e9])
+            xcom.fetch_xcom_data("Ge", e_range_kev=[0.1, 1e9])
 
     def test_except_e_range_kev_order(self):
         """Test fetch_xcom_data raises except if e_range_kev out of order."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=[1000., 1.])
+            xcom.fetch_xcom_data("Ge", e_range_kev=[1000.0, 1.0])
 
     def test_except_bad_kwarg(self):
         """Test fetch_xcom_data raises exception if bad keyword given."""
         with pytest.raises(xcom.XCOMInputError):
-            xcom.fetch_xcom_data('Ge', e_range_kev=[1., 10000.], bad_kw=None)
+            xcom.fetch_xcom_data("Ge", e_range_kev=[1.0, 10000.0], bad_kw=None)