Repository: hyperspy/rosettasciio (language: python)

Issue:
Add templates and changelog
- Add issue and PR templates to project (based of HyperSpy ones).
- Add towncrier changelog  (based of HyperSpy).
Doc and import improvement
As discussed in https://github.com/hyperspy/rosettasciio/issues/38. This changes the import and docstring/documentation for the `bruker` and `blockfile` format as examples for discussion.

### Progress of the PR
- [x] Change implemented (can be split into several points),
- [x] update docstring (if appropriate),
- [x] update user guide (if appropriate),
- [ ] add an changelog entry in the `upcoming_changes` folder (see [`upcoming_changes/README.rst`](https://github.com/hyperspy/rosettasciio/blob/main/upcoming_changes/README.rst)),
- [ ] Check formatting changelog entry in the `readthedocs` doc build of this PR (link in github checks)
- [ ] add tests,
- [x] ready for review.

### Minimal example of the bug fix or the new feature
```python
from rsciio.msa import api
api.file_reader("your_msa_file.msa")
# Your new feature...
```
Note that this example can be useful to update the user guide.


Consolidate docstring and documentation
The problem is also, that it is not consistent how detailed the `file_reader` and `file_writer` docstrings are. For some formats more is documented in the user guide, for others more in the docstrings.

_Originally posted by @jlaehne in https://github.com/hyperspy/rosettasciio/pull/35#discussion_r968012172_


Discussion / hints:
# [Codecov](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) Report
> Merging [#3](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) (30707ac) into [main](https://codecov.io/gh/hyperspy/rosettasciio/commit/a3539d5c72e6838053ac275eec677ee2d3c53688?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) (a3539d5) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main       #3   +/-   ##
=======================================
  Coverage   85.77%   85.77%           
=======================================
  Files          72       72           
  Lines       14689    14689           
=======================================
  Hits        12599    12599           
  Misses       2090     2090           
```


| [Impacted Files](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) | Coverage Δ | |
|---|---|---|
| [setup.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/3/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-c2V0dXAucHk=) | `0.00% <ø> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy). Last update [a3539d5...30707ac](https://codecov.io/gh/hyperspy/rosettasciio/pull/3?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy).

The `docs/readthedocs.org:rosettasciiio` link to the documentation is still missing. Downgraded to `python 3.8` in `.readthedocs.yml` since a test in https://github.com/LumiSpy/lumispy/pull/138 showed that this is the latest version which seems to run through.
> Does it deserve a changelog note? ;) Possibly for the two other PRs, as this is useful for future reference?

Added a changelog entry for the github actions and for this PR.
I would say the first Readme version is not significant enough.
> Added a changelog entry for the github actions and for this PR. I would say the first Readme version is not significant enough.

Did you push these changes?
> Did you push these changes?

I forgot the commit, it was late yesterday ;-)
# [Codecov](https://codecov.io/gh/hyperspy/rosettasciio/pull/47?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) Report
Base: **83.35**% // Head: **83.39**% // Increases project coverage by **`+0.04%`** :tada:
> Coverage data is based on head [(`d08ae32`)](https://codecov.io/gh/hyperspy/rosettasciio/pull/47?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) compared to base [(`e641fca`)](https://codecov.io/gh/hyperspy/rosettasciio/commit/e641fcac88fd17bb9e8a8ddc7ee4a1b4f8f169f2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy).
> Patch coverage: 97.29% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #47      +/-   ##
==========================================
+ Coverage   83.35%   83.39%   +0.04%     
==========================================
  Files          40       43       +3     
  Lines        8337     8348      +11     
  Branches     1896     1896              
==========================================
+ Hits         6949     6962      +13     
+ Misses        913      911       -2     
  Partials      475      475              
```


| [Impacted Files](https://codecov.io/gh/hyperspy/rosettasciio/pull/47?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy) | Coverage Δ | |
|---|---|---|
| [rsciio/blockfile/\_api.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2Jsb2NrZmlsZS9fYXBpLnB5) | `95.31% <95.65%> (ø)` | |
| [rsciio/blockfile/\_\_init\_\_.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2Jsb2NrZmlsZS9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [rsciio/bruker/\_\_init\_\_.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2JydWtlci9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [rsciio/bruker/\_api.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2JydWtlci9fYXBpLnB5) | `88.27% <100.00%> (ø)` | |
| [rsciio/docstrings.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2RvY3N0cmluZ3MucHk=) | `100.00% <100.00%> (ø)` | |
| [rsciio/image/api.py](https://codecov.io/gh/hyperspy/rosettasciio/pull/47/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy#diff-cnNjaWlvL2ltYWdlL2FwaS5weQ==) | `91.56% <0.00%> (+2.40%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/hyperspy/rosettasciio/pull/47?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperspy).

Loading the API documentation in the user guide worked out for bruker, but not for blockfile.
All other format descriptions consistently have the ``.`` in the file abbreviations -- we can change it everywhere when going through the formats, but we should be consistent.
A subsection header for the API part of the format pages would be nice, e.g. *API documentation* or *API parameters* or *Provided API functions*
> Loading the API documentation in the user guide worked out for bruker, but not for blockfile.

`scikit-image` was missing from the build and as it is a requirement for the blockfile plugin, the import of this module will fail.


> All other format descriptions consistently have the `.` in the file abbreviations -- we can change it everywhere when going through the formats, but we should be consistent.

Ok, if this is done consistently in other format, I will revert this change. I am not sure if we need the `.` but we should consider it in a separate PR.

> A subsection header for the API part of the format pages would be nice, e.g. API documentation or API parameters or Provided API functions

I started to add one but I find it a bit unnecessary/redundant. 
> Ok, if this is done consistently in other format, I will revert this change. I am not sure if we need the `.` but we should consider it in a separate PR.

It was a complete mixture, but in a previous PR on the docs I did it that way. Can live with the other way at well, but should be uniform.
 
> > A subsection header for the API part of the format pages would be nice, e.g. API documentation or API parameters or Provided API functions
> 
> I started to add one but I find it a bit unnecessary/redundant.

Well, if the initial description is long, it does not hurt to have a link in the right-side menu to jump there. And for a page like the NeXus one, where there are various subsections, I would find it weird to not have one for the API part.
Before continuing this PR, it would good to agree on the main changes:

## Public API
- importing functions have changed from `from rsciio.bruker.api import file_reader` to `from rsciio.bruker import file_reader`
- the public api of a plugin is defined in `__init__` and expose only `file_reader` and `file_writer`, all other functions would be made private
- the submodules are private  
- the io plugins registry haven't changed

@francisco-dlp, you implemented the current structure of the plugins, does the simplification of this PR sounds good to you? 

## Documentation

- Pull docstring of `file_reader`/`file_write` in plugin documentation page to avoid duplication of documentation - in particular for parameters
- For plugin, the documentation should have an introduction to the format, then the API (sphinx `automodule`) and some other section where suitable.

For the procedure to continue, should we do all the readers in this PR (other people could contribute certain readers) or do separate PRs for a number of plugins at a time?
I would prefer separate PRs, otherwise the diff preview on github is difficult to use... As several plugins with simple (or related/similar) changes can be put in one single PR, there shouldn't be that many PRs, maybe about 6-7?
> Before continuing this PR, it would good to agree on the main changes:

I agree, a reply from @francisco-dlp would be good though.
> A subsection header for the API part of the format pages would be nice, e.g. _API documentation_ or _API parameters_ or _Provided API functions_

In my opinion, this PR would just need a suitable header for the pulled in API sections and then we could proceed to implement everything for the other readers as well. I would say _API functions_ is appropriate.
LGTM!
@jlaehne, should we merge this PR to update other plugins documentation in separate PRs?
Just a few points I realized while working on the first other formats that we should maybe fix before merging. In the meantime, I started working from the back of the alphabet in #59 - the PR needs to be rebased once this one is merged as I included your changes to start out with.
I will update #59 accordingly to reflect the latest changes in this PR.
One approach could be to:
- put the generic/introductory plugin specific documentation a `rst`/`md` file for each plugin, which could live in the `docs` folder or in the plugin folder
- add the `file_reader` and `file_write` docstrings in the plugin specific page of the docs

This could be done by one of the two:
- use a sphinx template
- include the API reference in the plugin specific documentation page.

Considering that sphinx is a bit brittle and not always clearly documented, using the sphinx template approach may be more difficult but it would have the advantage of enforcing consistence between plugins and avoid repeating code.
>     * put the generic/introductory plugin specific documentation a `rst`/`md` file for each plugin, which could live in the `docs` folder or in the plugin folder

That file exists for every plugin afaik, in `docs/supported_formats/`.
 
>     * add the `file_reader` and `file_write` docstrings in the plugin specific page of the docs

That remains to be done, though some plugins have very detailed docstrings and others none at all.
 

Fix patch:
diff --git a/docs/api.rst b/docs/api/index.rst
similarity index 98%
rename from docs/api.rst
rename to docs/api/index.rst
index 927d782cc..dbf5fb3f1 100644
--- a/docs/api.rst
+++ b/docs/api/index.rst
@@ -5,6 +5,12 @@ API
 The RosettaSciIO Application Programming Interface allows other python packages
 to use its input/output (IO) capabilities.
 
+.. toctree::
+   :hidden:
+
+   utils
+
+
 .. _interfacing-api:
 
 Interfacing the RosettaSciIO plugins
@@ -64,4 +70,3 @@ for reading/writing of data files:
     electron backscatter diffraction (EBSD) patterns in Python 
   * `PyXem <https://pyxem.readthedocs.io>`_: An open-source Python library for
     multi-dimensional diffraction microscopy. 
-
diff --git a/docs/api/utils.rst b/docs/api/utils.rst
new file mode 100644
index 000000000..86583e087
--- /dev/null
+++ b/docs/api/utils.rst
@@ -0,0 +1,15 @@
+.. _utils:
+
+Utility functions
+=================
+
+RosettaSciIO provides certain utility functions that are applicable for multiple
+formats, e.g. for the HDF5-format on which a number of plugins are based.
+
+.. _hdf5-utils:
+
+HDF5 utility functions
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: rsciio.utils.hdf5
+   :members:
diff --git a/docs/file_specification/index.rst b/docs/file_specification/index.rst
index 9beff99a5..74da01b62 100644
--- a/docs/file_specification/index.rst
+++ b/docs/file_specification/index.rst
@@ -1,9 +1,14 @@
 File Specifications
 ===================
 
-This section contains reference documentation for file specification available
+This section contains reference documentation for file specifications available
 publicly.
 
+.. toctree::
+   :hidden:
+   
+   ripple-specs
+
 .. _edax-file_specification:
 
 EDAX
@@ -15,3 +20,10 @@ available publicly available from EDAX:
 - :download:`spc <edax/SPECTRUM-V70.pdf>`
 - :download:`spd <edax/SpcMap-spd.file.format.pdf>`
 - :download:`ipr <edax/ImageIPR.pdf>`
+
+
+Ripple (Lispix)
+---------------
+
+:ref:`Format description <ripple-file_specification>` for the :ref:`ripple
+<ripple-format>` file format.
diff --git a/docs/file_specification/ripple-specs.rst b/docs/file_specification/ripple-specs.rst
new file mode 100644
index 000000000..322d8d78e
--- /dev/null
+++ b/docs/file_specification/ripple-specs.rst
@@ -0,0 +1,105 @@
+.. _ripple-file_specification:
+
+Ripple (Lispix) format description
+==================================
+
+This page summarizes the specifications for the :ref:`ripple <ripple-format>`
+("raw parameter list") file format developed at NIST as native format for `Lispix
+<https://www.nist.gov/services-resources/software/lispix>`_.
+
+Images and spectral images or data cubes that are written in the
+(Lispix) raw file format, which contains just a continuous string of numbers.
+
+Data cubes can be stored image by image, or spectrum by spectrum.
+Single images are stored row by row, vector cubes are stored row by row
+(each row spectrum by spectrum), image cubes are stored image by image.
+
+All of the numbers are in the same format, such as 16 bit signed integer,
+IEEE 8-byte real, 8-bit unsigned byte, etc.
+
+The ``.raw`` file should be accompanied by an ASCII text, tab delimited file with
+the same name and ``.rpl`` extension. This "raw parameter list" file lists the
+characteristics of the ``.raw`` file.
+
+RPL keywords
+^^^^^^^^^^^^
+
+Some of the following keys are specific to `HyperSpy <https://hyperspy.org>`_ and
+will be ignored by other software.
+
+.. code::
+
+    ========================  =======  =================================================
+    Key                       Type     Description
+    ========================  =======  =================================================
+    width                     int      pixels per row
+    height                    int      number of rows
+    depth                     int      number of images or spectral pts
+    offset                    int      bytes to skip
+    data-type                 str      'signed', 'unsigned', or 'float'
+    data-length               str      bytes per pixel  '1', '2', '4', or '8'
+    byte-order                str      'big-endian', 'little-endian', or 'dont-care'
+    record-by                 str      'image', 'vector', or 'dont-care'
+    # X-ray keys:
+    ev-per-chan               int      optional, eV per channel
+    detector-peak-width-ev    int      optional, FWHM for the Mn K-alpha line
+    # HyperSpy-specific keys
+    depth-origin              int      energy offset in pixels
+    depth-scale               float    energy scaling (units per pixel)
+    depth-units               str      energy units, usually eV
+    depth-name                str      Name of the magnitude stored as depth
+    width-origin              int      column offset in pixels
+    width-scale               float    column scaling (units per pixel)
+    width-units               str      column units, usually nm
+    width-name                str      Name of the magnitude stored as width
+    height-origin             int      row offset in pixels
+    height-scale              float    row scaling (units per pixel)
+    height-units              str      row units, usually nm
+    height-name               str      Name of the magnitude stored as height
+    signal                    str      Type of the signal stored, e.g. EDS_SEM
+    convergence-angle         float    TEM convergence angle in mrad
+    collection-angle          float    EELS spectrometer collection semi-angle in mrad
+    beam-energy               float    TEM beam energy in keV
+    elevation-angle           float    Elevation angle of the EDS detector
+    azimuth-angle             float    Elevation angle of the EDS detector
+    live-time                 float    Live time per spectrum
+    energy-resolution         float    Resolution of the EDS (FHWM of MnKa)
+    tilt-stage                float    The tilt of the stage
+    date                      str      date in ISO 8601
+    time                      str      time in ISO 8601
+    title                     str      title of the signal to be stored
+    ========================  =======  =================================================
+
+
+Notes
+^^^^^
+
+When ``'data-length'`` is 1, the ``'byte order'`` is not relevant as there is only
+one byte per datum, and ``'byte-order'`` should be ``'dont-care'``.
+
+When ``'depth'`` is 1, the file has one image, ``'record-by'`` is not relevant and
+should be ``'dont-care'``. For spectral images, ``'record-by'`` is 'vector'.
+For stacks of images, ``'record-by'`` is ``'image'``.
+
+Floating point numbers can be IEEE 4-byte, or IEEE 8-byte. Therefore if
+data-type is float, data-length MUST be 4 or 8.
+
+The ``.rpl`` file is read in a case-insensitive manner. However, when providing
+a dictionary as input, the keys MUST be lowercase.
+
+Comment lines, beginning with a semi-colon ``;`` are allowed anywhere.
+
+The first non-comment in the ``.rpl`` file line MUST have two column names:
+``'name_1'<TAB>'name_2'``; any name would do e.g. ``'key'<TAB>'value'``.
+
+Parameters can be in ANY order.
+
+In the ``.rpl`` file, the parameter name is followed by ONE tab (spaces are
+ignored) e.g.: ``'data-length'<TAB>'2'``
+
+In the ``.rpl`` file, other data and more tabs can follow the two items on
+each row, and are ignored.
+
+Other keys and values can be included and are ignored.
+
+Any number of spaces can go along with each tab.
diff --git a/docs/index.rst b/docs/index.rst
index e94e0cf59..ae9a16064 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -42,8 +42,8 @@ Table of contents
    :maxdepth: 2
 
    supported_formats/index
-   api
-   interoperatibility
+   api/index
+   interoperability
    file_specification/index
    contributing
    changes
diff --git a/docs/interoperatibility.rst b/docs/interoperability.rst
similarity index 100%
rename from docs/interoperatibility.rst
rename to docs/interoperability.rst
diff --git a/docs/supported_formats/hspy.rst b/docs/supported_formats/hspy.rst
index ef7f13ad7..338a7a695 100644
--- a/docs/supported_formats/hspy.rst
+++ b/docs/supported_formats/hspy.rst
@@ -97,6 +97,8 @@ passing ``chunks=True`` results in ``(7, 7, 256)`` chunks.
 Choosing the correct chunk-size can significantly affect the speed of reading, writing and performance of many HyperSpy algorithms.
 See the :external+hyperspy:ref:`chunking section <big_data.chunking>` for more information.
 
+Also see the :ref:`hdf5-utils` for inspecting HDF5 files.
+
 Extra saving arguments
 ^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/docs/supported_formats/netcdf.rst b/docs/supported_formats/netcdf.rst
index cd21d34ad..a3ba60932 100644
--- a/docs/supported_formats/netcdf.rst
+++ b/docs/supported_formats/netcdf.rst
@@ -9,5 +9,13 @@ but we do not support writing to this format.
 
 Note that only NetCDF files written by EELSLab are supported.
 
-To use this format a python netcdf interface must be installed manually because
-it is not installed by default when using the automatic installers.
+To use this format a python netcdf interface, such as `netcdf4
+<http://unidata.github.io/netcdf4-python/>`_ must be installed manually because
+it is not installed by default.
+
+
+API functions
+^^^^^^^^^^^^^
+
+.. automodule:: rsciio.netcdf
+   :members:
diff --git a/docs/supported_formats/nexus.rst b/docs/supported_formats/nexus.rst
index 1604a1e31..d906bd1c7 100644
--- a/docs/supported_formats/nexus.rst
+++ b/docs/supported_formats/nexus.rst
@@ -49,14 +49,14 @@ metadata structured in different ways, the loader is written to be quite
 flexible and can also be used to inspect any hdf5 based file.
 
 
-Differences with respect to hspy
+Differences with respect to HSpy
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 The `HyperSpy metadata structure <https://hyperspy.org/hyperspy-doc/current/user_guide/metadata_structure.html>`_
 stores arrays as hdf datasets without attributes
 and stores floats, ints and strings as attributes.
-NeXus formats typically use hdf datasets attributes to store additional
-information such as an indication of the units for an axis or the NX_class which
+The NeXus format uses hdf dataset attributes to store additional
+information such as an indication of the units for an axis or the ``NX_class`` which
 the dataset structure follows. The metadata, HyperSpy or ``original_metadata``,
 therefore needs to be able to indicate the values and attributes of a dataset.
 To implement this structure the ``value`` and ``attrs`` of a dataset can also be
@@ -140,8 +140,18 @@ restored when a signal is loaded from a previously saved NeXus file.
     Altering the standard metadata structure of a signal
     using ``attrs`` or ``value`` keywords is not recommended.
 
-Reading
-^^^^^^^
+Also see the :ref:`hdf5-utils` for inspecting HDF5 files.
+
+API functions
+^^^^^^^^^^^^^
+
+.. automodule:: rsciio.nexus
+   :members:
+
+
+Reading examples
+^^^^^^^^^^^^^^^^
+
 NeXus files can contain multiple datasets within the same file, but the
 ordering of datasets can vary depending on the setup of an experiment or
 processing step when the data was collected.
@@ -151,16 +161,6 @@ and returns a list of signals but in this example case the indexing is different
 To control which data or metadata is loaded and in what order
 some additional loading arguments are provided.
 
-Extra loading arguments
-+++++++++++++++++++++++
-
-- ``dataset_key``: ``None``, ``str`` or ``list`` of strings - Default is ``None`` . String(s) to search for in the path to find one or more datasets.
-- ``dataset_path``: ``None``, ``str`` or ``list`` of strings - Default is ``None`` . Absolute path(s) to search for in the path to find one or more datasets.
-- ``metadata_key``: ``None``, ``str`` or ``list`` of strings - Default is ``None`` . Absolute path(s) or string(s) to search for in the path to find metadata.
-- ``skip_array_metadata``: ``bool`` - Default is False. Option to skip loading metadata that are arrays to avoid duplicating loading of data.
-- ``nxdata_only``: ``bool`` - Default is False. Option to only convert NXdata formatted data to signals.
-- ``hardlinks_only``: ``bool`` - Default is False. Option to ignore soft or External links in the file.
-- ``use_default``: ``bool`` - Default is False. Only load the ``default`` dataset, if defined, from the file. Otherwise load according to the other keyword options.
 
 .. note::
 
@@ -168,25 +168,10 @@ Extra loading arguments
     is strongly recommended if the content of the HDF5 file is not known apriori.
     This prevents issues with regard to loading datasets far larger than memory.
 
-    Also note that setting ``lazy=True`` leaves the file handle to the HDF5 file open
-    and it can be closed with :py:meth:`~._signals.lazy.LazySignal.close_file`
-    or when using :py:meth:`~._signals.lazy.LazySignal.compute` with ``close_file=True``.
-
-
-Reading a NeXus file (a single NeXus dataset):
-
-.. code-block:: python
-
-    >>> sig = hs.load("sample.nxs")
-
-By default, the loader will look for stored NXdata objects.
-If there are hdf datasets which are not stored as NXdata, but which
-should be loaded as signals, set the ``nxdata_only`` keyword to False and all
-hdf datasets will be returned as signals:
-
-.. code-block:: python
+    Also note that setting ``lazy=True`` leaves the file handle to the HDF5 file
+    open. In Hyperspy, it can with ``_signals.lazy.LazySignal.close_file``
+    or when using ``_signals.lazy.LazySignal.compute`` with ``close_file=True``.
 
-    >>> sig = hs.load("sample.nxs", nxdata_only=False)
 
 We can load a specific dataset using the ``dataset_path`` keyword argument.
 Setting it to the absolute path of the desired dataset will cause
@@ -194,8 +179,9 @@ the single dataset to be loaded:
 
 .. code-block:: python
 
+    >>> from rsciio.nexus import file_reader
     >>> # Loading a specific dataset
-    >>> hs.load("sample.nxs", dataset_path="/entry/experiment/EDS/data")
+    >>> file_reader("sample.nxs", dataset_path="/entry/experiment/EDS/data")
 
 We can also choose to load datasets based on a search key using the
 ``dataset_key`` keyword argument. This can also be used to load NXdata not
@@ -206,7 +192,7 @@ returned. The previous example could also be written as:
 .. code-block:: python
 
     >>> # Loading datasets containing the string "EDS"
-    >>> hs.load("sample.nxs", dataset_key="EDS")
+    >>> file_reader("sample.nxs", dataset_key="EDS")
 
 The difference between ``dataset_path`` and ``dataset_key`` is illustrated
 here:
@@ -214,23 +200,23 @@ here:
 .. code-block:: python
 
     >>> # Only the dataset /entry/experiment/EDS/data will be loaded
-    >>> hs.load("sample.nxs", dataset_path="/entry/experiment/EDS/data")
+    >>> file_reader("sample.nxs", dataset_path="/entry/experiment/EDS/data")
     >>> # All datasets contain the entire string "/entry/experiment/EDS/data" will be loaded
-    >>> hs.load("sample.nxs", dataset_key="/entry/experiment/EDS/data")
+    >>> file_reader("sample.nxs", dataset_key="/entry/experiment/EDS/data")
 
 Multiple datasets can be loaded by providing a number of keys:
 
 .. code-block:: python
 
     >>> # Loading a specific dataset
-    >>> hs.load("sample.nxs", dataset_key=["EDS", "Fe", "Ca"])
+    >>> file_reader("sample.nxs", dataset_key=["EDS", "Fe", "Ca"])
 
 Metadata can also be filtered in the same way using ``metadata_key``:
 
 .. code-block:: python
 
     >>> # Load data with metadata matching metadata_key
-    >>> hs.load("sample.nxs", metadata_key="entry/instrument")
+    >>> file_reader("sample.nxs", metadata_key="entry/instrument")
 
 .. note::
 
@@ -243,33 +229,26 @@ Metadata that are arrays can be skipped by using ``skip_array_metadata``:
     >>> # Load data while skipping metadata that are arrays
     >>> hs.load("sample.nxs", skip_array_metadata=True)
 
-NeXus files also support parameters or dimensions that have been varied
-non-linearly. Since the reading of non-uniform axes is not yet implemented for the 
-NeXus plugin, such non-linear information would be lost in the axes manager and
-replaced with indices.
-NeXus and HDF can result in large metadata structures with large datasets within the loaded
-original_metadata. If lazy loading is used this may not be a concern but care must be taken
-when saving the data. To control whether large datasets are loaded or saved,
-use the ``metadata_key`` to load only the most relevant information. Alternatively,
-set ``skip_array_metadata`` to ``True`` to avoid loading those large datasets in original_metadata.
+.. note::
 
+    NeXus files also support parameters or dimensions that have been varied
+    non-linearly. Since the reading of non-uniform axes is not yet implemented for the 
+    NeXus plugin, such non-linear information would be lost in the axes manager and
+    replaced with indices.
 
-Writing
-^^^^^^^
-Signals can be written to new NeXus files using the standard :py:meth:`~.signal.BaseSignal.save`
-function.
+.. note::
 
-Extra saving arguments
-++++++++++++++++++++++
-- ``save_original_metadata``: ``bool`` - Default is True, option to save the original_metadata when storing to file.
-- ``skip_metadata_key``: ``bool`` - ``None``, ``str`` or ``list`` of strings - Default is ``None``. Option to skip certain metadata keys when storing to file.
-- ``use_default``: ``bool`` - Default is False. Set the ``default`` attribute for the NeXus file.
+    NeXus and HDF can result in large metadata structures with large datasets within the loaded
+    original_metadata. If lazy loading is used this may not be a concern but care must be taken
+    when saving the data. To control whether large datasets are loaded or saved,
+    use the ``metadata_key`` to load only the most relevant information. Alternatively,
+    set ``skip_array_metadata`` to ``True`` to avoid loading those large datasets in original_metadata.
 
-.. code-block:: python
 
-    >>> sig.save("output.nxs")
+Writing examples
+^^^^^^^^^^^^^^^^
 
-Using the save method will store the nexus file with the following structure:
+Using the ``file_writer`` method will store the NeXus file with the following structure:
 
 ::
 
@@ -283,24 +262,13 @@ Using the save method will store the nexus file with the following structure:
     │   │   │   ├── data and axes (NXdata format)
 
 
-The original_metadata can include hdf datasets which you may not wish to store.
-The original_metadata can be omitted using ``save_original_metadata``.
 
-.. code-block:: python
-
-    >>> sig.save("output.nxs", save_original_metadata=False)
-
-If only certain metadata are to be ignored, use ``skip_metadata_key``:
+To save multiple signals, the ``file_writer`` can be called directly passing a
+list of signals.
 
 .. code-block:: python
 
-    >>> sig.save("output.nxs", skip_metadata_key=['xsp3', 'solstice_scan'])
-
-To save multiple signals, the file_writer method can be called directly.
-
-.. code-block:: python
-
-    >>> from hyperspy.io_plugins.nexus import file_writer
+    >>> from rsciio.nexus import file_writer
     >>> file_writer("test.nxs",[signal1,signal2])
 
 When saving multiple signals, a default signal can be defined. This can be used when storing
@@ -311,10 +279,8 @@ The default signal is selected as the first signal in the list:
 
 .. code-block:: python
 
-    >>> from hyperspy.io_plugins.nexus import file_writer
-    >>> import hyperspy.api as hs
+    >>> from rsciio.nexus import file_writer
     >>> file_writer("test.nxs", [signal1, signal2], use_default = True)
-    >>> hs.load("test.nxs", use_default = True)
 
 The output will be arranged by signal name:
 
@@ -340,40 +306,9 @@ The output will be arranged by signal name:
 
 .. note::
 
-    Signals saved as ``.nxs`` by this plugin can be loaded normally and the
-    original_metadata, signal data, axes, metadata and learning_results
+    Signals saved as ``.nxs`` by this plugin can be loaded normally in HyperSpy
+    and the original_metadata, signal data, axes, metadata and learning_results
     will be restored. Model information is not currently stored.
     NeXus does not store how the data should be displayed.
     To preserve the signal details an additional navigation attribute
     is added to each axis to indicate if it is a navigation axis.
-
-
-Inspecting
-^^^^^^^^^^
-Looking in a NeXus or HDF file for specific metadata is often useful - e.g. to find
-what position a specific stage was at. The methods ``read_metadata_from_file``
-and ``list_datasets_in_file`` can be used to load the file contents or
-list the hdf datasets contained in a file. The inspection methods use the same ``metadata_key`` or ``dataset_key`` as when loading.
-For example to search for metadata in a file:
-
-    >>> from hyperspy.io_plugins.nexus import read_metadata_from_file
-    >>> read_metadata_from_file("sample.hdf5",metadata_key=["stage1_z"])
-    {'entry': {'instrument': {'scannables': {'stage1': {'stage1_z': {'value': -9.871700000000002,
-    'attrs': {'gda_field_name': 'stage1_z',
-    'local_name': 'stage1.stage1_z',
-    'target': '/entry/instrument/scannables/stage1/stage1_z',
-    'units': 'mm'}}}}}}}
-
-To list the datasets stored in the file:
-
-    >>> from hyperspy.io_plugins.nexus import read_datasets_from_file
-    >>> list_datasets_in_file("sample.nxs")
-    NXdata found
-    /entry/xsp3_addetector
-    /entry/xsp3_addetector_total
-    HDF datasets found
-    /entry/solstice_scan/keys/uniqueKeys
-    /entry/solstice_scan/scan_shape
-    Out[3]:
-    (['/entry/xsp3_addetector', '/entry/xsp3_addetector_total'],
-     ['/entry/solstice_scan/keys/uniqueKeys', '/entry/solstice_scan/scan_shape'])
diff --git a/docs/supported_formats/panta_rhei.rst b/docs/supported_formats/panta_rhei.rst
index 0b71a5241..d59c8667c 100644
--- a/docs/supported_formats/panta_rhei.rst
+++ b/docs/supported_formats/panta_rhei.rst
@@ -10,3 +10,10 @@ However, as new features are continuously being added to Panta Rhei, it is also
 possible to occasionally encounter some which are not yet supported by RosettaSciIO.
 Please report any problematic file on the `issues tracker 
 <https://github.com/hyperspy/rosettasciio/issues>`_ to make us aware of it.
+
+
+API functions
+^^^^^^^^^^^^^
+
+.. automodule:: rsciio.prz
+   :members:
diff --git a/docs/supported_formats/phenom.rst b/docs/supported_formats/phenom.rst
index 18d39850b..b4682c1a2 100644
--- a/docs/supported_formats/phenom.rst
+++ b/docs/supported_formats/phenom.rst
@@ -4,7 +4,7 @@ Phenom ELID format
 ------------------
 
 The ``.elid`` file format is used by the software package Element Identification for the Thermo
-Fisher Scientific Phenom desktop SEM. It is a proprietary binary format which can contain
+Fisher Scientific Phenom desktop SEMs. It is a proprietary binary format which can contain
 images, single EDS spectra, 1D line scan EDS spectra and 2D EDS spectrum maps. The reader
 will convert all signals and its metadata into dictionaries according to the
 RosettaSciIO API.
@@ -12,3 +12,10 @@ RosettaSciIO API.
 The current implementation supports ``.elid`` files created with Element Identification version
 3.8.0 and later. You can convert older ``.elid`` files by loading the file into a recent Element
 Identification release and then save the ``.elid`` file into the newer file format.
+
+
+API functions
+^^^^^^^^^^^^^
+
+.. automodule:: rsciio.phenom
+   :members:
diff --git a/docs/supported_formats/protochips.rst b/docs/supported_formats/protochips.rst
index f8dbab3cc..d70c7b886 100644
--- a/docs/supported_formats/protochips.rst
+++ b/docs/supported_formats/protochips.rst
@@ -3,7 +3,7 @@
 Protochips logfile
 ------------------
 
-RosettaSciIO can read heater, biasing and gas cell ``.log`` files for Protochips holder.
+RosettaSciIO can read heater, biasing and gas cell logfiles for Protochips holder.
 The format stores all the captured data together with a small header in a ``.csv``
 file. The reader extracts the measured quantity (e. g. temperature, pressure,
 current, voltage) along the time axis, as well as the notes saved during the
@@ -13,8 +13,16 @@ the reader assumes that the step is constant and takes its mean, which is a
 good approximation. Further releases of RosettaSciIO will read the time axis more
 precisely by supporting non-uniform axis (to be implemented!).
 
-To read Protochips logfiles, use the reader argument to define the correct file reader:
+To read Protochips logfiles in HyperSpy, use the ``reader`` argument to define the
+correct file plugin as the ``.csv`` extension is not unique to this reader:
 
 .. code-block:: python
 
     >>> hs.load("filename.csv", reader="protochips")
+
+
+API functions
+^^^^^^^^^^^^^
+
+.. automodule:: rsciio.protochips
+   :members:
diff --git a/docs/supported_formats/ripple.rst b/docs/supported_formats/ripple.rst
index 5882a4b31..42c1ccca1 100644
--- a/docs/supported_formats/ripple.rst
+++ b/docs/supported_formats/ripple.rst
@@ -7,7 +7,10 @@ The ``.rpl`` format (short for "Raw Parameter List") is an *open standard format
 developed at NIST as native format for `Lispix
 <https://www.nist.gov/services-resources/software/lispix>`_ and is widely used to
 exchange multidimensional data. However, it only supports data of up to three
-dimensions.
+dimensions. See the :ref:`file specifications <ripple-file_specification>`
+for a description of the file format and the parameter keys (some of which are
+specific to `HyperSpy <https://hyperspy.org>`_). This format is often used in
+EDS/EDX experiments.
 
 The ``.rpl`` file lists the characteristics of the corresponding ``.raw`` file so
 that it can be loaded without human intervention. Thus, the reader parses a
@@ -19,116 +22,14 @@ It can also be used to exchange data with Bruker and used in
 combination with the :ref:`import-rpl` it is very useful for exporting data
 to Gatan's Digital Micrograph.
 
-The default encoding is latin-1. It is possible to set a different encoding
-using the encoding argument, e.g. when using HyperSpy to save a file:
-
-.. code-block:: python
-
-    >>> s.save('file.rpl', encoding = 'utf8')
-
-For ``.rpl`` files, ``hs.load`` takes the ``mmap_mode`` keyword argument enabling
-loading the file using a different mode (default is copy-on-write). However,
-note that lazy loading does not support in-place writing (i.e lazy loading and
-the "r+" mode are incompatible).
-
 .. note::
 
     This format may not provide information on the calibration.
     If so, you should add that after loading the file.
 
 
-Format description
-^^^^^^^^^^^^^^^^^^
-
-Images and spectral images or data cubes that are written in the
-(Lispix) raw file format are just a continuous string of numbers.
-
-Data cubes can be stored image by image, or spectrum by spectrum.
-Single images are stored row by row, vector cubes are stored row by row
-(each row spectrum by spectrum), image cubes are stored image by image.
-
-All of the numbers are in the same format, such as 16 bit signed integer,
-IEEE 8-byte real, 8-bit unsigned byte, etc.
-
-The ``.raw`` file should be accompanied by an ASCII text, tab delimited file with the
-same name and ``.rpl`` extension. This "raw parameter list" file lists the
-characteristics of the ``.raw`` file.
-
-Some of the following keys are specific to HyperSpy and will be ignored by other software.
-
-.. code::
-
-    ========================  =======  =================================================
-    Key                       Type     Description
-    ========================  =======  =================================================
-    width                     int      pixels per row
-    height                    int      number of rows
-    depth                     int      number of images or spectral pts
-    offset                    int      bytes to skip
-    data-type                 str      'signed', 'unsigned', or 'float'
-    data-length               str      bytes per pixel  '1', '2', '4', or '8'
-    byte-order                str      'big-endian', 'little-endian', or 'dont-care'
-    record-by                 str      'image', 'vector', or 'dont-care'
-    # X-ray keys:
-    ev-per-chan               int      optional, eV per channel
-    detector-peak-width-ev    int      optional, FWHM for the Mn K-alpha line
-    # HyperSpy-specific keys
-    depth-origin              int      energy offset in pixels
-    depth-scale               float    energy scaling (units per pixel)
-    depth-units               str      energy units, usually eV
-    depth-name                str      Name of the magnitude stored as depth
-    width-origin              int      column offset in pixels
-    width-scale               float    column scaling (units per pixel)
-    width-units               str      column units, usually nm
-    width-name                str      Name of the magnitude stored as width
-    height-origin             int      row offset in pixels
-    height-scale              float    row scaling (units per pixel)
-    height-units              str      row units, usually nm
-    height-name               str      Name of the magnitude stored as height
-    signal                    str      Type of the signal stored, e.g. EDS_SEM
-    convergence-angle         float    TEM convergence angle in mrad
-    collection-angle          float    EELS spectrometer collection semi-angle in mrad
-    beam-energy               float    TEM beam energy in keV
-    elevation-angle           float    Elevation angle of the EDS detector
-    azimuth-angle             float    Elevation angle of the EDS detector
-    live-time                 float    Live time per spectrum
-    energy-resolution         float    Resolution of the EDS (FHWM of MnKa)
-    tilt-stage                float    The tilt of the stage
-    date                      str      date in ISO 8601
-    time                      str      time in ISO 8601
-    title                     str      title of the signal to be stored
-    ========================  =======  =================================================
-
-
-Notes
-,,,,, 
-
-When ``'data-length'`` is 1, the ``'byte order'`` is not relevant as there is only
-one byte per datum, and ``'byte-order'`` should be ``'dont-care'``.
-
-When ``'depth'`` is 1, the file has one image, ``'record-by'`` is not relevant and
-should be ``'dont-care'``. For spectral images, ``'record-by'`` is 'vector'.
-For stacks of images, ``'record-by'`` is ``'image'``.
-
-Floating point numbers can be IEEE 4-byte, or IEEE 8-byte. Therefore if
-data-type is float, data-length MUST be 4 or 8.
-
-The ``.rpl`` file is read in a case-insensitive manner. However, when providing
-a dictionary as input, the keys MUST be lowercase.
-
-Comment lines, beginning with a semi-colon ``;`` are allowed anywhere.
-
-The first non-comment in the ``.rpl`` file line MUST have two column names:
-``'name_1'<TAB>'name_2'``; any name would do e.g. ``'key'<TAB>'value'``.
-
-Parameters can be in ANY order.
-
-In the ``.rpl`` file, the parameter name is followed by ONE tab (spaces are
-ignored) e.g.: ``'data-length'<TAB>'2'``
-
-In the ``.rpl`` file, other data and more tabs can follow the two items on
-each row, and are ignored.
-
-Other keys and values can be included and are ignored.
+API functions
+^^^^^^^^^^^^^
 
-Any number of spaces can go along with each tab.
+.. automodule:: rsciio.ripple
+   :members:
diff --git a/docs/supported_formats/usid.rst b/docs/supported_formats/usid.rst
index eef73b608..181e7e962 100644
--- a/docs/supported_formats/usid.rst
+++ b/docs/supported_formats/usid.rst
@@ -21,6 +21,8 @@ If you have any questions regarding this module, please consider
 `contacting <https://pycroscopy.github.io/pyUSID/contact.html>`_
 the developers of pyUSID.
 
+Also see the :ref:`hdf5-utils` for inspecting HDF5 files.
+
 .. Note::
 
     h5USID files can contain multiple USID datasets within the same file.
diff --git a/rsciio/__init__.py b/rsciio/__init__.py
index 6ecacf188..55792f923 100644
--- a/rsciio/__init__.py
+++ b/rsciio/__init__.py
@@ -35,6 +35,12 @@
                 "Blockfile",
                 "BrukerComposite",
                 "DigitalSurfSurface",
+                "netCDF",
+                "NeXus",
+                "Phenom",
+                "Protochips",
+                "PantaRhei",
+                "Ripple",
                 "Semper",
                 "TIFF",
                 "TVIPS",
diff --git a/rsciio/netcdf/__init__.py b/rsciio/netcdf/__init__.py
index e69de29bb..d4de92f67 100644
--- a/rsciio/netcdf/__init__.py
+++ b/rsciio/netcdf/__init__.py
@@ -0,0 +1,10 @@
+from ._api import file_reader
+
+
+__all__ = [
+    "file_reader",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/netcdf/api.py b/rsciio/netcdf/_api.py
similarity index 93%
rename from rsciio/netcdf/api.py
rename to rsciio/netcdf/_api.py
index 7bd114f24..2d691fb39 100644
--- a/rsciio/netcdf/api.py
+++ b/rsciio/netcdf/_api.py
@@ -21,8 +21,11 @@
 
 import numpy as np
 
+from rsciio.docstrings import FILENAME_DOC, RETURNS_DOC
+
 _logger = logging.getLogger(__name__)
 
+
 no_netcdf = False
 try:
     from netCDF4 import Dataset
@@ -80,6 +83,15 @@
 
 
 def file_reader(filename, *args, **kwds):
+    """
+    Read netCDF ``.nc`` files saved using the HyperSpy predecessor EELSlab.
+
+    Parameters
+    ----------
+    %s
+
+    %s
+    """
     if no_netcdf is True:
         raise ImportError(
             "No netCDF library installed. "
@@ -90,9 +102,11 @@ def file_reader(filename, *args, **kwds):
 
     ncfile = Dataset(filename, "r")
 
-    if hasattr(ncfile, "file_format_version"):
-        if ncfile.file_format_version == "EELSLab 0.1":
-            dictionary = nc_hyperspy_reader_0dot1(ncfile, filename, *args, **kwds)
+    if (
+        hasattr(ncfile, "file_format_version")
+        and ncfile.file_format_version == "EELSLab 0.1"
+    ):
+        dictionary = nc_hyperspy_reader_0dot1(ncfile, filename, *args, **kwds)
     else:
         ncfile.close()
         raise IOError("Unsupported netCDF file")
@@ -100,6 +114,9 @@ def file_reader(filename, *args, **kwds):
     return (dictionary,)
 
 
+file_reader.__doc__ %= (FILENAME_DOC, RETURNS_DOC)
+
+
 def nc_hyperspy_reader_0dot1(ncfile, filename, *args, **kwds):
     calibration_dict, acquisition_dict, treatments_dict = {}, {}, {}
     dc = ncfile.variables["data_cube"]
diff --git a/rsciio/nexus/__init__.py b/rsciio/nexus/__init__.py
index e69de29bb..5699aa972 100644
--- a/rsciio/nexus/__init__.py
+++ b/rsciio/nexus/__init__.py
@@ -0,0 +1,14 @@
+from ._api import (
+    file_reader,
+    file_writer,
+)
+
+
+__all__ = [
+    "file_reader",
+    "file_writer",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/nexus/api.py b/rsciio/nexus/_api.py
similarity index 88%
rename from rsciio/nexus/api.py
rename to rsciio/nexus/_api.py
index 00e640aa5..73af2e8d7 100644
--- a/rsciio/nexus/api.py
+++ b/rsciio/nexus/_api.py
@@ -1,4 +1,4 @@
-"""Nexus file reading, writing and inspection."""
+"""NeXus file reading and writing."""
 # -*- coding: utf-8 -*-
 # Copyright 2007-2022 The HyperSpy developers
 #
@@ -23,8 +23,13 @@
 import dask.array as da
 import h5py
 import numpy as np
-import pprint
 
+from rsciio.docstrings import (
+    FILENAME_DOC,
+    LAZY_DOC,
+    RETURNS_DOC,
+    SIGNAL_DOC,
+)
 from rsciio._hierarchical import get_signal_chunks
 from rsciio.hspy.api import overwrite_dataset
 from rsciio.utils.tools import DTBox
@@ -313,7 +318,7 @@ def _extract_hdf_dataset(group, dataset, lazy=False):
         if isinstance(data_key, bytes):
             data_key = data_key.decode()
         if dataset.split("/")[-1] == data_key:
-            return
+            return None
 
     nav_list = _get_nav_list(data, data.parent)
 
@@ -437,20 +442,10 @@ def file_reader(
 ):
     """Read NXdata class or hdf datasets from a file and return signal(s).
 
-    Note
-    ----
-    Loading all datasets can result in a large number of signals
-    Please review your datasets and use the dataset_key to target
-    the datasets of interest.
-    "keys" is a special keywords and prepended with "fix" in the metadata
-    structure to avoid any issues.
-
-    Datasets are all arrays with size>2 (arrays, lists)
-
     Parameters
     ----------
-    filename : str
-        Input filename
+    %s
+    %s
     dataset_key  : None, str, list of strings, default : None
         If None all datasets are returned.
         If a string or list of strings is provided only items
@@ -483,17 +478,22 @@ def file_reader(
         default is defined the file will be loaded according to
         the keyword options.
 
-    Returns
-    -------
-    dict : signal dictionary or list of signal dictionaries
+    %s
+
+    Note
+    ----
+    Loading all datasets can result in a large number of signals
+    Please review your datasets and use the dataset_key to target
+    the datasets of interest.
+    "keys" is a special keywords and prepended with "fix" in the metadata
+    structure to avoid any issues.
 
+    Datasets are all arrays with size>2 (arrays, lists)
 
     See Also
     --------
-    * :py:meth:`~.io_plugins.nexus.list_datasets_in_file`
-    * :py:meth:`~.io_plugins.nexus.read_metadata_from_file`
-
-
+    * :py:meth:`~.utils.hdf5.list_datasets_in_file`
+    * :py:meth:`~.utils.hdf5.read_metadata_from_file`
     """
     # search for NXdata sets...
 
@@ -619,6 +619,9 @@ def file_reader(
     return signal_dict_list
 
 
+file_reader.__doc__ %= (FILENAME_DOC, LAZY_DOC, RETURNS_DOC)
+
+
 def _is_linear_axis(data):
     """Check if the data is linearly incrementing.
 
@@ -1068,134 +1071,12 @@ def _write_nexus_attr(dictionary, group, skip_keys=None):
                     _write_nexus_attr(dictionary[key], group[key], skip_keys=skip_keys)
 
 
-def read_metadata_from_file(
-    filename, metadata_key=None, lazy=False, verbose=False, skip_array_metadata=False
-):
-    """Read the metadata from a nexus or hdf file.
-
-    This method iterates through the file and returns a dictionary of
-    the entries.
-    This is a convenience method to inspect a file for a value
-    rather than loading the file as a signal.
-
-    Parameters
-    ----------
-    filename : str
-        path of the file to read
-    metadata_key  : None,str or list_of_strings , default : None
-        None will return all datasets found including linked data.
-        Providing a string or list of strings will only return items
-        which contain the string(s).
-        For example, search_keys = ["instrument","Fe"] will return
-        hdf entries with "instrument" or "Fe" in their hdf path.
-    verbose: bool, default : False
-        Pretty Print the results to screen
-    skip_array_metadata : bool, default : False
-        Whether to skip loading array metadata. This is useful as a lot of
-        large array may be present in the metadata and it is redundant with
-        dataset itself.
-
-    Returns
-    -------
-    dict
-        Metadata dictionary.
-
-    See Also
-    --------
-    * :py:meth:`~.io_plugins.nexus.file_reader`
-    * :py:meth:`~.io_plugins.nexus.file_writer`
-    * :py:meth:`~.io_plugins.nexus.list_datasets_in_file`
-
-
-    """
-    search_keys = _check_search_keys(metadata_key)
-    fin = h5py.File(filename, "r")
-    # search for NXdata sets...
-    # strip out the metadata (basically everything other than NXdata)
-    stripped_metadata = _load_metadata(
-        fin, lazy=lazy, skip_array_metadata=skip_array_metadata
-    )
-    stripped_metadata = _find_search_keys_in_dict(
-        stripped_metadata, search_keys=search_keys
-    )
-    if verbose:
-        pprint.pprint(stripped_metadata)
-
-    fin.close()
-    return stripped_metadata
-
-
-def list_datasets_in_file(
-    filename, dataset_key=None, hardlinks_only=False, verbose=True
-):
-    """Read from a nexus or hdf file and return a list of the dataset paths.
-
-    This method is used to inspect the contents of a Nexus file.
-    The method iterates through group attributes and returns NXdata or
-    hdf datasets of size >=2 if they're not already NXdata blocks
-    and returns a list of the entries.
-    This is a convenience method to inspect a file to list datasets
-    present rather than loading all the datasets in the file as signals.
-
-    Parameters
-    ----------
-    filename : str
-        path of the file to read
-    dataset_key  : str, list of strings or None , default: None
-        If a str or list of strings is provided only return items whose
-        path contain the strings.
-        For example, dataset_key = ["instrument", "Fe"] will only return
-        hdf entries with "instrument" or "Fe" somewhere in their hdf path.
-    hardlinks_only : bool, default : False
-        If true any links (soft or External) will be ignored when loading.
-    verbose : boolean, default : True
-        Prints the results to screen
-
-
-    Returns
-    -------
-    list
-        list of paths to datasets
-
-
-    See Also
-    --------
-    * :py:meth:`~.io_plugins.nexus.file_reader`
-    * :py:meth:`~.io_plugins.nexus.file_writer`
-    * :py:meth:`~.io_plugins.nexus.read_metadata_from_file`
-
-
-    """
-    search_keys = _check_search_keys(dataset_key)
-    fin = h5py.File(filename, "r")
-    # search for NXdata sets...
-    # strip out the metadata (basically everything other than NXdata)
-    nexus_data_paths, hdf_dataset_paths = _find_data(
-        fin, search_keys=search_keys, hardlinks_only=hardlinks_only
-    )
-    if verbose:
-        if nexus_data_paths:
-            print("NXdata found")
-            for nxd in nexus_data_paths:
-                print(nxd)
-        else:
-            print("NXdata not found")
-        if hdf_dataset_paths:
-            print("Unique HDF datasets found")
-            for hdfd in hdf_dataset_paths:
-                print(hdfd, fin[hdfd].shape)
-        else:
-            print("No HDF datasets not found or data is captured by NXdata")
-    fin.close()
-    return nexus_data_paths, hdf_dataset_paths
-
-
 def _write_signal(signal, nxgroup, signal_name, **kwds):
     """Store the signal data as an NXdata dataset.
 
     Parameters
     ----------
-    signal : Hyperspy signal dictionary
+    signal : HyperSpy signal dictionary
     nxgroup : HDF group
         Entry at which to save signal data
     signal_name : str
@@ -1258,7 +1139,7 @@ def file_writer(
     *args,
     **kwds,
 ):
-    """Write the signal and metadata as a nexus file.
+    """Write the signal and metadata as a NeXus file.
 
     This will save the signal in NXdata format in the file.
     As the form of the metadata can vary and is not validated it will
@@ -1266,27 +1147,25 @@ def file_writer(
 
     Parameters
     ----------
-    filename : str
-        Path of the file to write
-    signals : signal or list of signals
-        Signal(s) to be written
-    save_original_metadata : bool , default : False
+    %s
+    %s
+    save_original_metadata : bool , default : True
           Option to save hyperspy.original_metadata with the signal.
-          A loaded Nexus file may have a large amount of data
-          when loaded which you may wish to omit on saving
+          A loaded NeXus file may have a large amount of data
+          when loaded which you may wish to omit on saving.
     skip_metadata_key : str or list of str, default : None
-        the key(s) to skip when it is saving original metadata. This is useful
-        when some metadata's keys are to be ignored.
+        The key(s) to skip when saving original metadata. This is useful
+        when some metadata keys should be ignored.
     use_default : bool , default : False
           Option to define the default dataset in the file.
           If set to True the signal or first signal in the list of signals
-          will be defined as the default (following Nexus v3 data rules).
+          will be defined as the default (following NeXus v3 data rules).
 
     See Also
     --------
-    * :py:meth:`~.io_plugins.nexus.file_reader`
-    * :py:meth:`~.io_plugins.nexus.list_datasets_in_file`
-    * :py:meth:`~.io_plugins.nexus.read_metadata_from_file`
+    * :py:meth:`~.nexus.file_reader`
+    * :py:meth:`~.utils.hdf5.list_datasets_in_file`
+    * :py:meth:`~.utils.hdf5.read_metadata_from_file`
 
     """
     if not isinstance(signals, list):
@@ -1348,3 +1227,6 @@ def file_writer(
                 # write the groups and structure
                 _write_nexus_groups(md, nxmd, **kwds)
                 _write_nexus_attr(md, nxmd)
+
+
+file_writer.__doc__ %= (FILENAME_DOC.replace("read", "write to"), SIGNAL_DOC)
diff --git a/rsciio/nexus/specifications.yaml b/rsciio/nexus/specifications.yaml
index b379182d8..e5c56d869 100644
--- a/rsciio/nexus/specifications.yaml
+++ b/rsciio/nexus/specifications.yaml
@@ -1,6 +1,6 @@
-name: Nexus
+name: NeXus
 name_aliases: []
-description: Read NXdata sets from Nexus files and metadata. Data and metadata can also be examined from general hdf5 files.
+description: Read NXdata sets from NeXus files and metadata. Data and metadata from general hdf5 files can also be examined.
 full_support: False
 file_extensions: [nxs, NXS]
 default_extension: 0
diff --git a/rsciio/phenom/__init__.py b/rsciio/phenom/__init__.py
index e69de29bb..d4de92f67 100644
--- a/rsciio/phenom/__init__.py
+++ b/rsciio/phenom/__init__.py
@@ -0,0 +1,10 @@
+from ._api import file_reader
+
+
+__all__ = [
+    "file_reader",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/phenom/api.py b/rsciio/phenom/_api.py
similarity index 97%
rename from rsciio/phenom/api.py
rename to rsciio/phenom/_api.py
index 41e2d48da..406ed6a80 100644
--- a/rsciio/phenom/api.py
+++ b/rsciio/phenom/_api.py
@@ -45,6 +45,8 @@
 import tifffile
 import xml.etree.ElementTree as ET
 
+from rsciio.docstrings import FILENAME_DOC, LAZY_DOC, RETURNS_DOC
+
 
 def element_symbol(z):
     elements = [
@@ -200,17 +202,16 @@ def __init__(self, pathname, block_size=1024 * 1024):
             raise Exception("not an ELID file")
 
         self._pathname = pathname
-        self._file = open(pathname, "rb")
-        self._decompressor = bz2.BZ2Decompressor()
-        self._block_size = block_size
-        (id, version) = struct.unpack("<4si", self._read(8))
-        if id != b"EID2":
-            raise Exception("not an ELID file")
-        if version > 2:
-            raise Exception("unsupported ELID format")
-        self._version = version
-        self.dictionaries = self._read_Project()
-        self._file.close()
+        with open(pathname, "rb") as self._file:
+            self._decompressor = bz2.BZ2Decompressor()
+            self._block_size = block_size
+            (id, version) = struct.unpack("<4si", self._read(8))
+            if id != b"EID2":
+                raise Exception("not an ELID file")
+            if version > 2:
+                raise Exception("unsupported ELID format")
+            self._version = version
+            self.dictionaries = self._read_Project()
 
     def _read(self, size=1):
         data = self._decompressor.decompress(b"", size)
@@ -917,6 +918,20 @@ def _read_Project(self):
         return [dict for dict in dictionaries if dict]
 
 
-def file_reader(filename, log_info=False, lazy=False, **kwds):
+def file_reader(filename, lazy=False, **kwds):
+    """
+    Read a Phenom ``.elid`` file from the software Element Identification (>v3.8.0)
+    used by the Thermo Fisher Scientific Phenom desktop SEMs.
+
+    Parameters
+    ----------
+    %s
+    %s
+
+    %s
+    """
     reader = ElidReader(filename)
     return reader.dictionaries
+
+
+file_reader.__doc__ %= (FILENAME_DOC, LAZY_DOC, RETURNS_DOC)
diff --git a/rsciio/protochips/__init__.py b/rsciio/protochips/__init__.py
index e69de29bb..d4de92f67 100644
--- a/rsciio/protochips/__init__.py
+++ b/rsciio/protochips/__init__.py
@@ -0,0 +1,10 @@
+from ._api import file_reader
+
+
+__all__ = [
+    "file_reader",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/protochips/api.py b/rsciio/protochips/_api.py
similarity index 96%
rename from rsciio/protochips/api.py
rename to rsciio/protochips/_api.py
index f959790fe..f4f80ab4a 100644
--- a/rsciio/protochips/api.py
+++ b/rsciio/protochips/_api.py
@@ -23,6 +23,8 @@
 import warnings
 import logging
 
+from rsciio.docstrings import FILENAME_DOC, RETURNS_DOC
+
 
 _logger = logging.getLogger(__name__)
 
@@ -37,10 +39,23 @@
 
 
 def file_reader(filename, *args, **kwds):
+    """
+    Read a Protochips ``.csv`` logfile containing data for heater, biasing or gas
+    cell experiments using an in-situ holder.
+
+    Parameters
+    ----------
+    %s
+
+    %s
+    """
     csv_file = ProtochipsCSV(filename)
     return _protochips_log_reader(csv_file)
 
 
+file_reader.__doc__ %= (FILENAME_DOC, RETURNS_DOC)
+
+
 def _protochips_log_reader(csv_file):
     csvs = []
     for key in csv_file.logged_quantity_name_list:
diff --git a/rsciio/prz/__init__.py b/rsciio/prz/__init__.py
index e69de29bb..61acf603f 100644
--- a/rsciio/prz/__init__.py
+++ b/rsciio/prz/__init__.py
@@ -0,0 +1,11 @@
+from ._api import file_reader, file_writer
+
+
+__all__ = [
+    "file_reader",
+    "file_writer",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/prz/api.py b/rsciio/prz/_api.py
similarity index 98%
rename from rsciio/prz/api.py
rename to rsciio/prz/_api.py
index 3d040f4a6..0494e2cbf 100644
--- a/rsciio/prz/api.py
+++ b/rsciio/prz/_api.py
@@ -24,6 +24,7 @@
 
 import numpy as np
 
+from rsciio.docstrings import FILENAME_DOC, RETURNS_DOC, SIGNAL_DOC
 from rsciio.utils.tools import DTBox
 
 _logger = logging.getLogger(__name__)
@@ -37,13 +38,34 @@
 
 
 def file_reader(filename, **kwds):
+    """
+    Read a PantaRhei ``.prz`` file.
+
+    Parameters
+    ----------
+    %s
+
+    %s
+    """
     prz_file = np.load(filename, allow_pickle=True)
     data = prz_file["data"]
     meta_data = prz_file["meta_data"][0]
     return import_pr(data, meta_data, filename)
 
 
+file_reader.__doc__ %= (FILENAME_DOC, RETURNS_DOC)
+
+
 def file_writer(filename, signal, **kwds):
+    """
+    Write signal to PantaRhei ``.prz`` format.
+
+    Parameters
+    ----------
+    %s
+    %s
+    """
+
     data, meta_data = export_pr(signal=signal)
     with open(filename, mode="wb") as f:
         # use open file to avoid numpy adding the npz extension
@@ -56,6 +78,9 @@ def file_writer(filename, signal, **kwds):
         )
 
 
+file_writer.__doc__ %= (FILENAME_DOC.replace("read", "write to"), SIGNAL_DOC)
+
+
 def import_pr(data, meta_data, filename=None):
     """Converts metadata from PantaRhei to hyperspy format, and corrects
     the order of data axes if needed.
diff --git a/rsciio/prz/specifications.yaml b/rsciio/prz/specifications.yaml
index e3ad4c7f5..b4cea8ba0 100644
--- a/rsciio/prz/specifications.yaml
+++ b/rsciio/prz/specifications.yaml
@@ -1,5 +1,5 @@
 name: PantaRhei
-name_aliases: []
+name_aliases: [PRZ]
 description: File format used by CEOS PantaRhei, based on simple numpy arrays and dictionaries.
 full_support: False
 file_extensions: [prz, PRZ]
diff --git a/rsciio/ripple/__init__.py b/rsciio/ripple/__init__.py
index e69de29bb..61acf603f 100644
--- a/rsciio/ripple/__init__.py
+++ b/rsciio/ripple/__init__.py
@@ -0,0 +1,11 @@
+from ._api import file_reader, file_writer
+
+
+__all__ = [
+    "file_reader",
+    "file_writer",
+]
+
+
+def __dir__():
+    return sorted(__all__)
diff --git a/rsciio/ripple/api.py b/rsciio/ripple/_api.py
similarity index 78%
rename from rsciio/ripple/api.py
rename to rsciio/ripple/_api.py
index 54de65d46..55b55804d 100644
--- a/rsciio/ripple/api.py
+++ b/rsciio/ripple/_api.py
@@ -28,6 +28,12 @@
 
 import numpy as np
 
+from rsciio.docstrings import (
+    FILENAME_DOC,
+    LAZY_DOC,
+    RETURNS_DOC,
+    SIGNAL_DOC,
+)
 from rsciio.version import __version__
 from rsciio.utils.tools import DTBox
 
@@ -190,14 +196,14 @@ def read_raw(rpl_info, fp, mmap_mode="c"):
         A dictionary containing the keywords as parsed by read_rpl
     fp:
     mmap_mode: {None, 'r+', 'r', 'w+', 'c'}, optional
-    If not None, then memory-map the file, using the given mode
-    (see `numpy.memmap`).  The mode has no effect for pickled or
-    zipped files.
-    A memory-mapped array is stored on disk, and not directly loaded
-    into memory.  However, it can be accessed and sliced like any
-    ndarray.  Memory mapping is especially useful for accessing
-    small fragments of large files without reading the entire file
-    into memory.
+        If not None, then memory-map the file, using the given mode
+        (see `numpy.memmap`). The mode has no effect for pickled or
+        zipped files.
+        A memory-mapped array is stored on disk, and not directly loaded
+        into memory.  However, it can be accessed and sliced like any
+        ndarray.  Memory mapping is especially useful for accessing
+        small fragments of large files without reading the entire file
+        into memory.
 
 
     """
@@ -247,109 +253,33 @@ def read_raw(rpl_info, fp, mmap_mode="c"):
 def file_reader(
     filename, rpl_info=None, encoding="latin-1", mmap_mode="c", *args, **kwds
 ):
-    """Parses a Lispix (https://www.nist.gov/services-resources/software/lispix) ripple (.rpl) file
-    and reads the data from the corresponding raw (.raw) file;
-    or, read a raw file if the dictionary rpl_info is provided.
-
-    This format is often uses in EDS/EDX experiments.
-
-    Images and spectral images or data cubes that are written in the
-    (Lispix) raw file format are just a continuous string of numbers.
-
-    Data cubes can be stored image by image, or spectrum by spectrum.
-    Single images are stored row by row, vector cubes are stored row by row
-    (each row spectrum by spectrum), image cubes are stored image by image.
-
-    All of the numbers are in the same format, such as 16 bit signed integer,
-    IEEE 8-byte real, 8-bit unsigned byte, etc.
-
-    The "raw" file should be accompanied by a text file with the same name and
-    ".rpl" extension. This file lists the characteristics of the raw file so
-    that it can be loaded without human intervention.
-
-    Alternatively, a dictionary 'rpl_info' containing the information can
-    be given.
-
-    Some keys are specific to HyperSpy and will be ignored by other software.
-
-    RPL stands for "Raw Parameter List", an ASCII text, tab delimited file in
-    which HyperSpy reads the image parameters for a raw file.
-
-    ========================  =======  =================================================
-    Key                       Type     Description
-    ========================  =======  =================================================
-    width                     int      pixels per row
-    height                    int      number of rows
-    depth                     int      number of images or spectral pts
-    offset                    int      bytes to skip
-    data-type                 str      'signed', 'unsigned', or 'float'
-    data-length               str      bytes per pixel  '1', '2', '4', or '8'
-    byte-order                str      'big-endian', 'little-endian', or 'dont-care'
-    record-by                 str      'image', 'vector', or 'dont-care'
-    # X-ray keys:
-    ev-per-chan               int      optional, eV per channel
-    detector-peak-width-ev    int      optional, FWHM for the Mn K-alpha line
-    # HyperSpy-specific keys
-    depth-origin              int      energy offset in pixels
-    depth-scale               float    energy scaling (units per pixel)
-    depth-units               str      energy units, usually eV
-    depth-name                str      Name of the magnitude stored as depth
-    width-origin              int      column offset in pixels
-    width-scale               float    column scaling (units per pixel)
-    width-units               str      column units, usually nm
-    width-name                str      Name of the magnitude stored as width
-    height-origin             int      row offset in pixels
-    height-scale              float    row scaling (units per pixel)
-    height-units              str      row units, usually nm
-    height-name               str      Name of the magnitude stored as height
-    signal                    str      Type of the signal stored, e.g. EDS_SEM
-    convergence-angle         float    TEM convergence angle in mrad
-    collection-angle          float    EELS spectrometer collection semi-angle in mrad
-    beam-energy               float    TEM beam energy in keV
-    elevation-angle           float    Elevation angle of the EDS detector
-    azimuth-angle             float    Elevation angle of the EDS detector
-    live-time                 float    Live time per spectrum
-    energy-resolution         float    Resolution of the EDS (FHWM of MnKa)
-    tilt-stage                float    The tilt of the stage
-    date                      str      date in ISO 8601
-    time                      str      time in ISO 8601
-    title                     str      title of the signal to be stored
-    ========================  =======  =================================================
-
-
-    NOTE
-    ----
-
-    When 'data-length' is 1, the 'byte order' is not relevant as there is only
-    one byte per datum, and 'byte-order' should be 'dont-care'.
-
-    When 'depth' is 1, the file has one image, 'record-by' is not relevant and
-    should be 'dont-care'. For spectral images, 'record-by' is 'vector'.
-    For stacks of images, 'record-by' is 'image'.
-
-    Floating point numbers can be IEEE 4-byte, or IEEE 8-byte. Therefore if
-    data-type is float, data-length MUST be 4 or 8.
-
-    The rpl file is read in a case-insensitive manner. However, when providing
-    a dictionary as input, the keys MUST be lowercase.
-
-    Comment lines, beginning with a semi-colon ';' are allowed anywhere.
-
-    The first non-comment in the rpl file line MUST have two column names:
-    'name_1'<TAB>'name_2'; any name would do e.g. 'key'<TAB>'value'.
-
-    Parameters can be in ANY order.
-
-    In the rpl file, the parameter name is followed by ONE tab (spaces are
-    ignored) e.g.: 'data-length'<TAB>'2'
-
-    In the rpl file, other data and more tabs can follow the two items on
-    each row, and are ignored.
-
-    Other keys and values can be included and are ignored.
-
-    Any number of spaces can go along with each tab.
+    """Parses a Lispix (https://www.nist.gov/services-resources/software/lispix)
+    ripple (.rpl) file and reads the data from the corresponding raw (.raw) file;
+    or, read a raw file if the dictionary ``rpl_info`` is provided.
 
+    Parameters
+    ----------
+    %s
+    %s
+    rpl_info : dict, Default=None
+        A dictionary containing the keywords in order to read a ``.raw`` file
+        without corresponding ``.rpl`` file. If ``None``, the keywords are parsed
+        automatically from the ``.rpl`` file.
+    enconding : str, optional
+        The encoding used to read the content of the ``rpl`` file. The default
+        encoding is ``latin-1``. Different file encodings, such as ``utf8`` can be
+        set via this argument.
+    mmap_mode : str, optional
+        Default is copy-on-write ``"c"``, but different modes can be set. However,
+        note that lazy loading does not support in-place writing (i.e lazy loading
+        and the ``"r+"`` mode are incompatible). The mode has no effect for pickled or
+        zipped files. A memory-mapped array is stored on disk, and not directly loaded
+        into memory.  However, it can be accessed and sliced like any
+        ndarray.  Memory mapping is especially useful for accessing
+        small fragments of large files without reading the entire file
+        into memory.
+
+    %s
     """
 
     if not rpl_info:
@@ -536,7 +466,22 @@ def file_reader(
     ]
 
 
+file_reader.__doc__ %= (FILENAME_DOC, LAZY_DOC, RETURNS_DOC)
+
+
 def file_writer(filename, signal, encoding="latin-1", *args, **kwds):
+    """Writes a Lispix (https://www.nist.gov/services-resources/software/lispix)
+    ripple (.rpl) file and saves the data in a corresponding raw (.raw) file.
+
+    Parameters
+    ----------
+    %s
+    %s
+    enconding : str, optional
+        The encoding used to write the content of the ``rpl`` file. The default
+        encoding is ``latin-1``. Different file encodings, such as ``utf8`` can be
+        set via this argument.
+    """
 
     # Set the optional keys to None
     ev_per_chan = None
@@ -661,6 +606,9 @@ def file_writer(filename, signal, encoding="latin-1", *args, **kwds):
     write_raw(filename, signal, record_by, sig_axes, nav_axes)
 
 
+file_writer.__doc__ %= (FILENAME_DOC.replace("read", "write to"), SIGNAL_DOC)
+
+
 def write_rpl(filename, keys_dictionary, encoding="ascii"):
     with codecs.open(filename, "w", encoding=encoding, errors="ignore") as f:
         f.write(";File created by RosettaSciIO version %s\n" % __version__)
diff --git a/rsciio/utils/__init__.py b/rsciio/utils/__init__.py
index e69de29bb..8b1378917 100644
--- a/rsciio/utils/__init__.py
+++ b/rsciio/utils/__init__.py
@@ -0,0 +1,1 @@
+
diff --git a/rsciio/utils/hdf5.py b/rsciio/utils/hdf5.py
new file mode 100644
index 000000000..d76829bf7
--- /dev/null
+++ b/rsciio/utils/hdf5.py
@@ -0,0 +1,156 @@
+"""HDF5 file inspection."""
+# -*- coding: utf-8 -*-
+# Copyright 2007-2022 The HyperSpy developers
+#
+# This file is part of RosettaSciIO.
+#
+# RosettaSciIO is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# RosettaSciIO is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with RosettaSciIO. If not, see <https://www.gnu.org/licenses/#GPL>.
+#
+
+import h5py
+import pprint
+
+from rsciio.docstrings import FILENAME_DOC
+from rsciio.nexus._api import (
+    _check_search_keys,
+    _load_metadata,
+    _find_search_keys_in_dict,
+    _find_data,
+)
+
+
+def read_metadata_from_file(
+    filename, metadata_key=None, lazy=False, verbose=False, skip_array_metadata=False
+):
+    """Read the metadata from a NeXus or ``.hdf`` file.
+
+    This method iterates through the hdf5 file and returns a dictionary of
+    the entries.
+    This is a convenience method to inspect a file for a value
+    rather than loading the file as a signal.
+
+    Parameters
+    ----------
+    %s
+    metadata_key  : None,str or list_of_strings , default : None
+        None will return all datasets found including linked data.
+        Providing a string or list of strings will only return items
+        which contain the string(s).
+        For example, search_keys = ["instrument","Fe"] will return
+        hdf entries with "instrument" or "Fe" in their hdf path.
+    verbose: bool, default : False
+        Pretty Print the results to screen
+    skip_array_metadata : bool, default : False
+        Whether to skip loading array metadata. This is useful as a lot of
+        large array may be present in the metadata and it is redundant with
+        dataset itself.
+
+    Returns
+    -------
+    dict
+        Metadata dictionary.
+
+    See Also
+    --------
+    * :py:meth:`~.io_plugins.nexus.file_reader`
+    * :py:meth:`~.io_plugins.nexus.file_writer`
+    * :py:meth:`~.io_plugins.nexus.list_datasets_in_file`
+
+
+    """
+    search_keys = _check_search_keys(metadata_key)
+    fin = h5py.File(filename, "r")
+    # search for NXdata sets...
+    # strip out the metadata (basically everything other than NXdata)
+    stripped_metadata = _load_metadata(
+        fin, lazy=lazy, skip_array_metadata=skip_array_metadata
+    )
+    stripped_metadata = _find_search_keys_in_dict(
+        stripped_metadata, search_keys=search_keys
+    )
+    if verbose:
+        pprint.pprint(stripped_metadata)
+
+    fin.close()
+    return stripped_metadata
+
+
+read_metadata_from_file.__doc__ %= FILENAME_DOC
+
+
+def list_datasets_in_file(
+    filename, dataset_key=None, hardlinks_only=False, verbose=True
+):
+    """Read from a NeXus or ``.hdf`` file and return a list of the dataset paths.
+
+    This method is used to inspect the contents of an hdf5 file.
+    The method iterates through group attributes and returns NXdata or
+    hdf datasets of size >=2 if they're not already NXdata blocks
+    and returns a list of the entries.
+    This is a convenience method to inspect a file to list datasets
+    present rather than loading all the datasets in the file as signals.
+
+    Parameters
+    ----------
+    %s
+    dataset_key  : str, list of strings or None , default: None
+        If a str or list of strings is provided only return items whose
+        path contain the strings.
+        For example, dataset_key = ["instrument", "Fe"] will only return
+        hdf entries with "instrument" or "Fe" somewhere in their hdf path.
+    hardlinks_only : bool, default : False
+        If true any links (soft or External) will be ignored when loading.
+    verbose : boolean, default : True
+        Prints the results to screen
+
+
+    Returns
+    -------
+    list
+        List of paths to datasets.
+
+
+    See Also
+    --------
+    * :py:meth:`~.io_plugins.nexus.file_reader`
+    * :py:meth:`~.io_plugins.nexus.file_writer`
+    * :py:meth:`~.io_plugins.nexus.read_metadata_from_file`
+
+
+    """
+    search_keys = _check_search_keys(dataset_key)
+    fin = h5py.File(filename, "r")
+    # search for NXdata sets...
+    # strip out the metadata (basically everything other than NXdata)
+    nexus_data_paths, hdf_dataset_paths = _find_data(
+        fin, search_keys=search_keys, hardlinks_only=hardlinks_only
+    )
+    if verbose:
+        if nexus_data_paths:
+            print("NXdata found")
+            for nxd in nexus_data_paths:
+                print(nxd)
+        else:
+            print("NXdata not found")
+        if hdf_dataset_paths:
+            print("Unique HDF datasets found")
+            for hdfd in hdf_dataset_paths:
+                print(hdfd, fin[hdfd].shape)
+        else:
+            print("No HDF datasets not found or data is captured by NXdata")
+    fin.close()
+    return nexus_data_paths, hdf_dataset_paths
+
+
+list_datasets_in_file.__doc__ %= FILENAME_DOC


Test patch:
diff --git a/rsciio/tests/test_import.py b/rsciio/tests/test_import.py
index a3157b8c1..723a1206f 100644
--- a/rsciio/tests/test_import.py
+++ b/rsciio/tests/test_import.py
@@ -98,6 +98,8 @@ def test_format_name_aliases():
 
 
 def test_dir_plugins():
+    # Once all plugins use the new api format, write an automated test that
+    # iterates over all plugins
 
     from rsciio import bruker
 
@@ -109,6 +111,34 @@ def test_dir_plugins():
 
     assert dir(blockfile) == ["file_reader", "file_writer"]
 
+    from rsciio import netcdf
+
+    assert dir(netcdf) == ["file_reader"]
+
+    from rsciio import nexus
+
+    assert dir(nexus) == ["file_reader", "file_writer"]
+
+    from rsciio import phenom
+
+    assert dir(phenom) == ["file_reader"]
+
+    from rsciio import protochips
+
+    assert dir(protochips) == ["file_reader"]
+
+    from rsciio import prz
+
+    assert dir(prz) == ["file_reader", "file_writer"]
+
+    from rsciio import ripple
+
+    assert dir(ripple) == ["file_reader", "file_writer"]
+
+    from rsciio import semper_unf
+
+    assert dir(semper_unf) == ["file_reader", "file_writer"]
+
     from rsciio import semper_unf
 
     assert dir(semper_unf) == ["file_reader", "file_writer"]
diff --git a/rsciio/tests/test_nexus_hdf.py b/rsciio/tests/test_nexus_hdf.py
index 996166b79..fd47f209d 100644
--- a/rsciio/tests/test_nexus_hdf.py
+++ b/rsciio/tests/test_nexus_hdf.py
@@ -28,15 +28,14 @@
 
 from hyperspy.exceptions import VisibleDeprecationWarning
 
-from rsciio.nexus.api import (
+from rsciio.nexus import file_writer
+from rsciio.utils.hdf5 import list_datasets_in_file, read_metadata_from_file
+from rsciio.nexus._api import (
     _byte_to_string,
     _fix_exclusion_keys,
     _is_int,
     _is_numeric_data,
-    file_writer,
-    list_datasets_in_file,
     _get_nav_list,
-    read_metadata_from_file,
     _getlink,
     _check_search_keys,
     _parse_from_file,
diff --git a/rsciio/tests/test_protochips.py b/rsciio/tests/test_protochips.py
index ff0ccafc7..0bb59afc0 100644
--- a/rsciio/tests/test_protochips.py
+++ b/rsciio/tests/test_protochips.py
@@ -23,7 +23,7 @@
 
 hs = pytest.importorskip("hyperspy.api", reason="hyperspy not installed")
 
-from rsciio.protochips.api import ProtochipsCSV, invalid_file_error
+from rsciio.protochips._api import ProtochipsCSV, invalid_file_error
 
 testdirpath = os.path.dirname(__file__)
 dirpath = os.path.join(testdirpath, "protochips_data")
diff --git a/rsciio/tests/test_ripple.py b/rsciio/tests/test_ripple.py
index 1a7c2df90..15c6658e1 100644
--- a/rsciio/tests/test_ripple.py
+++ b/rsciio/tests/test_ripple.py
@@ -8,7 +8,7 @@
 
 hs = pytest.importorskip("hyperspy.api", reason="hyperspy not installed")
 
-from rsciio.ripple import api as ripple
+from rsciio.ripple import _api as ripple
 
 # Tuple of tuples (data shape, signal_dimensions)
 SHAPES_SDIM = (