diff --git a/docs/source/user-guide/io.ipynb b/docs/source/user-guide/io.ipynb index e357a830..d24f7e94 100644 --- a/docs/source/user-guide/io.ipynb +++ b/docs/source/user-guide/io.ipynb @@ -1,251 +1,388 @@ { - "cells": [ - { - "cell_type": "raw", - "metadata": { - "editable": true, - "raw_mimetype": "text/restructuredtext", - "slideshow": { - "slide_type": "" + "cells": [ + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "Reading and writing data\n", + "========================\n", + "\n", + "In ERLabPy, most data are represented as :class:`xarray.Dataset` objects or\n", + ":class:`xarray.DataArray` objects. :class:`xarray.DataArray` are similar to\n", + "waves in igor pro, but are much more flexible. Apposed to the maximum of 4\n", + "dimensions in igor pro, :class:`xarray.DataArray` can have as many dimensions as\n", + "you want (up to 64). Another advantage is that the coordinates of the dimensions\n", + "do not have to be evenly spaced. In fact, they are not limited to numbers, but\n", + "can be any type of data, such as date and time representations.\n", + "\n", + "This guide will introduce you to reading and writing data from and to various\n", + "file formats, and how to implement a custom reader for a experimental setup.\n", + "\n", + "Skip to the `corresponding section <#Loading-ARPES-data>`_ for guides on loading ARPES data.\n", + "\n", + "Reading data from Igor Pro\n", + "--------------------------\n", + "\n", + ".. warning::\n", + "\n", + " Loading waves from complex ``.pxp`` files may fail or produce unexpected\n", + " results. It is recommended to export the waves to a ``.ibw`` file to load\n", + " them in ERLabPy. If you encounter any problems, please let us know by opening\n", + " an issue.\n", + "\n", + "\n", + "ERLabPy can read ``.ibw``, ``.pxt``, ``.pxp``, and HDF5 files exported from Igor\n", + "Pro by using the following functions in :mod:`erlab.io.igor`:\n", + "\n", + ".. list-table::\n", + " :widths: 50 50\n", + " :header-rows: 1\n", + "\n", + " * - File extension\n", + " - Function\n", + " * - ``.ibw``\n", + " - :func:`erlab.io.igor.load_wave`\n", + " * - ``.pxt``, ``.pxp``\n", + " - :func:`erlab.io.igor.load_experiment`\n", + " * - ``.h5``\n", + " - :func:`erlab.io.igor.load_igor_hdf5`\n", + "\n", + "For easy access, the first two functions are also available in the :mod:`erlab.io` namespace.\n", + "\n", + ".. Note::\n", + " \n", + " Internally, the `igor2 `_ package is used to read the data." + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "Reading arbitrary formats\n", + "-------------------------\n", + "\n", + "Spreadsheet data can be read using :func:`pandas.read_csv` or :func:`pandas.read_excel`. The resulting DataFrame can be converted to an xarray object using :meth:`pandas.DataFrame.to_xarray` or :meth:`xarray.Dataset.from_dataframe`.\n", + "\n", + "For reading HDF5 files with arbitrary groups and metadata, explore each group using `h5netcdf `_. Loading into an xarray object can be done using :func:`xarray.open_dataset` or :func:`xarray.open_mfdataset` by supplying the ``group`` argument. For an example that handles complex HDF5 groups, see the implementation of :meth:`erlab.io.plugins.ssrl52.SSRL52Loader.load_single`.\n", + "\n", + "FITS files can be read with `astropy\n", + "`_." + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "Writing data\n", + "------------\n", + "\n", + "Since Python interpreters do not save their state, it is important to save your data in a format that can be easily read and written.\n", + "\n", + "While it is possible to save and load entire Python interpreter sessions using `pickle` or the more versatile `dill `_, it is out of the scope of this guide. Instead, we recommend saving your data in a format that is easy to read and write, such as HDF5 or NetCDF. These formats are supported by many programming languages and are optimized for fast read and write operations.\n", + "\n", + "To save and load :mod:`xarray` objects, see the :mod:`xarray` documentation on `I/O operations `_. ERLabPy offers convenience functions :func:`load_hdf5 ` and :func:`save_as_hdf5 ` for loading and saving xarray objects from and to HDF5 files.\n", + "\n", + ".. note::\n", + "\n", + " As an experimental feature, :func:`save_as_hdf5 ` can\n", + " save certain :class:`xarray.DataArray`\\ s in a format that is compatible with the Igor\n", + " Pro HDF5 loader. An `accompanying Igor procedure `_ is\n", + " available in the repository. If loading in Igor Pro fails, try saving again\n", + " with all attributes removed.\n", + "\n" + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "Loading ARPES data\n", + "------------------\n", + "\n", + "ERLabPy's data loading framework consists of various plugins, or *loaders*, each\n", + "designed to load data from a different beamline or laboratory. Each loader is a\n", + "class that has a ``load`` method that takes a file path or sequence number as\n", + "input and returns the corresponding data.\n", + "\n", + "Let's see the list of loaders available by default:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import erlab.io\n", + "\n", + "erlab.io.loaders" + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "You can access each loader by its name or alias, both as an attribute or an\n", + "item. For example, to access the loader for the ALS beamline 4.0.3, you can use\n", + "any of the following:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "erlab.io.loaders[\"merlin\"]\n", + "erlab.io.loaders[\"bl403\"]\n", + "erlab.io.loaders.merlin\n", + "erlab.io.loaders.bl403" + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "For a single session, it is very common to use only one type of loader for a\n", + "single folder with all your data. Hence, the module provides a way to set a\n", + "default loader for a session. This is done with :func:`erlab.io.set_loader`. The\n", + "same can be done for the data directory using :func:`erlab.io.set_data_dir`.\n", + "\n", + "The main function used to load ARPES data is :func:`erlab.io.load`.\n", + "\n", + "\n", + ".. admonition:: Work in Progress\n", + " :class: warning\n", + "\n", + " This guide is a work in progress. In the meantime, check out the examples at\n", + " :doc:`../erlab.io`\\ .\n" + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [], + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "Implementing a data loader plugin\n", + "---------------------------------\n", + "\n", + "It is easy to add new loaders to the framework. Any class that subclasses\n", + ":class:`LoaderBase ` is automatically registered\n", + "as a loader upon its definition. The class must have a valid ``name`` attribute,\n", + "which is used to access the loader.\n", + "\n", + "If the ``name`` attribute is prefixed with an underscore, the loader is not\n", + "registered as a loader. This is useful for abstract classes that are not meant\n", + "to be used directly. One example is :class:`DA30Loader\n", + "`, which serves as a base class for\n", + "implementing loaders for setups using the Scienta Omicron DA30 hemispherical\n", + "analyzer with ``SES.exe``.\n", + "\n", + "ARPES data from a single experiment are usually stored in one folder, with files\n", + "that look like ``file_0001.h5``, ``file_0002.h5``, etc. If the naming scheme\n", + "does not deviate from this pattern, only two methods need to be implemented:\n", + ":meth:`identify ` and\n", + ":meth:`load_single `. The following\n", + "flowchart shows the process of loading data from a single scan, given either a\n", + "file path or a sequence number:\n", + "\n", + ".. image:: ../images/flowchart_single.pdf\n", + " :align: center\n", + " \n", + "Here, :meth:`identify ` is given an\n", + "integer sequence number(``identifier``) and the path to the data\n", + "folder(``data_dir``), and returns the full path to the corresponding data file. \n", + "\n", + "The method :meth:`load_single ` is\n", + "given a full path to a single data file and must return the data as an\n", + ":class:`xarray.DataArray` or a :class:`xarray.Dataset`. If the data cannot be\n", + "combined into a single object, the method can also return a list of\n", + ":class:`xarray.DataArray` objects.\n", + "\n", + "If only all data formats were as simple as this! Unfortunately, there are some\n", + "setups where data for a single scan is saved over multiple files. In this case,\n", + "the files will look like ``file_0001_0001.h5``, ``file_0001_0002.h5``, etc. For\n", + "these kinds of setups, an additional method :meth:`infer_index\n", + "` must be implemented. The following\n", + "flowchart shows the process of loading data from multiple files:\n", + "\n", + ".. image:: ../images/flowchart_multiple.pdf\n", + " :align: center\n", + "\n", + "In this case, the method :meth:`identify\n", + "` should resolve *all* files that\n", + "belong to the given sequence number, and return a list of file paths along with\n", + "a dictionary of corresponding coordinates.\n", + "\n", + "The method :meth:`infer_index ` is\n", + "given a bare file name (without the extension and path) like and must return the\n", + "sequence number of the scan. For example, given the file name\n", + "``file_0003_0123``, the method should return ``1``.\n", + "\n", + "Consider a setup that saves data into a ``.csv`` file named ``data_0001.csv``\n", + "and so on. A bare minimum implementation of a loader for the setup will look\n", + "something like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import pandas as pd\n", + "from erlab.io.dataloader import LoaderBase\n", + "\n", + "\n", + "class MyLoader(LoaderBase):\n", + " name = \"my_loader\"\n", + " aliases = None\n", + " name_map = {}\n", + " coordinate_attrs = {}\n", + " additional_attrs = {\"information\": \"any metadata you want to load with the data\"}\n", + " skip_validate = False\n", + " always_single = True\n", + "\n", + " def identify(self, num, data_dir):\n", + " file = os.path.join(data_dir, f\"data_{str(num).zfill(4)}.csv\")\n", + " return [file], {}\n", + "\n", + " def load_single(self, file_path):\n", + " return pd.read_csv(file_path).to_xarray()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "erlab.io.loaders" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "erlab.io.loaders[\"my_loader\"]" + ] + }, + { + "cell_type": "raw", + "metadata": { + "editable": true, + "raw_mimetype": "text/restructuredtext", + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "We can see that the loader has been registered.\n", + "\n", + "See :doc:`../generated/erlab.io.dataloader` for information about each\n", + "attribute, and the values and types of the expected outputs. The implementation\n", + "of existing loaders in the :mod:`erlab.io.plugins` module is a good starting\n", + "point; see the `source code on github\n", + "`_." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } }, - "tags": [] - }, - "source": [ - "Loading data\n", - "============\n", - "\n", - "ARPES data are loaded as :class:`xarray.Dataset` objects or :class:`xarray.DataArray` objects. :class:`xarray.DataArray` are similar to waves in igor pro, but are much more flexible. Apposed to the maximum of 4 dimensions in igor pro, :class:`xarray.DataArray` can have as many dimensions as you want (up to 64). Another advantage is that the coordinates of the dimensions do not have to be evenly spaced. In fact, they are not limited to numbers, but can be any type of data, such as date and time representations.\n", - "\n", - ".. admonition:: Work in Progress\n", - " :class: warning\n", - "\n", - " This guide is a work in progress. In the meantime, check out the examples at\n", - " :doc:`../erlab.io`\\ .\n", - "\n", - "ERLabPy's data loading framework consists of various plugins, or *loaders*, each\n", - "designed to load data from a different beamline or laboratory. Each loader is a\n", - "class that has a ``load`` method that takes a file path or sequence number as\n", - "input and returns the corresponding data.\n", - "\n", - "Let's see the list of loaders available by default:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "editable": true, - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "outputs": [ - { - "data": { - "text/html": [ - "
NameAliasesLoader class
krissKRISSerlab.io.plugins.kriss.KRISSLoader
merlinALS_BL4, als_bl4, BL403, bl403erlab.io.plugins.merlin.BL403Loader
ssrlssrl52, bl5-2erlab.io.plugins.ssrl52.SSRL52Loader
" - ], - "text/plain": [ - "Registered data loaders\n", - "=======================\n", - "\n", - "Loaders\n", - "-------\n", - "kriss: \n", - "merlin: \n", - "ssrl: \n", - "\n", - "Aliases\n", - "-------\n", - "kriss: ['KRISS']\n", - "merlin: ['ALS_BL4', 'als_bl4', 'BL403', 'bl403']\n", - "ssrl: ['ssrl52', 'bl5-2']" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import erlab.io\n", - "\n", - "erlab.io.loaders" - ] - }, - { - "cell_type": "raw", - "metadata": { - "editable": true, - "raw_mimetype": "text/restructuredtext", - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "You can access each loader by its name or alias, both as an attribute or an\n", - "item. For example, to access the loader for the ALS beamline 4.0.3, you can use\n", - "any of the following:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "erlab.io.loaders[\"merlin\"]\n", - "erlab.io.loaders[\"bl403\"]\n", - "erlab.io.loaders.merlin\n", - "erlab.io.loaders.bl403" - ] - }, - { - "cell_type": "raw", - "metadata": { - "editable": true, - "raw_mimetype": "text/restructuredtext", - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "For a single session, it is very common to use only one type of loader for a\n", - "single folder with all your data. Hence, the module provides a way to set a\n", - "default loader for a session. This is done with :func:`erlab.io.set_loader`. The\n", - "same can be done for the data directory using :func:`erlab.io.set_data_dir`.\n", - "\n", - "The main function used to load ARPES data is :func:`erlab.io.load`." - ] - }, - { - "cell_type": "raw", - "metadata": { - "editable": true, - "raw_mimetype": "text/restructuredtext", - "slideshow": { - "slide_type": "" - }, - "tags": [] - }, - "source": [ - "Advanced: implementing a data loader plugin\n", - "-------------------------------------------\n", - "\n", - "It is easy to add new loaders to the framework. Any class that subclasses\n", - ":class:`LoaderBase ` is automatically registered\n", - "as a loader upon its definition. The class must have a valid ``name`` attribute,\n", - "which is used to access the loader.\n", - "\n", - "If the ``name`` attribute is prefixed with an underscore, the loader is not\n", - "registered as a loader. This is useful for abstract classes that are not meant\n", - "to be used directly. One example is :class:`DA30Loader\n", - "`, which serves as a base class for\n", - "implementing loaders for setups using the Scienta Omicron DA30 hemispherical\n", - "analyzer with ``SES.exe``.\n", - "\n", - "ARPES data from a single experiment are usually stored in one folder, with files\n", - "that look like ``file_0001.h5``, ``file_0002.h5``, etc. If the naming scheme\n", - "does not deviate from this pattern, only two methods need to be implemented:\n", - ":meth:`identify ` and\n", - ":meth:`load_single `. The following\n", - "flowchart shows the process of loading data from a single scan, given either a\n", - "file path or a sequence number:\n", - "\n", - ".. image:: ../images/flowchart_single.pdf\n", - " :align: center\n", - "\n", - "However, there are some setups where data for a single scan is saved over\n", - "multiple files. In this case, the files will look like ``file_0001_0001.h5``,\n", - "``file_0001_0002.h5``, etc. For these kinds of setups, an additional method\n", - ":meth:`infer_index ` must be\n", - "implemented. The following flowchart shows the process of loading data from\n", - "multiple files:\n", - "\n", - ".. image:: ../images/flowchart_multiple.pdf\n", - " :align: center\n", - "\n", - "See :doc:`../generated/erlab.io.dataloader` for more information. The\n", - "implementation of existing loaders in the :mod:`erlab.io.plugins` module is a\n", - "good starting point; see the `source code on github\n", - "`_." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
NameAliasesLoader class
krissKRISSerlab.io.plugins.kriss.KRISSLoader
merlinALS_BL4, als_bl4, BL403, bl403erlab.io.plugins.merlin.BL403Loader
ssrlssrl52, bl5-2erlab.io.plugins.ssrl52.SSRL52Loader
" - ], - "text/plain": [ - "Registered data loaders\n", - "=======================\n", - "\n", - "Loaders\n", - "-------\n", - "kriss: \n", - "merlin: \n", - "ssrl: \n", - "\n", - "Aliases\n", - "-------\n", - "kriss: ['KRISS']\n", - "merlin: ['ALS_BL4', 'als_bl4', 'BL403', 'bl403']\n", - "ssrl: ['ssrl52', 'bl5-2']" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from erlab.io.dataloader import LoaderBase\n", - "\n", - "\n", - "class MyLoader(LoaderBase):\n", - " name = \"my_loader\"\n", - "\n", - " skip_validate = True" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.8" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "nbformat": 4, + "nbformat_minor": 4 } diff --git a/src/erlab/io/dataloader.py b/src/erlab/io/dataloader.py index 843e3e5f..2ca376e1 100644 --- a/src/erlab/io/dataloader.py +++ b/src/erlab/io/dataloader.py @@ -8,12 +8,15 @@ implementation of `LoaderBase.validate` for details. For any data loader plugin subclassing `LoaderBase`, the following attributes and -methods must be defined: `LoaderBase.name`, `LoaderBase.aliases`, -`LoaderBase.name_map`, `LoaderBase.coordinate_attrs`, `LoaderBase.additional_attrs`, +methods must be defined: `LoaderBase.name`, `LoaderBase.aliases`, `LoaderBase.name_map`, +`LoaderBase.coordinate_attrs`, `LoaderBase.additional_attrs`, `LoaderBase.always_single`, `LoaderBase.skip_validate`, :func:`LoaderBase.load_single`, :func:`LoaderBase.identify`, :func:`LoaderBase.infer_index`, and :func:`LoaderBase.generate_summary`. +A detailed guide on how to implement a data loader can be found in +:doc:`../user-guide/io`. + If additional post-processing is required, the :func:`LoaderBase.post_process` method can be extended to include the necessary functionality. @@ -149,7 +152,7 @@ def __init_subclass__(cls, **kwargs): def formatter(cls, val: object): """Format the given value based on its type. - This method is used when formtting the cells of the summary dataframe. + This method is used when formatting the cells of the summary dataframe. Parameters ---------- @@ -400,9 +403,9 @@ def summarize( `pandas.DataFrame`, much like a log file. This is useful for quickly inspecting the contents of a directory. - The dataframe is formatted using `get_styler` and displayed in the IPython - shell. Results are cached in a pickle file in the directory. If the pickle file - is not found, the summary is generated with `generate_summary` and cached. + The dataframe is formatted using the style from :meth:`get_styler + ` and displayed in the IPython shell. + Results are cached in a pickle file in the directory. Parameters ---------- @@ -410,7 +413,7 @@ def summarize( Directory to summarize. usecache Whether to use the cached summary if available. If `False`, the summary will - be regenerated and cached. + be regenerated and the cache will be updated. **kwargs Additional keyword arguments to be passed to `generate_summary`. @@ -460,7 +463,7 @@ def load_single( Parameters ---------- - file_paths + file_path Full path to the file to be loaded. Returns @@ -477,7 +480,10 @@ def identify( """Identify the files and coordinates for a given scan number. This method takes a scan index and transforms it into a list of file paths and - coordinates. + coordinates. For scans spread over multiple files, the coordinates must be a + dictionary mapping scan axes names to scan coordinates. For single file scans, + the list should contain only one file path and coordinates must be an empty + dictionary. Parameters ---------- @@ -502,6 +508,9 @@ def identify( def infer_index(self, name: str) -> int | None: """Infer the index for the given file name. + This method takes a file name and tries to infer the scan index from it. If the + index can be inferred, it is returned; otherwise, `None` should be returned. + Parameters ---------- name diff --git a/src/erlab/io/igor.py b/src/erlab/io/igor.py index aecbf637..b2a603d6 100644 --- a/src/erlab/io/igor.py +++ b/src/erlab/io/igor.py @@ -7,7 +7,7 @@ import numpy as np import xarray as xr -__all__ = ["load_experiment", "load_h5", "load_ibw", "load_pxp", "load_wave"] +__all__ = ["load_experiment", "load_igor_hdf5", "load_wave"] def _load_experiment_raw( @@ -102,7 +102,20 @@ def load_experiment( ) -def load_h5(filename): +def load_igor_hdf5(filename: str | os.PathLike) -> xr.Dataset: + """Load a HDF5 file exported by Igor Pro into an `xarray.Dataset`. + + Parameters + ---------- + filename + The path to the file. + + Returns + ------- + xarray.Dataset + The loaded data. + + """ ncf = h5netcdf.File(filename, mode="r", phony_dims="sort") ds = xr.open_dataset(xr.backends.H5NetCDFStore(ncf)) for dv in ds.data_vars: @@ -226,4 +239,7 @@ def get_dim_name(index): load_pxp = load_experiment +"""Alias for :func:`load_experiment`.""" + load_ibw = load_wave +"""Alias for :func:`load_wave`.""" diff --git a/src/erlab/io/utilities.py b/src/erlab/io/utilities.py index 89ed9937..c0fa3902 100644 --- a/src/erlab/io/utilities.py +++ b/src/erlab/io/utilities.py @@ -168,7 +168,10 @@ def save_as_hdf5( filename Target file name. igor_compat - Make the resulting file compatible with Igor's `HDF5OpenFile`. + (*Experimental*) Make the resulting file compatible with Igor's `HDF5OpenFile` for DataArrays + with up to 4 dimensions. A convenient Igor procedure is `included in the + repository `_. + Default is `True`. **kwargs Extra arguments to `xarray.DataArray.to_netcdf`: refer to the `xarray` documentation for a list of all possible arguments. @@ -183,8 +186,12 @@ def save_as_hdf5( data = data.assign_attrs({k: "None"}) if isinstance(v, dict): data = data.assign_attrs({k: str(v)}) + if isinstance(data, xr.Dataset): igor_compat = False + elif data.ndim > 4: + igor_compat = False + if igor_compat: # IGORWaveScaling order: chunk row column layer scaling = [[1, 0]]