myokit 1.37.0__py3-none-any.whl → 1.37.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- myokit/_aux.py +4 -0
- myokit/_datablock.py +10 -10
- myokit/_datalog.py +40 -5
- myokit/_myokit_version.py +1 -1
- myokit/formats/axon/_abf.py +11 -4
- myokit/formats/diffsl/__init__.py +60 -0
- myokit/formats/diffsl/_ewriter.py +145 -0
- myokit/formats/diffsl/_exporter.py +435 -0
- myokit/formats/heka/_patchmaster.py +337 -113
- myokit/gui/datalog_viewer.py +17 -3
- myokit/tests/data/io/bad1d-2-no-header.zip +0 -0
- myokit/tests/data/io/bad1d-3-no-data.zip +0 -0
- myokit/tests/data/io/bad1d-4-not-a-zip.zip +1 -105
- myokit/tests/data/io/bad1d-5-bad-data-type.zip +0 -0
- myokit/tests/data/io/bad1d-6-time-too-short.zip +0 -0
- myokit/tests/data/io/bad1d-7-0d-too-short.zip +0 -0
- myokit/tests/data/io/bad1d-8-1d-too-short.zip +0 -0
- myokit/tests/data/io/bad2d-2-no-header.zip +0 -0
- myokit/tests/data/io/bad2d-3-no-data.zip +0 -0
- myokit/tests/data/io/bad2d-4-not-a-zip.zip +1 -105
- myokit/tests/data/io/bad2d-5-bad-data-type.zip +0 -0
- myokit/tests/data/io/bad2d-8-2d-too-short.zip +0 -0
- myokit/tests/data/io/block1d.mmt +187 -0
- myokit/tests/data/io/datalog-18-duplicate-keys.csv +4 -0
- myokit/tests/test_aux.py +4 -0
- myokit/tests/test_datablock.py +6 -6
- myokit/tests/test_datalog.py +20 -0
- myokit/tests/test_formats_diffsl.py +728 -0
- myokit/tests/test_formats_exporters_run.py +3 -0
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/METADATA +1 -1
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/RECORD +35 -29
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/LICENSE.txt +0 -0
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/WHEEL +0 -0
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/entry_points.txt +0 -0
- {myokit-1.37.0.dist-info → myokit-1.37.1.dist-info}/top_level.txt +0 -0
|
@@ -625,7 +625,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
625
625
|
|
|
626
626
|
1. In the individual :class:`Trace` objects. This is somewhat
|
|
627
627
|
counter-intuitive as some of these properties (e.g.
|
|
628
|
-
:meth:Trace.
|
|
628
|
+
:meth:Trace.r_pipette()`) were set before a series was
|
|
629
629
|
acquired and do no change between channels or sweeps.
|
|
630
630
|
2. In the series' :class:`AmplifierState`, which can be accessed via
|
|
631
631
|
the :meth:`amplifier_state()` method.
|
|
@@ -679,6 +679,13 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
679
679
|
'Unexpected amplifier state offset: expecting 472 (information'
|
|
680
680
|
' stored with Series) or 0 (information stored in .amp file).')
|
|
681
681
|
|
|
682
|
+
# Read user parameters
|
|
683
|
+
# handle.seek(i + 344) # SeSeUserParams1 = 344; (* 4 x LONGREAL *)
|
|
684
|
+
# p = reader.read('dddd')
|
|
685
|
+
# handle.seek(i + 1120) # SeSeUserParams2 = 1120; (* 4x LONGREAL *)
|
|
686
|
+
# p += reader.read('dddd')
|
|
687
|
+
# All zero!
|
|
688
|
+
|
|
682
689
|
def _read_finalize(self):
|
|
683
690
|
# See TreeNode._read_finalize
|
|
684
691
|
|
|
@@ -969,12 +976,15 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
969
976
|
a = self.amplifier_state()
|
|
970
977
|
t = self[0][0] if len(self) and len(self[0]) else None
|
|
971
978
|
log.meta['current_gain_mV_per_pA'] = a.current_gain()
|
|
972
|
-
log.meta['filter1'] = a.
|
|
979
|
+
log.meta['filter1'] = a.filter1_str()
|
|
980
|
+
log.meta['filter2'] = a.filter2_str()
|
|
981
|
+
log.meta['stimulus_filter'] = a.stimulus_filter_str()
|
|
982
|
+
log.meta['ljp_correction_mV'] = a.ljp()
|
|
983
|
+
log.meta['voltage_offset_mV'] = a.v_off()
|
|
984
|
+
log.meta['holding_potential_mV'] = a.v_hold()
|
|
973
985
|
if t is not None:
|
|
974
986
|
log.meta['r_pipette_MOhm'] = t.r_pipette()
|
|
975
987
|
log.meta['r_seal_MOhm'] = t.r_seal()
|
|
976
|
-
log.meta['ljp_correction_mV'] = a.ljp()
|
|
977
|
-
log.meta['voltage_offset_mV'] = a.v_off()
|
|
978
988
|
if a.c_fast_enabled():
|
|
979
989
|
log.meta['c_fast_compensation_enabled'] = 'true'
|
|
980
990
|
log.meta['c_fast_pF'] = a.c_fast()
|
|
@@ -982,18 +992,42 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
982
992
|
else:
|
|
983
993
|
log.meta['c_fast_compensation_enabled'] = 'false'
|
|
984
994
|
log.meta['c_slow_pF'] = a.c_slow()
|
|
995
|
+
if a.c_slow_enabled():
|
|
996
|
+
log.meta['c_slow_compensation_enabled'] = 'true'
|
|
997
|
+
log.meta['c_slow_range'] = a.c_slow_range()
|
|
998
|
+
css = a.c_slow_auto_settings()
|
|
999
|
+
log.meta['c_slow_auto_amplitude_mV'] = css[0]
|
|
1000
|
+
log.meta['c_slow_auto_cycles'] = css[1]
|
|
1001
|
+
log.meta['c_slow_auto_timeout'] = css[2]
|
|
1002
|
+
else:
|
|
1003
|
+
log.meta['c_slow_compensation_enabled'] = 'false'
|
|
985
1004
|
log.meta['r_series_MOhm'] = a.r_series()
|
|
986
1005
|
if a.r_series_enabled():
|
|
987
1006
|
log.meta['r_series_compensation_enabled'] = 'true'
|
|
988
1007
|
log.meta['r_series_compensation_percent'] = round(
|
|
989
1008
|
a.r_series_fraction() * 100, 1)
|
|
990
1009
|
log.meta['r_series_compensation_tau_us'] = a.r_series_tau()
|
|
991
|
-
if t is not None:
|
|
992
|
-
log.meta['r_series_post_compensation_MOhm'] = \
|
|
993
|
-
t.r_series_remaining()
|
|
994
1010
|
else:
|
|
995
1011
|
log.meta['r_series_compensation_enabled'] = 'false'
|
|
996
1012
|
|
|
1013
|
+
# Add protocol to meta data
|
|
1014
|
+
stimulus = self.stimulus()
|
|
1015
|
+
stimulus_channel = stimulus.supported_channel()
|
|
1016
|
+
if stimulus_channel is not None:
|
|
1017
|
+
log.meta['amplifier_mode'] = str(stimulus_channel.amplifier_mode())
|
|
1018
|
+
log.meta['protocol'] = stimulus.protocol().code()
|
|
1019
|
+
|
|
1020
|
+
# Add completion status
|
|
1021
|
+
if stimulus_channel is None:
|
|
1022
|
+
c = 'Unknown'
|
|
1023
|
+
elif self.is_complete():
|
|
1024
|
+
c = 'All sweeps ran and completed'
|
|
1025
|
+
elif self._intended_sweep_count == len(self):
|
|
1026
|
+
c = 'Final sweep incomplete'
|
|
1027
|
+
else:
|
|
1028
|
+
c = f'Ran {len(self)} out of {self._intended_sweep_count} sweeps'
|
|
1029
|
+
log.meta['completed'] = c
|
|
1030
|
+
|
|
997
1031
|
return log
|
|
998
1032
|
|
|
999
1033
|
def meta_str(self, verbose=False):
|
|
@@ -1007,7 +1041,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1007
1041
|
out.append(f' version {self._file.version()}')
|
|
1008
1042
|
out.append(f'Recorded on {self._time}')
|
|
1009
1043
|
out.append(f'{len(self)} sweeps,'
|
|
1010
|
-
f' {len(self._channel_names)} channels
|
|
1044
|
+
f' {len(self._channel_names)} channels')
|
|
1011
1045
|
|
|
1012
1046
|
# Completion status
|
|
1013
1047
|
c = self._stimulus.supported_channel()
|
|
@@ -1022,64 +1056,79 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1022
1056
|
out.append(f'Incomplete recording: {len(self)} out of'
|
|
1023
1057
|
f' {self._intended_sweep_count} ran.')
|
|
1024
1058
|
|
|
1025
|
-
#
|
|
1059
|
+
# Info from amplifier state
|
|
1026
1060
|
a = self.amplifier_state()
|
|
1027
1061
|
out.append('Information from amplifier state:')
|
|
1028
1062
|
out.append(f' Current gain: {a.current_gain()} mV/pA')
|
|
1063
|
+
out.append(f' Filter 1: {a.filter1_str()}')
|
|
1064
|
+
out.append(f' Filter 2: {a.filter2_str()}')
|
|
1065
|
+
out.append(f' Stimulus filter: {a.stimulus_filter_str()}')
|
|
1066
|
+
# Voltage info
|
|
1067
|
+
out.append(f' Holding potential: {a.v_hold()} mV')
|
|
1029
1068
|
if a.ljp():
|
|
1030
|
-
out.append(' LJP correction
|
|
1031
|
-
|
|
1069
|
+
out.append(f' LJP correction: {round(a.ljp(), 4)} mV')
|
|
1070
|
+
else:
|
|
1071
|
+
out.append(' LJP correction: no correction')
|
|
1072
|
+
out.append(f' Voltage offset: {a.v_off()} mV')
|
|
1073
|
+
# C fast
|
|
1032
1074
|
if a.c_fast_enabled():
|
|
1033
1075
|
out.append(f' C fast compensation: {a.c_fast()} pF,'
|
|
1034
|
-
f' {round(a.c_fast_tau(), 4)} us
|
|
1076
|
+
f' {round(a.c_fast_tau(), 4)} us')
|
|
1077
|
+
else:
|
|
1078
|
+
out.append(' C fast compensation: not enabled')
|
|
1079
|
+
# C slow
|
|
1080
|
+
if a.c_slow_enabled():
|
|
1081
|
+
out.append(f' C slow compensation: {a.c_slow()} pF')
|
|
1082
|
+
amp, cyc, tim = a.c_slow_auto_settings()
|
|
1083
|
+
out.append(f' C slow auto settings: amplitude {amp} mV,'
|
|
1084
|
+
f' cycles {cyc}, timeout {tim} s')
|
|
1035
1085
|
else:
|
|
1036
|
-
out.append(' C
|
|
1037
|
-
|
|
1038
|
-
out.append(f' R series: {a.r_series()} MOhm
|
|
1086
|
+
out.append(' C slow compensation: not enabled')
|
|
1087
|
+
# Rs comp
|
|
1088
|
+
out.append(f' R series: {a.r_series()} MOhm')
|
|
1039
1089
|
if a.r_series_enabled():
|
|
1040
1090
|
p = round(a.r_series_fraction() * 100, 1)
|
|
1041
|
-
|
|
1042
|
-
p
|
|
1043
|
-
out.append(f' R series compensation tau: {p} us')
|
|
1091
|
+
q = round(a.r_series_tau(), 1)
|
|
1092
|
+
out.append(f' R series compensation: {p} %, {q} us')
|
|
1044
1093
|
else:
|
|
1045
1094
|
out.append(' R series compensation: not enabled')
|
|
1095
|
+
|
|
1096
|
+
# Info from first trace
|
|
1046
1097
|
if len(self) and len(self[0]):
|
|
1047
1098
|
t = self[0][0]
|
|
1048
1099
|
out.append('Information from first trace:')
|
|
1049
1100
|
|
|
1050
|
-
out.append(f' Pipette resistance: {t.r_pipette()} MOhm
|
|
1051
|
-
out.append(f' Seal resistance: {t.r_seal()} MOhm
|
|
1052
|
-
out.append(f' Series resistance: {t.r_series()} MOhm
|
|
1053
|
-
out.append(f'
|
|
1054
|
-
f' MOhm.')
|
|
1055
|
-
out.append(f' C slow: {t.c_slow()} pF.')
|
|
1101
|
+
out.append(f' Pipette resistance: {t.r_pipette()} MOhm')
|
|
1102
|
+
out.append(f' Seal resistance: {t.r_seal()} MOhm')
|
|
1103
|
+
out.append(f' Series resistance: {t.r_series()} MOhm')
|
|
1104
|
+
out.append(f' C slow: {t.c_slow()} pF')
|
|
1056
1105
|
|
|
1057
1106
|
# Sweeps and channels
|
|
1058
1107
|
if verbose:
|
|
1059
1108
|
out.append('-' * 60)
|
|
1060
1109
|
for i, sweep in enumerate(self):
|
|
1061
1110
|
out.append(f'Sweep {i}, label: "{sweep.label()}", recorded on'
|
|
1062
|
-
f' {sweep.time()}
|
|
1111
|
+
f' {sweep.time()}')
|
|
1063
1112
|
if i == 0:
|
|
1064
1113
|
for j, trace in enumerate(self[0]):
|
|
1065
1114
|
out.append(f' Trace {j}, label: "{trace.label()}",'
|
|
1066
1115
|
f' in {trace.time_unit()} and'
|
|
1067
|
-
f' {trace.value_unit()}
|
|
1116
|
+
f' {trace.value_unit()}')
|
|
1068
1117
|
|
|
1069
1118
|
# Stimulus
|
|
1070
1119
|
if verbose:
|
|
1071
1120
|
stim = self._stimulus
|
|
1072
1121
|
out.append('-' * 60)
|
|
1073
|
-
out.append(f'Stimulus "{stim.label()}"
|
|
1074
|
-
out.append(f' {stim.sweep_count()} sweeps
|
|
1075
|
-
out.append(f' Delay between sweeps: {stim.sweep_interval()} s
|
|
1076
|
-
out.append(f' Sampling interval: {stim.sampling_interval()} s
|
|
1122
|
+
out.append(f'Stimulus "{stim.label()}"')
|
|
1123
|
+
out.append(f' {stim.sweep_count()} sweeps')
|
|
1124
|
+
out.append(f' Delay between sweeps: {stim.sweep_interval()} s')
|
|
1125
|
+
out.append(f' Sampling interval: {stim.sampling_interval()} s')
|
|
1077
1126
|
for i, ch in enumerate(stim):
|
|
1078
1127
|
out.append(f' Channel {i}, in {ch.unit()}, amplifier in'
|
|
1079
|
-
f' {ch.amplifier_mode()} mode
|
|
1080
|
-
out.append(f' Stimulus reconstruction: {ch.support_str()}
|
|
1128
|
+
f' {ch.amplifier_mode()} mode')
|
|
1129
|
+
out.append(f' Stimulus reconstruction: {ch.support_str()}')
|
|
1081
1130
|
z = ch.zero_segment() or '0 (disabled)'
|
|
1082
|
-
out.append(f' Zero segment: {z}
|
|
1131
|
+
out.append(f' Zero segment: {z}')
|
|
1083
1132
|
for j, seg in enumerate(ch):
|
|
1084
1133
|
out.append(f' Segment {j}, {seg.storage()}')
|
|
1085
1134
|
out.append(f' {seg.segment_class()}, {seg}')
|
|
@@ -1280,7 +1329,6 @@ class Trace(TreeNode):
|
|
|
1280
1329
|
# Meta data
|
|
1281
1330
|
self._r_pipette = None
|
|
1282
1331
|
self._r_seal = None
|
|
1283
|
-
self._r_series_comp = None
|
|
1284
1332
|
self._g_series = None
|
|
1285
1333
|
self._c_slow = None
|
|
1286
1334
|
|
|
@@ -1335,8 +1383,8 @@ class Trace(TreeNode):
|
|
|
1335
1383
|
self._c_slow = reader.read1('d')
|
|
1336
1384
|
handle.seek(i + 184) # TrGSeries = 184; (* LONGREAL *)
|
|
1337
1385
|
self._g_series = reader.read1('d')
|
|
1338
|
-
handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1339
|
-
self._r_series_comp = reader.read1('d')
|
|
1386
|
+
#handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1387
|
+
#self._r_series_comp = reader.read1('d')
|
|
1340
1388
|
|
|
1341
1389
|
# Convert unit
|
|
1342
1390
|
self._data_unit = myokit.parse_unit(self._data_unit)
|
|
@@ -1365,17 +1413,27 @@ class Trace(TreeNode):
|
|
|
1365
1413
|
|
|
1366
1414
|
def r_seal(self):
|
|
1367
1415
|
"""
|
|
1368
|
-
Returns the "seal resistance" (MOhm) determined
|
|
1369
|
-
|
|
1416
|
+
Returns the "seal resistance" (MOhm) that was determined the last time
|
|
1417
|
+
that the "amplifier window" was active (so at some indeterminate time
|
|
1418
|
+
before the Trace was aquired).
|
|
1419
|
+
|
|
1420
|
+
The values returned by :meth:`r_seal` and :meth:`r_pipette` are the
|
|
1421
|
+
same measurement ("R-memb") performed at a different time. The value
|
|
1422
|
+
``r_seal`` is updated whenever the "amplifier window" is active. Using
|
|
1423
|
+
a button or a programmed command, the same value can be stored as
|
|
1424
|
+
``r_pipette``, which should be done before breaking the seal.
|
|
1370
1425
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
and I
|
|
1426
|
+
"R-memb" is determined either using a test pulse or from the current
|
|
1427
|
+
V and I values. If a test pulse is used (the default), this is
|
|
1428
|
+
specified as a ``dV`` from the holding potential, and
|
|
1429
|
+
``R-memb = dV / dI`` where ``dV`` is the difference in command
|
|
1430
|
+
potential and ``dI`` is the measured difference in current (I during
|
|
1431
|
+
the step minus I at holding potential).
|
|
1376
1432
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1433
|
+
Users should be careful when interpreting this value, as it depends on
|
|
1434
|
+
(1) the last time that the amplifier window was active, (2) the test
|
|
1435
|
+
pulse settings, (3) the holding potential, (4) any currents active at
|
|
1436
|
+
the holding potential or during the step.
|
|
1379
1437
|
"""
|
|
1380
1438
|
return self._r_seal * 1e-6
|
|
1381
1439
|
|
|
@@ -1386,24 +1444,14 @@ class Trace(TreeNode):
|
|
|
1386
1444
|
"""
|
|
1387
1445
|
return 1e-6 / self._g_series
|
|
1388
1446
|
|
|
1389
|
-
def r_series_remaining(self):
|
|
1390
|
-
"""
|
|
1391
|
-
Returns the series resistance (MOhm) remaining after compensation.
|
|
1392
|
-
"""
|
|
1393
|
-
# "Absolute fraction of the compensated R-series value. The value
|
|
1394
|
-
# depends on the % of R-series compensation."
|
|
1395
|
-
return (1 / self._g_series - self._r_series_comp) * 1e-6
|
|
1396
|
-
|
|
1397
1447
|
def r_pipette(self):
|
|
1398
1448
|
"""
|
|
1399
|
-
Returns the pipette resistance (MOhm)
|
|
1400
|
-
|
|
1449
|
+
Returns the pipette resistance (MOhm) stored with this Trace, but
|
|
1450
|
+
calculated at an earlier point.
|
|
1401
1451
|
|
|
1402
|
-
|
|
1403
|
-
"R-memb
|
|
1404
|
-
|
|
1405
|
-
time. The intended use is to store the resistance of the pipette tip
|
|
1406
|
-
before touching a cell.
|
|
1452
|
+
Like the "seal resistance", the "pipette resistance" is a stored
|
|
1453
|
+
measurement of what patchmaster calls "R-memb". For details, see
|
|
1454
|
+
:meth:`r_seal`.
|
|
1407
1455
|
"""
|
|
1408
1456
|
return self._r_pipette * 1e-6
|
|
1409
1457
|
|
|
@@ -1578,13 +1626,6 @@ class AmplifierState:
|
|
|
1578
1626
|
"""
|
|
1579
1627
|
Describes the state of an amplifier used by PatchMaster.
|
|
1580
1628
|
"""
|
|
1581
|
-
_filter1_options = [
|
|
1582
|
-
'Bessel 100 kHz',
|
|
1583
|
-
'Bessel 30 kHz',
|
|
1584
|
-
'Bessel 10 kHz',
|
|
1585
|
-
'HQ 30 kHz',
|
|
1586
|
-
]
|
|
1587
|
-
|
|
1588
1629
|
def __init__(self, handle, reader):
|
|
1589
1630
|
|
|
1590
1631
|
# Read properties
|
|
@@ -1594,6 +1635,14 @@ class AmplifierState:
|
|
|
1594
1635
|
handle.seek(i + 8) # sCurrentGain = 8; (* LONGREAL *)
|
|
1595
1636
|
self._current_gain = reader.read1('d')
|
|
1596
1637
|
|
|
1638
|
+
# Holding potential and offsets
|
|
1639
|
+
handle.seek(i + 112) # sVHold = 112; (* LONGREAL *)
|
|
1640
|
+
self._holding = reader.read1('d')
|
|
1641
|
+
handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
|
|
1642
|
+
self._voff = reader.read1('d')
|
|
1643
|
+
handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
|
|
1644
|
+
self._ljp = reader.read1('d')
|
|
1645
|
+
|
|
1597
1646
|
# Series resistance and compensation
|
|
1598
1647
|
handle.seek(i + 40) # sRsFraction = 40; (* LONGREAL *)
|
|
1599
1648
|
self._rs_fraction = reader.read1('d')
|
|
@@ -1611,12 +1660,15 @@ class AmplifierState:
|
|
|
1611
1660
|
self._cf_amp2 = reader.read1('d')
|
|
1612
1661
|
handle.seek(i + 72) # sCFastTau = 72; (* LONGREAL *)
|
|
1613
1662
|
self._cf_tau = reader.read1('d')
|
|
1614
|
-
handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1615
|
-
self._cf_enabled = bool(reader.read1('b'))
|
|
1663
|
+
#handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1664
|
+
#self._cf_enabled = bool(reader.read1('b'))
|
|
1616
1665
|
|
|
1617
1666
|
# Slow capacitance correction
|
|
1618
1667
|
handle.seek(i + 80) # sCSlow = 80; (* LONGREAL *)
|
|
1619
1668
|
self._cs = reader.read1('d')
|
|
1669
|
+
handle.seek(i + 241) # sCSlowRange = 241; (* BYTE *)
|
|
1670
|
+
self._cs_range = CSlowRange(reader.read1('b'))
|
|
1671
|
+
|
|
1620
1672
|
# Auto CSlow settings. See 4.9 "EPC 10 USB Menu" in the manual.
|
|
1621
1673
|
handle.seek(i + 152) # sCSlowStimVolts = 152; (* LONGREAL *)
|
|
1622
1674
|
self._cs_auto_stim = reader.read1('d')
|
|
@@ -1625,71 +1677,78 @@ class AmplifierState:
|
|
|
1625
1677
|
handle.seek(i + 210) # sCSlowCycles = 210; (* INT16 *)
|
|
1626
1678
|
self._cs_auto_cycles = reader.read1('h')
|
|
1627
1679
|
|
|
1628
|
-
# Voltage offsets
|
|
1629
|
-
handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
|
|
1630
|
-
self._voff = reader.read1('d')
|
|
1631
|
-
handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
|
|
1632
|
-
self._ljp = reader.read1('d')
|
|
1633
|
-
|
|
1634
1680
|
# Filter 1
|
|
1681
|
+
# Set with a byte that controls type and frequency
|
|
1635
1682
|
handle.seek(i + 281) # sFilter1 = 281; (* BYTE *)
|
|
1636
|
-
self._filter1 = reader.read1('b')
|
|
1683
|
+
self._filter1 = Filter1Setting(reader.read1('b'))
|
|
1684
|
+
|
|
1685
|
+
# Filter 2
|
|
1686
|
+
# Set with a type and a separate frequency. The user setting for Filter
|
|
1687
|
+
# 2 is the combined bandwidth of Filter 1 and Filter 2.
|
|
1688
|
+
handle.seek(i + 239) # sF2Response = 239; (* BYTE *)
|
|
1689
|
+
self._filter2_type = Filter2Type(reader.read1('b'))
|
|
1690
|
+
handle.seek(i + 24) # sF2Frequency = 24; (* LONGREAL *)
|
|
1691
|
+
self._filter2_freq_solo = reader.read1('d')
|
|
1692
|
+
handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
|
|
1693
|
+
self._filter2_freq_both = reader.read1('d')
|
|
1694
|
+
|
|
1695
|
+
# Suspect this indicates external filter 2
|
|
1696
|
+
#handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
|
|
1697
|
+
#self._temp['sF2Source'] = reader.read1('b')
|
|
1698
|
+
# Don't know what Mode does.
|
|
1699
|
+
#handle.seek(i + 231) # sF2Mode = 231; (* BYTE *)
|
|
1700
|
+
#self._temp['sF2Mode'] = reader.read1('b')
|
|
1637
1701
|
|
|
1638
|
-
#TODO: Add these are proper properties with a docstring'd method that
|
|
1639
|
-
# returns them and says what the units are etc. or stop reading them.
|
|
1640
1702
|
# self._temp = {}
|
|
1641
|
-
# handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1642
|
-
# self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1643
|
-
|
|
1644
|
-
# handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
|
|
1645
|
-
# self._temp['sF2Bandwidth'] = reader.read1('d')
|
|
1646
|
-
# handle.seek(i + 24) # sF2Frequency = 24; (* LONGREAL *)
|
|
1647
|
-
# self._temp['sF2Frequency'] = reader.read1('d')
|
|
1648
|
-
# handle.seek(i + 230) # sHasF2Bypass = 230; (* BYTE *)
|
|
1649
|
-
# self._temp['sHasF2Bypass'] = reader.read1('b')
|
|
1650
|
-
# handle.seek(i + 231) # sF2Mode = 231; (* BYTE *)
|
|
1651
|
-
# self._temp['sF2Mode'] = reader.read1('b')
|
|
1652
|
-
# handle.seek(i + 239) # sF2Response = 239; (* BYTE *)
|
|
1653
|
-
# self._temp['sF2Response'] = reader.read1('b')
|
|
1654
|
-
# handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
|
|
1655
|
-
# self._temp['sF2Source'] = reader.read1('b')
|
|
1656
|
-
|
|
1657
1703
|
# handle.seek(i + 296) # sStimFilterHz = 296; (* LONGREAL *)
|
|
1658
|
-
#
|
|
1704
|
+
# print('STIM', reader.read1('d'))
|
|
1659
1705
|
# handle.seek(i + 320) # sInputFilterTau = 320; (* LONGREAL *)
|
|
1706
|
+
# print('InputFilterTau', reader.read1('d'))
|
|
1660
1707
|
# self._temp['sInputFilterTau'] = reader.read1('d')
|
|
1661
1708
|
# handle.seek(i + 328) # sOutputFilterTau = 328; (* LONGREAL *)
|
|
1709
|
+
# print('OutputFilterTau', reader.read1('d'))
|
|
1662
1710
|
# self._temp['sOutputFilterTau'] = reader.read1('d')
|
|
1663
|
-
|
|
1664
1711
|
# handle.seek(i + 384) # sVmonFiltBandwidth = 384; (* LONGREAL *)
|
|
1665
1712
|
# self._temp['sVmonFiltBandwidth'] = reader.read1('d')
|
|
1666
1713
|
# handle.seek(i + 392) # sVmonFiltFrequency = 392; (* LONGREAL *)
|
|
1667
1714
|
# self._temp['sVmonFiltFrequency'] = reader.read1('d')
|
|
1715
|
+
# handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1716
|
+
# self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1668
1717
|
|
|
1669
|
-
#
|
|
1670
|
-
#
|
|
1671
|
-
|
|
1672
|
-
# self._temp['sLastVHold'] = reader.read1('d')
|
|
1718
|
+
# Stimulus filter
|
|
1719
|
+
handle.seek(i + 282) # sStimFilterOn = 282; (* BYTE *)
|
|
1720
|
+
self._stimulus_filter = StimulusFilterSetting(reader.read1('b'))
|
|
1673
1721
|
|
|
1674
1722
|
def c_fast(self):
|
|
1675
1723
|
"""
|
|
1676
|
-
Return the capacitance (pF) used in fast capacitance correction
|
|
1677
|
-
|
|
1724
|
+
Return the capacitance (pF) used in fast capacitance correction.
|
|
1725
|
+
|
|
1726
|
+
HEKA amplifiers use a two-component fast capacitance cancellation, with
|
|
1727
|
+
an instantaneous part (component 1) and a delayed part (component 2).
|
|
1728
|
+
|
|
1729
|
+
The "total" capacitance, e.g. the sum of components 1 and 2 is
|
|
1730
|
+
returned.
|
|
1678
1731
|
"""
|
|
1679
|
-
#
|
|
1680
|
-
#
|
|
1681
|
-
|
|
1682
|
-
return self._cf_amp2 * 1e12
|
|
1732
|
+
# The total fast capacitance correction is a sum of two capacitances.
|
|
1733
|
+
# See the EPC-10 manual for details.
|
|
1734
|
+
return (self._cf_amp1 + self._cf_amp2) * 1e12
|
|
1683
1735
|
|
|
1684
1736
|
def c_fast_tau(self):
|
|
1685
1737
|
"""
|
|
1686
|
-
Returns the time constant (
|
|
1738
|
+
Returns the time constant (in microseconds) used in fast capacitance
|
|
1739
|
+
correction.
|
|
1740
|
+
|
|
1741
|
+
This is the time constant for the non-instantaneous component of the
|
|
1742
|
+
cancellation, see :meth:`c_fast`.
|
|
1687
1743
|
"""
|
|
1688
1744
|
return self._cf_tau * 1e6
|
|
1689
1745
|
|
|
1690
1746
|
def c_fast_enabled(self):
|
|
1691
|
-
"""
|
|
1692
|
-
|
|
1747
|
+
"""
|
|
1748
|
+
Returns ``True`` if the fast capacitance compensation is set to a
|
|
1749
|
+
non-zero capacitance.
|
|
1750
|
+
"""
|
|
1751
|
+
return (self._cf_amp1 + self._cf_amp2) > 0
|
|
1693
1752
|
|
|
1694
1753
|
def c_slow(self):
|
|
1695
1754
|
"""
|
|
@@ -1707,6 +1766,18 @@ class AmplifierState:
|
|
|
1707
1766
|
return (self._cs_auto_stim * 1e3, self._cs_auto_cycles,
|
|
1708
1767
|
self._cs_auto_timeout)
|
|
1709
1768
|
|
|
1769
|
+
def c_slow_enabled(self):
|
|
1770
|
+
"""
|
|
1771
|
+
Returns ``True`` if the C-slow range is not set to 'Off'.
|
|
1772
|
+
"""
|
|
1773
|
+
return self._cs_range is not CSlowRange.OFF
|
|
1774
|
+
|
|
1775
|
+
def c_slow_range(self):
|
|
1776
|
+
"""
|
|
1777
|
+
Returns the :class:`CSlowRange` used for slow capacitance correction.
|
|
1778
|
+
"""
|
|
1779
|
+
return self._cs_range
|
|
1780
|
+
|
|
1710
1781
|
def current_gain(self):
|
|
1711
1782
|
"""
|
|
1712
1783
|
The gain setting for current measurements, in mV/pA.
|
|
@@ -1715,9 +1786,50 @@ class AmplifierState:
|
|
|
1715
1786
|
|
|
1716
1787
|
def filter1(self):
|
|
1717
1788
|
"""
|
|
1718
|
-
Returns a
|
|
1789
|
+
Returns a :class:`Filter1Setting` describing filter 1.
|
|
1790
|
+
|
|
1791
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1792
|
+
"""
|
|
1793
|
+
return self._filter1
|
|
1794
|
+
|
|
1795
|
+
def filter1_str(self):
|
|
1796
|
+
"""
|
|
1797
|
+
Returns a string representing the Filter 1 settings.
|
|
1798
|
+
|
|
1799
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1719
1800
|
"""
|
|
1720
|
-
return self.
|
|
1801
|
+
return str(self._filter1)
|
|
1802
|
+
|
|
1803
|
+
def filter2(self):
|
|
1804
|
+
"""
|
|
1805
|
+
Returns a tuple ``(type, f_both, f_solo)`` describing the Filter 2
|
|
1806
|
+
settings, where ``type`` is a :class:`Filter2Type`, where ``f_both`` is
|
|
1807
|
+
the frequency in Hz of both filters combined, and when ``f_solo`` is
|
|
1808
|
+
the frequency of Filter 2 alone.
|
|
1809
|
+
|
|
1810
|
+
The frequency shown in PatchMaster is that of both filters combined.
|
|
1811
|
+
If the "bypass" filter is selected, the frequency settings are unused.
|
|
1812
|
+
|
|
1813
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1814
|
+
"""
|
|
1815
|
+
return (
|
|
1816
|
+
self._filter2_type,
|
|
1817
|
+
self._filter2_freq_both,
|
|
1818
|
+
self._filter2_freq_solo,
|
|
1819
|
+
)
|
|
1820
|
+
|
|
1821
|
+
def filter2_str(self):
|
|
1822
|
+
"""
|
|
1823
|
+
Returns a string describing Filter 2.
|
|
1824
|
+
|
|
1825
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1826
|
+
"""
|
|
1827
|
+
if self._filter2_type is Filter2Type.BYPASS:
|
|
1828
|
+
return str(self._filter2_type)
|
|
1829
|
+
fb = round(self._filter2_freq_both * 1e-3, 2)
|
|
1830
|
+
fs = round(self._filter2_freq_solo * 1e-3, 2)
|
|
1831
|
+
fb = int(fb) if fb == int(fb) else fb
|
|
1832
|
+
return f'{self._filter2_type} {fb} kHz combined, {fs} kHz f2-only'
|
|
1721
1833
|
|
|
1722
1834
|
def ljp(self):
|
|
1723
1835
|
"""
|
|
@@ -1763,12 +1875,122 @@ class AmplifierState:
|
|
|
1763
1875
|
"""
|
|
1764
1876
|
return self._rs_tau * 1e6 if self._rs_enabled else 0
|
|
1765
1877
|
|
|
1766
|
-
def
|
|
1878
|
+
def stimulus_filter(self):
|
|
1879
|
+
"""
|
|
1880
|
+
Returns a :class:`StimulusFilterSetting` descibing the stimulus filter
|
|
1881
|
+
settings.
|
|
1882
|
+
|
|
1883
|
+
For more information, see :class:`StimulusFilterSetting`.
|
|
1767
1884
|
"""
|
|
1768
|
-
|
|
1885
|
+
return self._stimulus_filter
|
|
1886
|
+
|
|
1887
|
+
def stimulus_filter_str(self):
|
|
1888
|
+
"""
|
|
1889
|
+
Returns a string descibing the stimulus filter settings.
|
|
1890
|
+
|
|
1891
|
+
For more information, see :class:`StimulusFilterSetting`.
|
|
1769
1892
|
"""
|
|
1893
|
+
return self._stimulus_filter
|
|
1894
|
+
|
|
1895
|
+
def v_off(self):
|
|
1896
|
+
""" Returns the used voltage offset (in mV), also called V0. """
|
|
1770
1897
|
return self._voff * 1e3
|
|
1771
1898
|
|
|
1899
|
+
def v_hold(self):
|
|
1900
|
+
""" Returns the holding potential (in mV). """
|
|
1901
|
+
return self._holding * 1e3
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
class Filter1Setting(enum.Enum):
|
|
1905
|
+
"""
|
|
1906
|
+
Settings for filter 1, which is applied before filter 2.
|
|
1907
|
+
|
|
1908
|
+
Both filter 1 and filter 2 are hardware filters, implemented on the EPC 9
|
|
1909
|
+
and 10. Filter 1 is used in voltage control, while filter 2 is used to
|
|
1910
|
+
perform filtering before digitisation. Filter 1 is always on, but some
|
|
1911
|
+
amplifiers allow filter 2 to be bypassed. The setting for filter 1
|
|
1912
|
+
determines both the type of filter (Bessel etc.) and the bandwidth. The
|
|
1913
|
+
user setting for filter 2 sets the combined bandwidth of both filters.
|
|
1914
|
+
|
|
1915
|
+
Measurements that passed only through filter 1 can be obtained from
|
|
1916
|
+
Imon1, while Imon2 provides output passed through both filters.
|
|
1917
|
+
"""
|
|
1918
|
+
BESSEL_100K = 0
|
|
1919
|
+
BESSEL_30K = 1
|
|
1920
|
+
BESSEL_10K = 2
|
|
1921
|
+
HQ_30K = 3
|
|
1922
|
+
|
|
1923
|
+
def __str__(self):
|
|
1924
|
+
if self is Filter1Setting.BESSEL_100K:
|
|
1925
|
+
return 'Bessel 100 kHz'
|
|
1926
|
+
elif self is Filter1Setting.BESSEL_30K:
|
|
1927
|
+
return 'Bessel 30 kHz'
|
|
1928
|
+
elif self is Filter1Setting.BESSEL_10K:
|
|
1929
|
+
return 'Bessel 10 kHz'
|
|
1930
|
+
else:
|
|
1931
|
+
return 'HQ 30 kHz'
|
|
1932
|
+
|
|
1933
|
+
|
|
1934
|
+
class Filter2Type(enum.Enum):
|
|
1935
|
+
"""
|
|
1936
|
+
Setting for filter 2, which is applied after filter 1.
|
|
1937
|
+
|
|
1938
|
+
Unlike Filter 1, this filter can be disabled, and the frequency is set
|
|
1939
|
+
separately.
|
|
1940
|
+
|
|
1941
|
+
For more information on the filters, see :class:`Filter1Setting`.
|
|
1942
|
+
"""
|
|
1943
|
+
BESSEL = 0
|
|
1944
|
+
BUTTERWORTH = 1
|
|
1945
|
+
BYPASS = 2
|
|
1946
|
+
#V_BESSEL = 3 Maybe!
|
|
1947
|
+
#V_Butterworth = 4 Maybe!
|
|
1948
|
+
|
|
1949
|
+
def __str__(self):
|
|
1950
|
+
if self is Filter2Type.BESSEL:
|
|
1951
|
+
return 'Bessel'
|
|
1952
|
+
elif self is Filter2Type.BUTTERWORTH:
|
|
1953
|
+
return 'Butterworth'
|
|
1954
|
+
else:
|
|
1955
|
+
return 'Bypass'
|
|
1956
|
+
|
|
1957
|
+
|
|
1958
|
+
class CSlowRange(enum.Enum):
|
|
1959
|
+
""" Available options for slow capacitance cancelling range. """
|
|
1960
|
+
OFF = 0
|
|
1961
|
+
pF30 = 1
|
|
1962
|
+
pF100 = 2
|
|
1963
|
+
pF1000 = 3
|
|
1964
|
+
|
|
1965
|
+
def __str__(self):
|
|
1966
|
+
if self is CSlowRange.OFF:
|
|
1967
|
+
return 'Off'
|
|
1968
|
+
elif self is CSlowRange.pF30:
|
|
1969
|
+
return '30 pF'
|
|
1970
|
+
elif self is CSlowRange.pF100:
|
|
1971
|
+
return '100 pF'
|
|
1972
|
+
else:
|
|
1973
|
+
return '1000 pF'
|
|
1974
|
+
|
|
1975
|
+
|
|
1976
|
+
class StimulusFilterSetting(enum.Enum):
|
|
1977
|
+
"""
|
|
1978
|
+
Setting for the stimulus filter.
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
The stimulus filter is a 2-pole Bessel filter applied over the stimulus
|
|
1982
|
+
signal to reduce fast capacitative currents. It is applied to voltages, the
|
|
1983
|
+
manual is less clear whether it is applied to currents too.
|
|
1984
|
+
"""
|
|
1985
|
+
BW2 = 0
|
|
1986
|
+
BW20 = 1
|
|
1987
|
+
|
|
1988
|
+
def __str__(self):
|
|
1989
|
+
if self is StimulusFilterSetting.BW2:
|
|
1990
|
+
return 'Bessel 2 us (off)'
|
|
1991
|
+
else:
|
|
1992
|
+
return 'Bessel 20 us (on)'
|
|
1993
|
+
|
|
1772
1994
|
|
|
1773
1995
|
#
|
|
1774
1996
|
# From StimFile_v9.txt
|
|
@@ -1885,6 +2107,8 @@ class Stimulus(TreeNode):
|
|
|
1885
2107
|
handle.seek(start + 144) # stNumberSweeps = 144; (* INT32 *)
|
|
1886
2108
|
self._sweep_count = reader.read1('i')
|
|
1887
2109
|
|
|
2110
|
+
# Filterfactor: Determines desired filtering frequency as a function of
|
|
2111
|
+
# sampling rate. Is overruled by autofilter when enabled.
|
|
1888
2112
|
# handle.seek(start + 136) # stFilterFactor = 136; (* LONGREAL *)
|
|
1889
2113
|
# self._filter_factor = reader.read1('d')
|
|
1890
2114
|
|