Skip to content

Commit

Permalink
release 0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
MirjamSchr committed Oct 24, 2023
2 parents 5e6985c + aadd63f commit a1ab57d
Show file tree
Hide file tree
Showing 21 changed files with 488 additions and 87 deletions.
1 change: 1 addition & 0 deletions .prospector.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ inherits:
ignore-paths:
- scratch_files
- cwepr/io/__init__.py
- build/

pydocstyle:
run: true
Expand Down
21 changes: 16 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ For more general information on the cwepr package and for how to use it, see its
Features
--------

A list of features, not all implemented yet but aimed at for the first public release (cwEPR 0.1):
A list of features:

- Fully reproducible processing of cw-EPR data
- Import of EPR data from diverse sources (Bruker ESP, EMX, Elexsys; Magnettech)
Expand All @@ -58,16 +58,27 @@ And to make it even more convenient for users and future-proof:
- Extensive user and API documentation


.. warning::
The cwEPR package is currently under active development and still considered in Beta development state. Therefore, expect frequent changes in features and public APIs that may break your own code. Nevertheless, feedback as well as feature requests are highly welcome.


Target audience
---------------

The cwepr package addresses scientists working with cwEPR data (both, measured and calculated) on a daily base and concerned with `reproducibility <https://www.reproducible-research.de/>`_. Due to being based on the `ASpecD framework <https://www.aspecd.de/>`_, the cwepr package ensures reproducibility and---as much as possible---replicability of data processing, starting from recording data and ending with their final (graphical) representation, e.g., in a peer-reviewed publication. This is achieved by automatically creating a gap-less record of each operation performed on your data. If you do care about reproducibility and are looking for a system that helps you to achieve this goal, the cwepr package may well be interesting for you.


How to cite
-----------

cwepr is free software. However, if you use cwepr for your own research, please cite both, the article describing it and the software itself:

* Mirjam Schröder, Till Biskup. cwepr -- a Python package for analysing cw-EPR data focussing on reproducibility and simple usage. *Journal of Magnetic Resonance* **335**:107140, 2022. `doi:10.1016/j.jmr.2021.107140 <https://doi.org/10.1016/j.jmr.2021.107140>`_ | `PDF <https://www.till-biskup.de/_media/de/person/schr-jmr-335-107140-accepted.pdf>`_ | `SI <https://www.till-biskup.de/_media/de/person/schr-jmr-335-107140-si.pdf>`_

* Mirjam Schröder, Till Biskup. cwepr (2021). `doi:10.5281/zenodo.4896687 <https://doi.org/10.5281/zenodo.4896687>`_

To make things easier, cwepr has a `DOI <https://doi.org/10.5281/zenodo.4896687>`_ provided by `Zenodo <https://zenodo.org/>`_, and you may click on the badge below to directly access the record associated with it. Note that this DOI refers to the package as such and always forwards to the most current version.

.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4896687.svg
:target: https://doi.org/10.5281/zenodo.4896687


Related projects
----------------

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0
0.5.0
2 changes: 1 addition & 1 deletion cwepr/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ def _get_linewidths(self):
index_max = np.argmax(self.dataset.data.data, axis=0)
index_min = np.argmin(self.dataset.data.data, axis=0)
self.linewidths = self.dataset.data.axes[0].values[index_min] - \
self.dataset.data.axes[0].values[index_max]
self.dataset.data.axes[0].values[index_max]

def _fill_dataset(self):
self.result.data.data = self.linewidths
Expand Down
2 changes: 1 addition & 1 deletion cwepr/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from .factory import DatasetImporterFactory
from .magnettech import MagnettechXMLImporter, GoniometerSweepImporter, \
AmplitudeSweepImporter, PowerSweepImporter
from .txt_file import TxtImporter, CsvImporter
from .txt_file import CsvImporter, TxtImporter
from .bes3t import BES3TImporter
from .esp_winepr import ESPWinEPRImporter
from .niehs import NIEHSDatImporter, NIEHSLmbImporter, NIEHSExpImporter
Expand Down
7 changes: 4 additions & 3 deletions cwepr/io/bes3t.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ def _infofile_exists(self):
if self._get_infofile_name() and os.path.exists(
self._get_infofile_name()[0]):
return True
print('No infofile found for dataset %s, import continued without '
'infofile.' % os.path.split(self.source)[1])
print(f'No infofile found for dataset '
f'{os.path.split(self.source)[1]}, import continued without '
f'infofile.')
return False

def _extract_metadata_from_dsc(self):
dsc_filename = self.source + '.DSC'
with open(dsc_filename, 'r') as file:
with open(dsc_filename, 'r', encoding='ascii') as file:
lines = file.read().splitlines()

for line in lines:
Expand Down
2 changes: 1 addition & 1 deletion cwepr/io/esp_winepr.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _set_defaults(self):

