Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 0.7.0 #94

Merged
merged 27 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0760850
Update version to develop
ephathaway Feb 6, 2021
a038844
Merge pull request #84 from BEL-Public/update-dev-0.6.2
ephathaway Feb 8, 2021
ea821a3
Add(bin): script to compare two MFFs
jusjusjus Apr 12, 2021
4d861f4
Add(mffdiff): compare signal1.bin
jusjusjus Apr 12, 2021
ccd5212
Add(setup): mffdiff.py to package
jusjusjus Apr 12, 2021
d6f4d35
Add(mffdiff): args for files
jusjusjus Apr 12, 2021
9c1e370
Add(mffdiff): description
jusjusjus Apr 12, 2021
673adc4
Chore: update changelog
jusjusjus Apr 13, 2021
d61c27c
Chg(mffdiff): ref. code, add docs
jusjusjus Apr 14, 2021
4f3cfaf
Merge pull request #85 from BEL-Public/add-mffdiff
jusjusjus Apr 20, 2021
4b3e9c2
Chore: update to v0.6.3-develop
jusjusjus May 21, 2021
0314d2e
Merge pull request #88 from BEL-Public/update-from-0.6.3
ephathaway May 21, 2021
41a90d4
MAINT: Use FileNotFound rather than ValueError
larsoner Jun 3, 2021
ebe7883
Use tmpdir fixture
ephathaway Jun 3, 2021
fb29322
FIX: Remove call to conversion target
larsoner Jun 5, 2021
387de96
FIX: install stubs for mypy types
ephathaway Jun 9, 2021
ca2eafc
Merge pull request #89 from larsoner/exc_class
ephathaway Jun 11, 2021
868b82b
feat: add ability to read bad channels from infoN.xml files
damian5710 Sep 20, 2021
b055809
test: add unit test for DataInfo.channels property
damian5710 Sep 20, 2021
aa8daf0
docs: update CHANGELOG.md file
damian5710 Sep 20, 2021
3f3b27f
test: update example_2.json file to include `channels` field
damian5710 Sep 20, 2021
8515824
Merge pull request #91 from BEL-Public/feature-get-badChs-from-data-info
damian5710 Sep 20, 2021
16ea6c2
perf(reader): cache _blobs
ianbrown9475 Jan 19, 2022
383f08e
docs: update changelog
ianbrown9475 Jan 20, 2022
6ff94d1
Merge pull request #93 from BEL-Public/cache-blobs
ianbrown9475 Jan 20, 2022
16bef8d
chore: bump version to 0.7.0
ianbrown9475 Jan 24, 2022
6a18706
fix(mffdiff): use mff_flavor instead of flavor
ianbrown9475 Jan 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ coverage.xml
*.cover
.hypothesis/
.pytest_cache/
test.xml
examples/example_1.mfz

# Translations
*.mo
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.0] - 2022-01-24
### Added
- Ability to read bad channels from infoN.xml files.
- script `mffdiff.py` to compare two MFF files

### Changed
- cache `Reader._blobs`

## [0.6.3] - 2021-05-17
### Added
- dependency Deprecated
Expand Down Expand Up @@ -55,7 +63,8 @@ In `Reader.get_physical_samples_from_epoch()`:
- Parse key elements in categories.xml files with `mffpy.xml_files.Categories` class.
- Incorporate `cached_property` dependency into `mffpy` library.

[Unreleased]: https://github.com/bel-public/mffpy/compare/v0.6.3...HEAD
[Unreleased]: https://github.com/bel-public/mffpy/compare/v0.7.0...HEAD
[0.7.0]: https://github.com/bel-public/mffpy/compare/v0.6.3...v0.7.0
[0.6.3]: https://github.com/bel-public/mffpy/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/bel-public/mffpy/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/bel-public/mffpy/compare/v0.6.0...v0.6.1
Expand Down
111 changes: 111 additions & 0 deletions bin/mffdiff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python
"""
mffdiff.py

Compare the content of two MFF files
"""
from os.path import join
from subprocess import check_output
from mffpy import Reader
from argparse import ArgumentParser

char_limit = 50

parser = ArgumentParser(description="""
compare the contents of two MFF files
""")
parser.add_argument('leftpath', type=str, help="MFF file to compare")
parser.add_argument('rightpath', type=str, help="second MFF file to compare")
args = parser.parse_args()

left_mff = Reader(args.leftpath)
right_mff = Reader(args.rightpath)


def getnested(instance, props, callback=None):
"""get nested property of instance

This is a recursive function to access a nested property, e.g. for
`instance.a.b.c`, props is `['a', 'b', 'c']`. `callback` is optionally
applied to the final value.

Parameters
----------
instance:
instance that has a property `props[0]`
props:
nested property split into a list
callback:
is optionally applied to the output value
"""
if props:
val = getattr(instance, props[0])
return getnested(val, props[1:], callback)

return callback(instance) if callback else instance


def compare_raw(filename):
"""compare file in MFF using `$ diff`"""
leftfile = join(args.leftpath, filename)
rightfile = join(args.rightpath, filename)
try:
check_output(['diff', leftfile, rightfile])
status = 'match'
except BaseException:
status = 'diff'

print(f">>> {status} @ file '{filename}'")


