Skip to content

Commit

Permalink
Merge branch 'stable' into 0.10.x
Browse files Browse the repository at this point in the history
  • Loading branch information
gb119 committed May 13, 2024
2 parents 6b6a22d + 3c96087 commit 79c0562
Show file tree
Hide file tree
Showing 350 changed files with 3,307 additions and 400 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests-action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7","3.8","3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
os: ["ubuntu-latest"]
steps:
- name: Check out repository code
Expand Down
41 changes: 27 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:target: https://coveralls.io/github/stonerlab/Stoner-PythonCode?branch=master

.. image:: https://app.codacy.com/project/badge/Grade/a9069a1567114a22b25d63fd4c50b228
:target: https://www.codacy.com/gh/stonerlab/Stoner-PythonCode/dashboard?utm_source=github.com&utm_medium=referral&utm_content=stonerlab/Stoner-PythonCode&utm_campaign=Badge_Grade
:target: https://app.codacy.com/gh/stonerlab/Stoner-PythonCode/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade

.. image:: https://badge.fury.io/py/Stoner.svg
:target: https://badge.fury.io/py/Stoner
Expand Down Expand Up @@ -63,26 +63,30 @@ After installing the current Anaconda version, open a terminal (Mac/Linux) or An

.. code-block:: sh
conda install -c phygbu Stoner
conda install -c phygbu -c conda-forge Stoner
If you are not using Anaconda python, then pip should also work:
If (and only if) you are not using Anaconda python, then pip should also work:

.. code-block:: sh
pip install Stoner
.. warning::
The conda packages are generally much better tested than the pip wheels, so we would recommend using
conda where possible.

This will install the Stoner package and any missing dependencies into your current Python environment. Since the package is under fairly
constant updates, you might want to follow the development with git. The source code, along with example scripts
and some sample data files can be obtained from the github repository: https://github.com/stonerlab/Stoner-PythonCode

Overview
========

The main part of the **Stoner** package provides four basic top-level classes that describe:
- an individual file of experimental data (**Stoner.Data**),
The main part of the **Stoner** package provides four top-level classes that describe:
- an individual file of experimental data (**Stoner.Data**) - somewhat similar to a DataFrame,
- an individual experimental image (**Stoner.ImageFile**),
- a list (such as a directory tree on disc) of many experimental files (**Stoner.DataFolder**)
- a list (such as a directory tree on disc) of many image files (**Stoner.ImageFolder**).
- a nested list (such as a directory tree on disc) of many experimental files (**Stoner.DataFolder**)
- a nested list (such as a directory tree on disc) of many image files (**Stoner.ImageFolder**).

For our research, a typical single experimental data file is essentially a single 2D table of floating point
numbers with associated metadata, usually saved in some ASCII text format. This seems to cover most experiments
Expand All @@ -99,6 +103,15 @@ operations to be chained together in one line.
This is a *data-centric* approach - we have some data and we do various operations on it to get to our result. In
contrasr, traditional functional programming thinks in terms of various functions into which you pass data.

.. note::
This is rather similar to pandas DataFrames and the package provides methods to easily convert to and from
DataFrames. Unlike a DataFrame, a **Stoner.Data** object maintains a dictionary of additional metadata
attached to the dataset (e.g. of instrument settings, experimental ort environmental; conditions
when thedata was taken). To assist with exporting to pandas DataFrames, the package will add a custom
attrobute handler to pandas DataFrames **DataFrame.metadata** to hold this additional data.

Unlike Pandas, the **Stoner** package's default is to operate in-place and also to return the object
from method calls to facilitate "chaining" of data methods into short single line pipelines.

Data and Friends
----------------
Expand Down Expand Up @@ -188,11 +201,11 @@ At the moment the development version is maily broen....
Build Status
~~~~~~~~~~~~

Version 0.7 onwards are tested using the Travis-CI services with unit test coverage assessed by Coveralls.
Version 0.7-0.9 were tested using the Travis-CI services with unit test coverage assessed by Coveralls.

