Source code for ipfx.x_to_nwb.conversion_utils
"""
Miscellaneous helper routines for the ABF/DAT to NWB v2 (aka X to NWB) conversion
functionality.
"""
import math
from pkg_resources import get_distribution, DistributionNotFound
import os
from subprocess import Popen, PIPE
import numpy as np
from pynwb.icephys import CurrentClampStimulusSeries, VoltageClampStimulusSeries, CurrentClampSeries, \
VoltageClampSeries, IZeroClampSeries
try:
from pynwb.form.backends.hdf5.h5_utils import H5DataIO
except ModuleNotFoundError:
from hdmf.backends.hdf5.h5_utils import H5DataIO
PLACEHOLDER = "PLACEHOLDER"
V_CLAMP_MODE = 0
I_CLAMP_MODE = 1
I0_CLAMP_MODE = 2
# TODO Use the pint package if doing that manually gets too involved
[docs]def parseUnit(unitString):
"""
Split a SI unit string with prefix into the base unit and the prefix (as number).
"""
if unitString == "pA":
return 1e-12, "A"
elif unitString == "nA":
return 1e-9, "A"
elif unitString == "A":
return 1.0, "A"
elif unitString == "mV":
return 1e-3, "V"
elif unitString == "V":
return 1.0, "V"
else:
raise ValueError(f"Unsupported unit string {unitString}.")
[docs]def getStimulusSeriesClass(clampMode):
"""
Return the appropriate pynwb stimulus class for the given clamp mode.
"""
if clampMode == V_CLAMP_MODE:
return VoltageClampStimulusSeries
elif clampMode == I_CLAMP_MODE:
return CurrentClampStimulusSeries
elif clampMode == I0_CLAMP_MODE:
return None
else:
raise ValueError(f"Unsupported clamp mode {clampMode}.")
[docs]def getAcquiredSeriesClass(clampMode):
"""
Return the appropriate pynwb acquisition class for the given clamp mode.
"""
if clampMode == V_CLAMP_MODE:
return VoltageClampSeries
elif clampMode == I_CLAMP_MODE:
return CurrentClampSeries
elif clampMode == I0_CLAMP_MODE:
return IZeroClampSeries
else:
raise ValueError(f"Unsupported clamp mode {clampMode}.")
[docs]def createSeriesName(prefix, number, total):
"""
Format a unique series group name of the form `prefix_XXX` where `XXX` is
the formatted `number` long enough for `total` number of groups.
"""
return f"{prefix}_{number:0{math.ceil(math.log(total, 10))}d}", number + 1
[docs]def createCycleID(numbers, total):
"""
Create an integer from all numbers which is unique for that combination.
:param: numbers:
Iterable holding non-negative integer numbers
:param: total:
Total number of TimeSeries written to the NWB file
"""
assert total > 0, f"Unexpected value for total {total}"
places = max(math.ceil(math.log(total, 10)), 1)
result = 0
for idx, n in enumerate(reversed(numbers)):
assert n >= 0, f"Unexpected value {n} at index {idx}"
assert n < 10**places, f"Unexpected value {n} which is larger than {total}"
result += n * (10**(idx * places))
return result
[docs]def convertDataset(array, compression):
"""
Convert to FP32 and optionally request compression for the given array and return it wrapped.
"""
data = array.astype(np.float32)
if compression:
return H5DataIO(data=data, compression=True, chunks=True, shuffle=True, fletcher32=True)
return data
[docs]def getPackageInfo():
"""
Return a dictionary with version information for the allensdk package
"""
def get_git_version():
"""
Returns the project version as derived by git.
"""
path = os.path.dirname(__file__)
branch = Popen(f'git -C "{path}" rev-parse --abbrev-ref HEAD', stdout=PIPE,
shell=True).stdout.read().rstrip().decode('ascii')
rev = Popen(f'git -C "{path}" describe --always --tags', stdout=PIPE,
shell=True).stdout.read().rstrip().decode('ascii')
if branch.startswith('fatal') or rev.startswith('fatal'):
raise ValueError("Could not determine git version")
return f"({branch}) {rev}"
try:
package_version = get_distribution('allensdk').version
except DistributionNotFound: # not installed as a package
package_version = None
try:
git_version = get_git_version()
except ValueError: # not in a git repostitory
git_version = None
version_info = {"repo": "https://github.com/AllenInstitute/ipfx",
"package_version": "Unknown",
"git_revision": "Unknown"}
if package_version:
version_info["package_version"] = package_version
if git_version:
version_info["git_revision"] = git_version
return version_info
[docs]def getStimulusRecordIndex(sweep):
return sweep.StimCount - 1
[docs]def getChannelRecordIndex(pgf, sweep, trace):
"""
Given a pgf node, a SweepRecord and TraceRecord this returns the
corresponding `ChannelRecordStimulus` node as index.
"""
stimRec = pgf[getStimulusRecordIndex(sweep)]
for idx, channelRec in enumerate(stimRec):
if channelRec.AdcChannel == trace.AdcChannel:
return idx
return None
[docs]def clampModeToString(clampMode):
"""
Return the given clamp mode as human readable string. Useful for error
messages.
"""
if clampMode == I_CLAMP_MODE:
return "I_CLAMP_MODE"
elif clampMode == V_CLAMP_MODE:
return "V_CLAMP_MODE"
elif clampMode == I0_CLAMP_MODE:
return "I0_CLAMP_MODE"
else:
raise ValueError(f"Unknown clampMode {clampMode}")