Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3088b4a
Fix arf converter (#16)
kylerbrown Aug 17, 2017
ebe8a42
Fix bark-for-each (#17)
gfetterman Aug 18, 2017
f0201fd
Minor refactor (#21)
gfetterman Aug 19, 2017
13ef4e8
datspike bug fixes (#23)
kylerbrown Aug 24, 2017
751524a
Fix #24 (#25)
kylerbrown Sep 5, 2017
3c44dbf
Fix empty tle (#26)
gfetterman Sep 6, 2017
add8eb6
Privatize LazyDict (#28)
gfetterman Sep 7, 2017
700bf79
bark-for-each new features (#29)
gfetterman Sep 10, 2017
9d3a972
Update README.md
Annali95 Sep 15, 2017
77f9d48
Add closers (#32)
gfetterman Oct 2, 2017
9d687dc
Split once (#34)
gfetterman Oct 10, 2017
25ddb32
better short description, fixing spec link (#35)
kylerbrown Oct 20, 2017
37c0534
Update rhd2bark.py (#37)
kylerbrown Dec 13, 2017
c144c19
Convert wav to dat format (#36)
DipanshuSehjal Dec 19, 2017
ffcb8b6
Psg-view added (#20)
Annali95 Mar 27, 2018
b7fcbd7
Forced QT5 backend for OS X (#40)
kylerbrown May 2, 2018
a5e6d0e
B plot (#38)
Annali95 May 2, 2018
f602e6a
Fix out-of-bounds bug when viewing end of file (#42)
gfetterman May 2, 2018
b53108f
Refactor RHD I/O (#41)
gfetterman May 2, 2018
b84c529
Fix Spyking Circus bark conversion (#45)
gfetterman May 10, 2018
f6a964d
dat-ref speedup (#46)
gfetterman May 10, 2018
896d396
Further RHD I/O speedup (#43)
gfetterman May 17, 2018
f349a45
Stream RHD I/O (#51)
gfetterman Jul 25, 2018
a0aea55
Mangle colliding attribute names in arf2bark (#49)
gfetterman Jul 25, 2018
07b466b
Add git instructions (#52)
gfetterman Aug 25, 2018
3615aa2
Write time bins even if amplifiers are absent (#55)
gfetterman Aug 25, 2018
74a5df9
Bugfix for issue 56 (#57)
gfetterman Dec 11, 2018
c1ad7e0
Add .mda I/O (#58)
gfetterman Feb 14, 2019
104c52a
.mda transpose bugfix (#60)
gfetterman Mar 13, 2019
b166c90
Add col/row to spike_times_dataframe_from_array() (#62)
gfetterman Apr 10, 2019
c00226b
Replace StopIteration on exhaustion with return (#65)
gfetterman Jul 28, 2021
d6dfba5
Remove divide-by-zero from tests (#67)
gfetterman Jul 28, 2021
3c10ab1
Replace deprecated matplotlib method (#69)
gfetterman Jul 29, 2021
a6239b6
Update installation instructions (#70)
gfetterman Jul 29, 2021
9892b8f
Enable bark-label-view split/add on OS X (#71)
gfetterman Jul 30, 2021
26dbbf7
Support Intan GUI v3 (#72)
gfetterman Aug 2, 2021
24ff42a
Update python version requirement
gfetterman Aug 18, 2021
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
1 change: 1 addition & 0 deletions bark/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from __future__ import absolute_import
from bark.bark import *
from bark.bark import __version__
from bark import stream
89 changes: 55 additions & 34 deletions bark/bark.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import arrow
import yaml
import numpy as np
import pandas as pd
import functools as ft

BUFFER_SIZE = 10000
DEFAULT_META = '.meta.yaml'

spec_version = "0.2"
__version__ = "0.2"
Expand All @@ -28,9 +30,6 @@
bark: %s
""" % (__version__)

_Units = namedtuple('_Units', ['TIME_UNITS'])
UNITS = _Units(TIME_UNITS=('s', 'samples'))

_pairs = ((None, None), ('UNDEFINED', 0), ('ACOUSTIC', 1), ('EXTRAC_HP', 2),
('EXTRAC_LF', 3), ('EXTRAC_EEG', 4), ('INTRAC_CC', 5),
('INTRAC_VC', 6), ('EVENT', 1000), ('SPIKET', 1001),
Expand All @@ -44,7 +43,7 @@



class LazyDict(dict):
class _LazyDict(dict):
"""Allows lazy loading of data and memoizing the result.

If value is a function, evaluates and replaces with the return
Expand All @@ -68,7 +67,7 @@ def __init__(self, path):
# entries are lazily loaded by creating a dictionary
# with the entry name and a function, that when called
# loads the data. See the custom LazyDict data structure
self.entries = LazyDict({os.path.split(x)[-1]: ft.partial(read_entry, name=x) for x in subdirs})
self.entries = _LazyDict({os.path.split(x)[-1]: ft.partial(read_entry, name=x) for x in subdirs})

def __getitem__(self, item):
return self.entries[item]
Expand All @@ -78,6 +77,15 @@ def __len__(self):

def __contains__(self, item):
return self.entries.__contains__(item)

def close(self):
for e_name in self.entries:
entry = self.entries.get(e_name)
if not callable(entry):
entry.close()
p = entry.path
self.entries[e_name] = ft.partial(read_entry, name=p)
del entry


class Entry():
Expand All @@ -100,6 +108,14 @@ def __contains__(self, item):

def __lt__(self, other):
return self.timestamp < other.timestamp

def close(self):
for ds_name in self.datasets:
dataset = self.datasets.get(ds_name)
if not callable(dataset):
p = dataset.path
self.datasets[ds_name] = ft.partial(read_dataset, fname=p)
del dataset


class Data():
Expand Down Expand Up @@ -168,18 +184,20 @@ def event_columns(dataframe, columns=None):
register with `dataframe`'s columns

Returns:
dict, or None: Columns dictionary for `dataframe`
dict: Columns dictionary for `dataframe`
"""
if columns is None:
return template_columns(dataframe.columns)
for fieldkey in columns:
if fieldkey not in dataframe.columns:
del columns[fieldkey]
for col in dataframe.columns:
if col not in columns:
columns[col] = {'units': None}
if 'units' not in columns[col]:
columns[col]['units'] = None
columns = template_columns(dataframe.columns)
else:
for fieldkey in columns:
if fieldkey not in dataframe.columns:
del columns[fieldkey]
for col in dataframe.columns:
if col not in columns:
columns[col] = {'units': None}
if 'units' not in columns[col]:
columns[col]['units'] = None
return columns


def sampled_columns(data, columns=None):
Expand All @@ -193,7 +211,7 @@ def sampled_columns(data, columns=None):
register with shape of `data`

Returns:
dict, or None: Columns dictionary for `data`
dict: Columns dictionary for `data`

Raises:
ValueError: if the keys in `columns` don't match up with `data`
Expand All @@ -203,16 +221,18 @@ def sampled_columns(data, columns=None):
else:
n_channels = data.shape[1]
if columns is None:
return template_columns(range(n_channels))
if len(columns) != n_channels:
raise ValueError(
'the columns attribute does not match the number of columns')
for i in range(n_channels):
if i not in columns:
columns = template_columns(range(n_channels))
else:
if len(columns) != n_channels:
raise ValueError(
'the columns attribute is missing column {}'.format(i))
if 'units' not in columns[i]:
columns[i]['units'] = None
'the columns attribute does not match the number of columns')
for i in range(n_channels):
if i not in columns:
raise ValueError(
'the columns attribute is missing column {}'.format(i))
if 'units' not in columns[i]:
columns[i]['units'] = None
return columns


def write_sampled(datfile, data, sampling_rate, **params):
Expand All @@ -229,6 +249,7 @@ def write_sampled(datfile, data, sampling_rate, **params):
Returns:
SampledData: sampled dataset containing `data`
"""
path = os.path.abspath(datfile)
if 'columns' not in params:
params['columns'] = sampled_columns(data)
params["dtype"] = data.dtype.str
Expand All @@ -237,7 +258,7 @@ def write_sampled(datfile, data, sampling_rate, **params):
mdata[:] = data[:]
write_metadata(datfile, sampling_rate=sampling_rate, **params)
params['sampling_rate'] = sampling_rate
return SampledData(mdata, datfile, params)
return SampledData(mdata, path, params)


def read_sampled(datfile, mode="r"):
Expand Down Expand Up @@ -274,7 +295,6 @@ def write_events(eventsfile, data, **params):
Returns:
EventData: event dataset containing `data`
"""
import pandas as pd
if 'columns' not in params:
params['columns'] = event_columns(data)
if data.empty and not list(data.columns):
Expand All @@ -293,10 +313,10 @@ def read_events(eventsfile):
Returns:
EventData: event dataset containing `eventsfile`'s data
"""
import pandas as pd
data = pd.read_csv(eventsfile).fillna('')
path = os.path.abspath(eventsfile)
params = read_metadata(eventsfile)
return EventData(data, eventsfile, params)
data = pd.read_csv(eventsfile).fillna('')
return EventData(data, path, params)


def read_dataset(fname):
Expand All @@ -316,7 +336,7 @@ def read_dataset(fname):
return dset


def read_metadata(path, meta='.meta.yaml'):
def read_metadata(path, meta=DEFAULT_META):
"""Loads metadata for a dataset.

Args:
Expand Down Expand Up @@ -348,7 +368,7 @@ def read_metadata(path, meta='.meta.yaml'):
raise FileNotFoundError(m.format(path, metafile))


def write_metadata(path, meta='.meta.yaml', **params):
def write_metadata(path, meta=DEFAULT_META, **params):
"""Writes metadata for a dataset.

Args:
Expand All @@ -359,6 +379,7 @@ def write_metadata(path, meta='.meta.yaml', **params):
**params: all other keyword arguments are treated as dataset attributes,
and added to the meta file
"""
# the two following checks are for backwards-compatibility
if 'n_channels' in params:
del params['n_channels']
if 'n_samples' in params:
Expand Down Expand Up @@ -422,7 +443,7 @@ def create_entry(name, timestamp, parents=False, **attributes):
return read_entry(name)


def read_entry(name, meta=".meta.yaml"):
def read_entry(name, meta=DEFAULT_META):
"""Reads a Bark Entry from a directory.

Args:
Expand All @@ -441,7 +462,7 @@ def read_entry(name, meta=".meta.yaml"):
# datasets are lazily loaded by creating a dictionary
# with the dataset name and a function, that when called
# loads the data. See the custom LazyDict data structure
datasets = LazyDict({name: ft.partial(read_dataset, fname=full_name)
datasets = _LazyDict({name: ft.partial(read_dataset, fname=full_name)
for name, full_name in zip(dset_names, dset_full_names)})
return Entry(datasets, path, attrs)

Expand Down
51 changes: 38 additions & 13 deletions bark/io/arf2bark.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import numpy
import collections as coll

ENTRY_PREFIX = 'entry'

def _parse_args(raw_args):
desc = 'Unspool an HDF5 ARF file into a Bark tree.'
parser = argparse.ArgumentParser(description=desc)
epi = 'Fails if bark_root already exists.'
parser = argparse.ArgumentParser(description=desc, epilog=epi)
parser.add_argument('-v',
'--verbose',
help='increase output verbosity',
Expand All @@ -19,8 +22,12 @@ def _parse_args(raw_args):
'--timezone',
help='timezone for data, tz database format (default is "America/Chicago")',
default=None)
parser.add_argument('-m',
'--mangle-prefix',
help='prefix for names which collide with bark function arguments (default: {})'.format(ENTRY_PREFIX),
default=ENTRY_PREFIX)
parser.add_argument('arf_file', help='ARF file to convert')
parser.add_argument('root_parent', help='directory in which to place the Bark Root')
parser.add_argument('bark_root', help='location of new bark root')
return parser.parse_args(raw_args)

def copy_attrs(attrs):
Expand All @@ -30,11 +37,8 @@ def copy_attrs(attrs):
na.update({k: v.decode() for k,v in na.items() if isinstance(v, bytes)})
return na

def arf2bark(arf_file, root_parent, timezone, verbose):
def arf2bark(arf_file, root_path, timezone, verbose, mangle_prefix=ENTRY_PREFIX):
with arf.open_file(arf_file, 'r') as af:
# root
root_dirname = os.path.splitext(arf_file)[0]
root_path = os.path.join(os.path.abspath(root_parent), root_dirname)
os.mkdir(root_path)
root = bark.Root(root_path)
if verbose:
Expand All @@ -45,6 +49,21 @@ def arf2bark(arf_file, root_parent, timezone, verbose):
if isinstance(entry, h5py.Group): # entries
entry_path = os.path.join(root_path, ename)
entry_attrs = copy_attrs(entry.attrs)
for pos_arg in ('name', 'parents'):
# along with 'timestamp' below, these are positional arguments to create_entry
# for now, I prefer hard-coding them over messing with runtime introspection
new_name = pos_arg
while new_name in entry_attrs:
new_name = '{}_{}'.format(mangle_prefix, new_name)
try:
entry_attrs[new_name] = entry_attrs.pop(pos_arg)
except KeyError:
pass
else:
if verbose:
print('Renamed attribute {} of entry {} to {}'.format(pos_arg,
ename,
new_name))
timestamp = entry_attrs.pop('timestamp')
if timezone:
timestamp = bark.convert_timestamp(timestamp, timezone)
Expand All @@ -62,10 +81,13 @@ def arf2bark(arf_file, root_parent, timezone, verbose):
else:
transfer_dset(ds_name, dataset, entry_path, verbose)
elif isinstance(entry, h5py.Dataset): # top-level datasets
if tle is None:
path = os.path.join(root_path, 'top_level')
tle = bark.create_entry(path, 0, parents=False).path
transfer_dset(ename, entry, tle, verbose)
if arf.is_time_series(entry) or arf.is_marked_pointproc(entry):
if tle is None:
path = os.path.join(root_path, 'top_level')
tle = bark.create_entry(path, 0, parents=False).path
transfer_dset(ename, entry, tle, verbose)
else:
unknown_ds_warning(ename) # and skip, w/o creating TLE
if found_trigin:
print('Warning: found datasets named "trig_in". Jill-created ' +
'"trig_in" datasets segfault when read, so these datasets' +
Expand All @@ -84,6 +106,10 @@ def build_columns(units, column_names=None):
d.update({k: None for k,v in d.items() if (k == 'units' and v == '')})
return cols

def unknown_ds_warning(ds_name):
print('Warning: unknown dataset type - neither time series nor point' +
' process. Skipping dataset ' + ds_name)

def transfer_dset(ds_name, ds, e_path, verbose=False):
ds_attrs = copy_attrs(ds.attrs)
units = ds_attrs.pop('units', None)
Expand All @@ -107,12 +133,11 @@ def transfer_dset(ds_name, ds, e_path, verbose=False):
if verbose:
print('Created event dataset: ' + ds_path)
else:
print('Warning: unknown dataset type - neither time series nor point' +
' process. Skipping dataset ' + ds_name)
unknown_ds_warning(ds_name)

def _main():
args = _parse_args(sys.argv[1:])
arf2bark(args.arf_file, args.root_parent, args.timezone, args.verbose)
arf2bark(args.arf_file, args.bark_root, args.timezone, args.verbose, args.mangle_prefix)

if __name__ == '__main__':
_main()
Expand Down
40 changes: 40 additions & 0 deletions bark/io/datfromwav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from scipy.io import wavfile
import bark, os


def dat_from_wav(wav, barkname, **attrs):
rate, data = wavfile.read(wav)
return bark.write_sampled(barkname, data, rate,**attrs)


def _main():
''' Function for getting commandline args.'''

import argparse

p = argparse.ArgumentParser(description='''
converts wav file to bark format
''')
p.add_argument('wav', help='path to wav file')
p.add_argument('out', help="path to bark file")
p.add_argument("-a",
"--attributes",
action='append',
type=lambda kv: kv.split("="),
dest='keyvalues',
help="extra metadata in the form of KEY=VALUE")

args = p.parse_args()

if args.keyvalues:
dat_from_wav(args.wav,
args.out,
**dict(args.keyvalues))
else:
dat_from_wav(args.wav, args.out)




if __name__ == '__main__':
_main()
Loading