Version 0.9 is tested with Python 2.7, 3.5, 3.6 using the standard unittest module.
Version 0.9 was tested with Python 2.7, 3.5, 3.6 using the standard unittest module.

Version 0.10 is tested using **pytest** with Python 3.6-3.9 using a github action.
Version 0.10 is tested using **pytest** with Python 3.7-3.11 using a github action.


Citing the Stoner Package
Expand All @@ -207,6 +220,7 @@ Stable Versions

New Features in 0.10 include:

* Support for Python 3.10 and 3.11
* Refactor Stoner.Core.DataFile to move functionality to mixin classes
* Start implementing PEP484 Type hinting
* Support pathlib for paths
Expand Down Expand Up @@ -252,12 +266,11 @@ The ancient stable version is 0.7.2. Features of 0.7.2 include
* DataFolder has an options to skip iterating over empty Data files
* Further improvements to :py:attr:`Stoner.Core.DataFile.setas` handline.

No further relases will be made to 0.7.x.
No further relases will be made to 0.7.x - 0.9.x

0.6, 0.7 should work on Python 2.7 and 3.5
0.8 is also tested on Python 3.6
Versions 0.6.x and earlier are now pre-historic!

.. _online documentation: http://stoner-pythoncode.readthedocs.io/en/latest/
.. _online documentation: http://stoner-pythoncode.readthedocs.io/en/stable/
.. _github repository: http://www.github.com/stonerlab/Stoner-PythonCode/
.. _Dr Gavin Burnell: http://www.stoner.leeds.ac.uk/people/gb
.. _User_Guide: http://stoner-pythoncode.readthedocs.io/en/latest/UserGuide/ugindex.html
1 change: 0 additions & 1 deletion Stoner/Analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@


class AnalysisMixin:

"""A mixin calss designed to work with :py:class:`Stoner.Core.DataFile` to provide additional analysis methods."""

def __dir__(self):
Expand Down
2 changes: 1 addition & 1 deletion Stoner/Core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Stoner.Core provides the core classes for the Stoner package."""

__all__ = [
"StonerLoadError",
"StonerSetasError",
Expand Down Expand Up @@ -64,7 +65,6 @@ class DataFile(
metadataObject,
MutableSequence,
):

"""Base class object that represents a matrix of data, associated metadata and column headers.
Attributes:
Expand Down
3 changes: 1 addition & 2 deletions Stoner/Folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
The core classes provides a means to access them as an ordered collection or as a mapping.
"""

__all__ = ["DataFolder", "PlotFolder"]

from Stoner.tools import make_Data
Expand All @@ -11,7 +12,6 @@


class DataFolder(DataMethodsMixin, DiskBasedFolderMixin, baseFolder):

"""Provide an interface to manipulating lots of data files stored within a directory structure on disc.
By default, the members of the DataFolder are instances of :class:`Stoner.Data`. The DataFolder emplys a lazy
Expand All @@ -28,5 +28,4 @@ def __init__(self, *args, **kargs):


class PlotFolder(PlotMethodsMixin, DataFolder):