def compare(prop: str, callback=None, info: str = ''):
"""compare property between left_mff and right_mff

Parameters
----------
prop:
string specifying a nested property of `mffpy.Reader` instance
callback:
post processing of the value of `prop`
info:
additional note of the nested property
"""
props = prop.split('.')
try:
left_value = getnested(left_mff, props, callback)
right_value = getnested(right_mff, props, callback)
left_msg = str(left_value)[:char_limit]
right_msg = str(right_value)[:char_limit]
if left_value != right_value:
msg = f'\t{left_msg} != {right_msg}'
status = 'diff'
else:
msg = ''
status = 'match'

except BaseException as err:
status = 'error'
msg = str(err)[:char_limit] + '\n'
left_value = None

print(f">>> {status} @ reader_instance.{prop} "
f"{': ' + info if info else ''}")
if msg:
print(msg)

return left_value if status == 'match' else None


simple_props = ['mff_flavor', 'sampling_rates', 'durations', 'startdatetime',
'units', 'num_channels', 'categories.categories']

print(f"Comparing {args.leftpath} with {args.rightpath}")
for prop in simple_props:
compare(prop)

epoch_count = compare('epochs.epochs', lambda e: len(e), 'len(..)')
if epoch_count is not None:
for i in range(epoch_count):
compare('epochs.epochs', lambda e: e[i].content, f"[{i}].content")

out = compare_raw('signal1.bin')
1 change: 1 addition & 0 deletions examples/example_1.mff/info1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</EEG>
</fileDataType>
</generalInformation>
<channels exclusion="badChannels">31 47 65 79</channels>
<filters>
<filter>
<beginTime>0</beginTime>
Expand Down
1 change: 1 addition & 0 deletions examples/example_2.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"sensorLayoutName": "HydroCel GSN 256 1.0",
"montageName": "HydroCel GSN 256 1.0"
},
"channels": [],
"filters": [],
"calibrations": {}
},
Expand Down
6 changes: 1 addition & 5 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ examples/example_1.mfz: examples/example_1.mff
# zip -Z store -r -j ./examples/example_1.mfz ./examples/example_1.mff
-python ./bin/mff2mfz.py ./examples/example_1.mff


# some tests depend on the existence of a zipped version of
# 'examples/example_1.mff/'
test: examples/example_1.mfz
test:
mypy --ignore-missing-imports mffpy
pytest --cov

clean:
-rm examples/example_1.mfz
-rm -rf .cache
5 changes: 3 additions & 2 deletions mffpy/mffdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ def filename(self, basename: str) -> str:
if basename in files:
return join(self._mffname, basename) + ext
else:
raise ValueError(f"No file with basename {basename} \
in directory {super().__str__()}.")
raise FileNotFoundError(
f"No file with basename {basename} "
f"in directory {super().__str__()}.")

def __contains__(self, filename: str) -> bool:
return exists(filename)
Expand Down
4 changes: 2 additions & 2 deletions mffpy/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def epochs(self) -> Epochs:
# Attempt to add category names to the `Epoch` objects in `epochs`
try:
categories = self.categories
except (ValueError, AssertionError):
except (FileNotFoundError, AssertionError):
print('categories.xml not found or of wrong type. '
'`Epoch.name` will default to "epoch" for all epochs.')
return epochs
Expand Down Expand Up @@ -208,7 +208,7 @@ def num_channels(self) -> Dict[str, int]:
for fn, bin_file in self._blobs.items()
}

@property
@cached_property
def _blobs(self) -> Dict[str, bin_files.BinFile]:
"""return dictionary of `BinFile` data readers by signal type"""
__blobs = {}
Expand Down
32 changes: 32 additions & 0 deletions mffpy/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Copyright 2019 Brain Electrophysiology Laboratory Company LLC

Licensed under the ApacheLicense, Version 2.0(the "License");
you may not use this module except in compliance with the License.
You may obtain a copy of the License at:

http: // www.apache.org / licenses / LICENSE - 2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied.
"""

from glob import glob
import os.path as op
from zipfile import ZipFile, ZIP_STORED

import pytest


@pytest.fixture(scope='session', autouse=True)
def ensure_mfz():
"""Ensure that the mfz file exists."""
fname = op.join(
op.dirname(__file__), '..', '..', 'examples', 'example_1.mfz')
if not op.isfile(fname):
with ZipFile(fname, mode='w', compression=ZIP_STORED) as zf:
for content_filename in glob(op.join(fname[:-3] + 'mff', '*')):
arc_filename = op.basename(content_filename)
zf.write(content_filename, arcname=arc_filename)
6 changes: 4 additions & 2 deletions mffpy/tests/test_dict2xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied.
"""
from os.path import join

from ..dict2xml import dict2xml, TEXT, ATTR


def test_dict2xml():
def test_dict2xml(tmpdir):
rootname = 'myroot'
content = {
'a': {TEXT: '35', ATTR: {'hello': 'world'}},
'b': [{TEXT: 'b' + str(i+1)} for i in range(2)]
}
elem = dict2xml(content, rootname=rootname)
elem.write('test.xml')
elem.write(join(str(tmpdir), 'test.xml'))
root = elem.getroot()
a = root.find('a')
bs = root.findall('b')
Expand Down
Loading