diff --git a/BioLogic.py b/BioLogic.py index 065201b..5575a21 100644 --- a/BioLogic.py +++ b/BioLogic.py @@ -1,11 +1,84 @@ # -*- coding: utf-8 -*- """Code to read in data files from Bio-Logic instruments""" +__all__ = ['MPTfileCSV', 'MPTfile'] + import re import csv +import numpy as np + + +def fieldname_to_dtype(fieldname): + """Converts a column header from the MPT file into a tuple of + canonical name and appropriate numpy dtype""" + + if fieldname == 'mode': + return ('mode', np.uint8) + elif fieldname in ("ox/red", "error", "control changes", "Ns changes", + "counter inc."): + return (fieldname, np.bool_) + elif fieldname in ("time/s", "Ewe/V", "P/W", "(Q-Qo)/mA.h", "x"): + return (fieldname, np.float_) + elif fieldname in ("dq/mA.h", "dQ/mA.h"): + return ("dQ/mA.h", np.float_) + elif fieldname in ("I/mA", "/mA"): + return ("I/mA", np.float_) + elif fieldname in ("control/V", "control/V/mA"): + return ("control/V/mA", np.float_) + else: + raise ValueError("Invalid column header: %s" % fieldname) + def MPTfile(file_or_path): + """Opens .mpt files as numpy record arrays + + Checks for the correct headings, skips any comments and returns a + numpy record array object and a list of comments + """ + + if isinstance(file_or_path, str): + mpt_file = open(file_or_path, 'rb') + else: + mpt_file = file_or_path + + magic = next(mpt_file) + if magic != b'EC-Lab ASCII FILE\r\n': + raise ValueError("Bad first line for EC-Lab file: '%s'" % magic) + + nb_headers_match = re.match(b'Nb header lines : (\d+)\s*$', next(mpt_file)) + nb_headers = int(nb_headers_match.group(1)) + if nb_headers < 3: + raise ValueError("Too few header lines: %d" % nb_headers) + + ## The 'magic number' line, the 'Nb headers' line and the column headers + ## make three lines. Every additional line is a comment line. + comments = [next(mpt_file) for i in range(nb_headers - 3)] + + fieldnames = next(mpt_file).decode('ascii').strip().split('\t') + + expected_fieldnames = ( + ["mode", "ox/red", "error", "control changes", "Ns changes", + "counter inc.", "time/s", "control/V/mA", "Ewe/V", "dq/mA.h", + "P/W", "/mA", "(Q-Qo)/mA.h", "x"], + ["mode", "ox/red", "error", "control changes", "Ns changes", + "counter inc.", "time/s", "control/V", "Ewe/V", "I/mA", + "dQ/mA.h", "P/W"], + ["mode", "ox/red", "error", "control changes", "Ns changes", + "counter inc.", "time/s", "control/V", "Ewe/V", "/mA", + "dQ/mA.h", "P/W"]) + if fieldnames not in expected_fieldnames: + raise ValueError("Unrecognised headers for MPT file format %s" % + fieldnames) + + record_type = np.dtype(list(map(fieldname_to_dtype, fieldnames))) + + mpt_array = np.loadtxt(mpt_file, dtype=record_type) + + return mpt_array, comments + + +def MPTfileCSV(file_or_path): """Simple function to open MPT files as csv.DictReader objects Checks for the correct headings, skips any comments and returns a @@ -21,7 +94,7 @@ def MPTfile(file_or_path): if magic != 'EC-Lab ASCII FILE\n': raise ValueError("Bad first line for EC-Lab file: '%s'" % magic) - nb_headers_match = re.match('Nb header lines : (\d+)', next(mpt_file)) + nb_headers_match = re.match('Nb header lines : (\d+)\s*$', next(mpt_file)) nb_headers = int(nb_headers_match.group(1)) if nb_headers < 3: raise ValueError("Too few header lines: %d" % nb_headers) diff --git a/tests/test_BioLogic.py b/tests/test_BioLogic.py index 08cec0d..ce065c3 100644 --- a/tests/test_BioLogic.py +++ b/tests/test_BioLogic.py @@ -4,7 +4,7 @@ import os.path from nose.tools import ok_, eq_, raises -from .. import MPTfile +from ..BioLogic import MPTfile, MPTfileCSV testdata_dir = os.path.join(os.path.dirname(__file__), 'testdata') @@ -12,6 +12,20 @@ testdata_dir = os.path.join(os.path.dirname(__file__), 'testdata') def test_open_MPT(): mpt1, comments = MPTfile(os.path.join(testdata_dir, 'bio-logic1.mpt')) eq_(comments, []) + eq_(mpt1.dtype.names, ("mode", "ox/red", "error", "control changes", + "Ns changes", "counter inc.", "time/s", + "control/V/mA", "Ewe/V", "dQ/mA.h", "P/W", + "I/mA", "(Q-Qo)/mA.h", "x")) + + +@raises(ValueError) +def test_open_MPT_fails_for_bad_file(): + mpt1 = MPTfile(os.path.join(testdata_dir, 'bio-logic1.mpr')) + + +def test_open_MPT_csv(): + mpt1, comments = MPTfileCSV(os.path.join(testdata_dir, 'bio-logic1.mpt')) + eq_(comments, []) eq_(mpt1.fieldnames, ["mode", "ox/red", "error", "control changes", "Ns changes", "counter inc.", "time/s", "control/V/mA", "Ewe/V", "dq/mA.h", "P/W", @@ -19,5 +33,5 @@ def test_open_MPT(): @raises(ValueError) -def test_open_MPT_fails_for_bad_file(): - mpt1 = MPTfile(os.path.join(testdata_dir, 'bio-logic1.mpr')) +def test_open_MPT_csv_fails_for_bad_file(): + mpt1 = MPTfileCSV(os.path.join(testdata_dir, 'bio-logic1.mpr'))