"""A :py:class:`Stoner.folders.baseFolder` that knows how to ploth its underlying data files."""
16 changes: 5 additions & 11 deletions Stoner/HDF5.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
to :py:class:`Stoner.Core.Data`.
"""

__all__ = ["HDF5File", "HDF5Folder", "HGXFile", "SLS_STXMFile", "STXMImage", "HDFFileManager"]
import importlib
import os.path as path
Expand Down Expand Up @@ -37,7 +38,7 @@ def get_hdf_loader(f, default_loader=lambda *args, **kargs: None):
Callable function that can produce an object of an appropriate class.
"""
if "type" not in f.attrs:
StonerLoadError("HDF5 Group does not specify the type attribute used to check we can load it.")
raise StonerLoadError("HDF5 Group does not specify the type attribute used to check we can load it.")
typ = bytes2str(f.attrs.get("type", ""))
if (typ not in globals() or not isinstance(globals()[typ], type)) and "module" not in f.attrs:
raise StonerLoadError(
Expand All @@ -52,7 +53,6 @@ def get_hdf_loader(f, default_loader=lambda *args, **kargs: None):


class HDFFileManager:

"""Context manager for HDF5 files."""

def __init__(self, filename, mode="r"):
Expand Down Expand Up @@ -84,8 +84,8 @@ def __init__(self, filename, mode="r"):
if not mode.startswith("w"):
with h5py.File(filename, "r"):
pass
except (IOError, OSError):
raise StonerLoadError(f"{filename} not at HDF5 File")
except (IOError, OSError) as err:
raise StonerLoadError(f"{filename} not at HDF5 File") from err
self.filename = filename

def __enter__(self):
Expand All @@ -108,14 +108,13 @@ def __enter__(self):
raise StonerLoadError("Note a resource that can be handled with HDF")
return self.handle

def __exit__(self, type, value, traceback):
def __exit__(self, _type, _value, _traceback):
"""Ensure we close the hdf file no matter what."""
if self.file is not None and self.close:
self.file.close()


class HDF5File(DataFile):

"""A sub class of DataFile that sores itself in a HDF5File or group.
Args:
Expand Down Expand Up @@ -287,7 +286,6 @@ def to_hdf(self, filename=None, **kargs): # pylint: disable=unused-argument


class HGXFile(DataFile):

"""A subclass of DataFile for reading GenX HDF Files.
These files typically have an extension .hgx. This class has been based on a limited sample
Expand Down Expand Up @@ -372,7 +370,6 @@ def main_data(self, data_grp):


class HDF5FolderMixin:

"""Provides a method to load and save data from a single HDF5 file with groups.
See :py:class:`Stoner.Folders.DataFolder` for documentation on constructor.
Expand Down Expand Up @@ -568,7 +565,6 @@ def save(self, root=None):


class HDF5Folder(HDF5FolderMixin, DataFolder):

"""Just enforces the loader attriobute to be an HDF5File."""

def __init__(self, *args, **kargs):
Expand All @@ -578,7 +574,6 @@ def __init__(self, *args, **kargs):


class SLS_STXMFile(DataFile):

"""Load images from the Swiss Light Source Pollux beamline."""

priority = 16
Expand Down Expand Up @@ -671,7 +666,6 @@ def scan_meta(self, group):


class STXMImage(ImageFile):

"""An instance of KerrArray that will load itself from a Swiss Light Source STXM image."""

# pylint: disable=no-member
Expand Down
2 changes: 0 additions & 2 deletions Stoner/Image/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def _proxy(self, *args, **kargs):

@class_modifier(draw, adaptor=_draw_apaptor, RTD_restrictions=False, no_long_names=True)
class DrawProxy:

"""Provides a wrapper around :py:mod:`skimage.draw` to allow easy drawing of objects onto images.
This class allows access the user to draw simply shapes on an image (or its mask) by specifying the desired shape
Expand Down Expand Up @@ -205,7 +204,6 @@ def square(self, r, c, w, angle=0.0, shape=None, value=1.0):


class MaskProxy:

"""Provides a wrapper to support manipulating the image mask easily.
The actual mask of a :py:class:`Stonmer.ImageFile` is held by the mask attribute of the underlying
Expand Down
7 changes: 4 additions & 3 deletions Stoner/Image/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ def copy_into(source: "ImageFile", dest: "ImageFile") -> "ImageFile":
@class_modifier([ndi], transpose=True)
@class_modifier(imagefuncs, overload=True)
class ImageArray(np.ma.MaskedArray, metadataObject):

"""A numpy array like class with a metadata parameter and pass through to skimage methods.
ImageArray is for manipulating images stored as a 2d numpy array.
Expand Down Expand Up @@ -251,7 +250,6 @@ def __new__(cls, *args, **kargs):
We're using __new__ rather than __init__ to imitate a numpy array as
close as possible.
"""

array_arg_keys = ["dtype", "copy", "order", "subok", "ndmin", "mask"] # kwargs for array setup
array_args = {k: kargs.pop(k) for k in array_arg_keys if k in kargs.keys()}
user_metadata = kargs.get("metadata", {})
Expand Down Expand Up @@ -671,6 +669,10 @@ def __delitem__(self, index):
else:
super().__delitem__(index)

def save(self, filename=None, **kargs):
"""Stub method for a save function."""
raise NotImplementedError(f"Save is not implemented in {self.__class__}")


@class_modifier(
[
Expand Down Expand Up @@ -698,7 +700,6 @@ def __delitem__(self, index):
@class_modifier(imagefuncs, overload=True, adaptor=image_file_adaptor)
@class_wrapper(target=ImageArray, exclude_below=metadataObject)
class ImageFile(metadataObject):

"""An Image file type that is analogous to :py:class:`Stoner.Data`.
This contains metadata and an image attribute which
Expand Down
15 changes: 3 additions & 12 deletions Stoner/Image/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from copy import deepcopy, copy

import numpy as np
from matplotlib.pyplot import figure, Figure, subplot, tight_layout
from matplotlib.pyplot import figure, Figure, subplot
from PIL.TiffImagePlugin import ImageFileDirectory_v2
from PIL import Image

Expand All @@ -19,7 +19,6 @@


class ImageFolderMixin:

"""Mixin to provide a folder object for images.
ImageFolderMixin is designed to behave pretty much like DataFolder but with
Expand Down Expand Up @@ -249,7 +248,7 @@ def from_tiff(cls, filename, **kargs):
except (TypeError, ValueError, IOError):
metadata = []
else:
raise TypeError(f"Cannot load as an ImageFolder due to lack of description tag")
raise TypeError("Cannot load as an ImageFolder due to lack of description tag")
imglist = []
for ix, md in enumerate(metadata):
img.seek(ix)
Expand Down Expand Up @@ -314,8 +313,6 @@ def montage(self, *args, **kargs):
Passed to matplotlib figure call.
plots_per_page(int):
maximum number of plots per figure.
tight_layout(dict or False):
If not False, arguments to pass to a call of :py:func:`matplotlib.pyplot.tight_layout`. Defaults to {}
Returns:
A list of :py:class:`matplotlib.pyplot.Axes` instances.
Expand All @@ -332,7 +329,6 @@ def montage(self, *args, **kargs):
plts = min(plts, len(self))

extra = kargs.pop("extra", lambda i, j, d: None)
tight = kargs.pop("tight_layout", {})

fig_num = kargs.pop("figure", getattr(self, "_figure", None))
if isinstance(fig_num, Figure):
Expand All @@ -344,7 +340,7 @@ def montage(self, *args, **kargs):
fig_num = fig_num.number

fig_args = getattr(self, "_fig_args", [])
fig_kargs = getattr(self, "_fig_kargs", {})
fig_kargs = getattr(self, "_fig_kargs", {"layout": "constrained"})
for arg in ("figsize", "dpi", "facecolor", "edgecolor", "frameon", "FigureClass"):
if arg in kargs:
fig_kargs[arg] = kargs.pop(arg)
Expand All @@ -363,8 +359,6 @@ def montage(self, *args, **kargs):
for i, d in enumerate(self):
plt_kargs = copy(kargs)
if i % plts == 0 and i != 0:
if isinstance(tight, dict):
tight_layout(**tight)
fig = figure(*fig_args, **fig_kargs)
fignum = fig.number
j = 1
Expand All @@ -381,8 +375,6 @@ def montage(self, *args, **kargs):
plt_kargs["title"] = kargs["title"](d)
ret.append(d.imshow(*args, **plt_kargs))
extra(i, j, d)
if isinstance(tight, dict):
tight_layout(**tight)
return ret

def stddev(self, weights=None, _box=False, _metadata="first"):
Expand Down Expand Up @@ -481,7 +473,6 @@ def to_tiff(self, filename):


class ImageFolder(ImageFolderMixin, DiskBasedFolderMixin, baseFolder):

"""Folder object for images.
ImageFolder is designed to behave pretty much like DataFolder but with
Expand Down
Loading

0 comments on commit 79c0562

Please sign in to comment.