def _read_parameter_file(self):
par_filename = self.source + '.par'
with open(par_filename, 'r') as file:
with open(par_filename, 'r', encoding='ascii') as file:
lines = file.read().splitlines()

for line in lines:
Expand Down
1 change: 1 addition & 0 deletions cwepr/io/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__(self):
"NIEHSLmb": [".lmb"],
"NIEHSExp": [".exp"],
"Txt": [".txt"],
"Csv": [".csv"],
}
self.data_format = None

Expand Down
18 changes: 9 additions & 9 deletions cwepr/io/magnettech.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ def _create_x_axis(self):
mw_abs_x_slope = float(self._data_curve.attrib['XSlope'])

mw_x = mw_abs_x_offset + \
np.linspace(0, len(self._yvalues) - 1, num=len(self._yvalues)) \
* mw_abs_x_slope
np.linspace(0, len(self._yvalues) - 1, num=len(self._yvalues)) \
* mw_abs_x_slope
b_field_x = b_field_x_offset + \
np.linspace(0, len(self._xvalues) - 1,
num=len(self._xvalues)) * b_field_x_slope
np.linspace(0, len(self._xvalues) - 1,
num=len(self._xvalues)) * b_field_x_slope
self._xvalues = np.interp(mw_x, b_field_x, self._xvalues)

def _extract_metadata_from_xml(self):
Expand Down Expand Up @@ -619,10 +619,10 @@ def _import_fixed_metadata(self):
self._data[0].metadata.experiment.runs
self.dataset.metadata.spectrometer = self._data[0].metadata.spectrometer
self.dataset.metadata.magnetic_field.start.from_string(
("{:.4f}".format(self.dataset.data.axes[0].values[0])) + ' ' +
self._data[0].metadata.magnetic_field.start.unit)
(f"{self.dataset.data.axes[0].values[0]:.4f} " +
self._data[0].metadata.magnetic_field.start.unit))
self.dataset.metadata.magnetic_field.stop.from_string(
("{:.4f}".format(self.dataset.data.axes[0].values[-1])) + ' ' +
f"{self.dataset.data.axes[0].values[-1]:.4f} " +
self._data[0].metadata.magnetic_field.stop.unit)
self.dataset.metadata.magnetic_field.sweep_width.value = \
self.dataset.metadata.magnetic_field.stop.value - \
Expand Down Expand Up @@ -829,10 +829,10 @@ def _import_fixed_metadata(self):
self._data[0].metadata.experiment.runs
self.dataset.metadata.spectrometer = self._data[0].metadata.spectrometer
self.dataset.metadata.magnetic_field.start.from_string(
("{:.4f}".format(self.dataset.data.axes[0].values[0])) + ' ' +
f"{self.dataset.data.axes[0].values[0]:.4f} " +
self._data[0].metadata.magnetic_field.start.unit)
self.dataset.metadata.magnetic_field.stop.from_string(
("{:.4f}".format(self.dataset.data.axes[0].values[-1])) + ' ' +
f"{self.dataset.data.axes[0].values[-1]:.4f} " +
self._data[0].metadata.magnetic_field.stop.unit)
self.dataset.metadata.magnetic_field.sweep_width.value = \
self.dataset.metadata.magnetic_field.stop.value - \
Expand Down
2 changes: 1 addition & 1 deletion cwepr/io/niehs.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ def _clean_filenames(self):

def _read_file_contents(self):
filename = self.source + ".exp"
with open(filename, 'r') as file:
with open(filename, 'r', encoding='ascii') as file:
self._file_contents = file.read()
self._lines = self._file_contents.splitlines()

Expand Down
185 changes: 141 additions & 44 deletions cwepr/io/txt_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,173 @@
You may have a look as well at the importers provided by the ASpecD package
for similar situations, particularly :class:`aspecd.io.TxtImporter`.
"""
import io
import numpy as np

import aspecd.io


class TxtImporter(aspecd.io.DatasetImporter):
"""Simple importer for txt files containing data.
class TxtImporter(aspecd.io.TxtImporter):
r"""
Importer for text files with various delimiters and separators.
Sometimes, data come from sources as a txt file only. So far, the format
is hardcoded and should contain two columns separated by a tabulator.
Automatically detects the extension of a file. Therefore, the importer
should be given explicitly in the recipe if it is different from ".txt".
"""
Due to the inherent lacking of metadata in text files despite their
widespread use, the importer adds three values as metadata in order to
get the axis label for the magnetic field axis correctly:
def __init__(self, source=''):
super().__init__(source=source)
# public properties
self.extension = '.txt'
* Unit of the first axis: mT
def _import(self):
self._get_data()
self._create_metadata()
* Quantity of the fist axis: magnetic field
def _get_data(self):
if self.source.endswith('.txt'):
self.source = self.source[:-4]
* Quantity of the second axis: intensity
self.source = self.source + self.extension
raw_data = np.loadtxt(self.source, delimiter='\t')
self.dataset.data.data = raw_data[:, 1]
self.dataset.data.axes[0].values = raw_data[:, 0]
def _create_metadata(self):
self.dataset.data.axes[0].unit = 'mT'
self.dataset.data.axes[1].quantity = 'intensity'
Attributes
----------
parameters : :class:`dict`
Parameters controlling the import
skiprows : :class:`int`
Number of rows to skip in text file (*e.g.*, header lines)
delimiter : :class:`str`
The string used to separate values.
Default: None (meaning: whitespace)
comments : :class:`str` | :class:`list`
Characters or list of characters indicating the start of a comment.
Default: #
separator : :class:`str`
Character used as decimal separator.
Default: None (meaning: dot)
Examples
--------
For convenience, a series of examples in recipe style (for details of the
recipe-driven data analysis, see :mod:`aspecd.tasks`) is given below for
how to make use of this class. The examples focus each on a single aspect.
The most general and simple case, using only default values:
.. code-block:: yaml
datasets:
- eprdata.txt
However, you can control the import in quite some detail, with respect to
delimiter, decimal separator, rows to skip from the top, and comment
character. A full example setting each of these parameters may look as
follows:
.. code-block:: yaml
datasets:
- source: eprdata.txt
importer_parameters:
delimiter: '\t'
separator: ','
skiprows: 3
comments: '%'
Here, the delimiter between columns is the tabulator, the decimal
separator the comma, the first three lines are skipped by default as well
as every line starting with a percent character, as this is interpreted as
comment.
A frequent use case is importing simulations that were carried out with
EasySpin. A MATLAB excerpt for saving the simulated spectrum might look
as follows:
.. code-block:: matlab
class CsvImporter(aspecd.io.DatasetImporter):
"""Importer for simple csv imports with different delimiters."""
[B_sim_iso, Spc_sim_iso] = garlic(Sys, Exp);
data = [B_sim_iso', Spc_sim_iso'];
writematrix(data, 'Simulated-spectrum')
Read in the simulated spectrum with:
.. code-block:: yaml
datasets:
- source: Simulated-spectrum.txt
id: simulation
importer: TxtImporter
importer_parameters:
delimiter: ','
.. versionchanged:: 0.5
Renamed from CsvImporter to TxtImporter and generalised handling of text
files. Now inherits from :class:`aspecd.io.TxtImporter`.
"""

def __init__(self, source=''):
if source.endswith('.csv'):
source = source[:-4]
super().__init__(source=source)
# public properties
self.extension = '.csv'
self.parameters["skiprows"] = 1
self.extension = '.txt'
self.parameters["skiprows"] = 0
self.parameters["delimiter"] = None
self.parameters["comments"] = "#"
self.parameters["separator"] = None

def _import(self):
self._read_data()
self._get_extension()
super()._import()
self._create_metadata()

def _read_data(self):
if "separator" in self.parameters:
separator = self.parameters.pop("separator")
def _get_extension(self):
if '.' in self.source:
extension = self.source[self.source.rfind('.'):]
else:
separator = None
if separator:
with open(self.source + self.extension, encoding="utf8") as file:
contents = file.read()
contents = contents.replace(separator, '.')
# noinspection PyTypeChecker
data = np.loadtxt(io.StringIO(contents), **self.parameters)
self.dataset.data.data = data[:, 1]
self.dataset.data.axes[0].values = data[:, 0]
extension = None
if extension:
self.extension = extension
self.source = self.source[:self.source.rfind('.')]
self.source += self.extension

def _create_metadata(self):
self.dataset.data.axes[0].unit = 'mT'
self.dataset.data.axes[0].quantity = 'magnetic field'
self.dataset.data.axes[1].quantity = 'intensity'


class CsvImporter(TxtImporter):
"""
Simple importer for csv files containing EPR data.
The delimiter defaults to the comma, as the name implies, but you can
set the delimiter as well as other parameters explicitly. See
:class:`TxtImporter` for details.
Due to the inherent lacking of metadata in text files despite their
widespread use, the importer adds three values as metadata in order to
get the axis label for the magnetic field axis correctly:
* Unit of the first axis: mT
* Quantity of the fist axis: magnetic field
* Quantity of the second axis: intensity
.. versionchanged:: 0.5
Renamed from CsvImporter to TxtImporter and generalised handling of text
files. Now inherits from :class:`TxtImporter`.
"""

def __init__(self, source=''):
super().__init__(source=source)
# public properties
self.extension = '.csv'
self.parameters["delimiter"] = ','
Loading

0 comments on commit a1ab57d

Please sign in to comment.