myokit 1.37.0__py3-none-any.whl → 1.37.2__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/__init__.py +2 -2
- myokit/_aux.py +4 -0
- myokit/_datablock.py +10 -10
- myokit/_datalog.py +55 -11
- myokit/_myokit_version.py +1 -1
- myokit/_sim/cvodessim.py +3 -3
- 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/__init__.py +4 -0
- myokit/formats/heka/_patchmaster.py +408 -156
- myokit/formats/sbml/__init__.py +21 -1
- myokit/formats/sbml/_api.py +160 -6
- myokit/formats/sbml/_exporter.py +53 -0
- myokit/formats/sbml/_writer.py +355 -0
- 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 +6 -0
- myokit/tests/test_formats_sbml.py +57 -1
- myokit/tests/test_sbml_api.py +90 -0
- myokit/tests/test_sbml_export.py +327 -0
- {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/LICENSE.txt +1 -1
- {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/METADATA +4 -4
- {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/RECORD +45 -36
- {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/WHEEL +1 -1
- {myokit-1.37.0.dist-info → myokit-1.37.2.dist-info}/entry_points.txt +0 -0
- {myokit-1.37.0.dist-info → myokit-1.37.2.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
|
|
|
@@ -966,33 +973,65 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
966
973
|
|
|
967
974
|
# Add meta data
|
|
968
975
|
log.meta['time'] = self._time.strftime(myokit.DATE_FORMAT)
|
|
969
|
-
a = self.amplifier_state()
|
|
970
976
|
t = self[0][0] if len(self) and len(self[0]) else None
|
|
971
|
-
log.meta['current_gain_mV_per_pA'] = a.current_gain()
|
|
972
|
-
log.meta['filter1'] = a.filter1()
|
|
973
977
|
if t is not None:
|
|
974
978
|
log.meta['r_pipette_MOhm'] = t.r_pipette()
|
|
975
979
|
log.meta['r_seal_MOhm'] = t.r_seal()
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
log.meta['
|
|
981
|
-
log.meta['
|
|
982
|
-
|
|
983
|
-
log.meta['
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
a.
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
980
|
+
a = self.amplifier_state()
|
|
981
|
+
amps = [a] if a is not None else self.amplifier_states()
|
|
982
|
+
for k, a in enumerate(amps):
|
|
983
|
+
pre = '' if len(amps) == 1 else f'amp{1 + k}_'
|
|
984
|
+
log.meta[f'{pre}current_gain_mV_per_pA'] = a.current_gain()
|
|
985
|
+
log.meta[f'{pre}filter1'] = a.filter1_str()
|
|
986
|
+
log.meta[f'{pre}filter2'] = a.filter2_str()
|
|
987
|
+
log.meta[f'{pre}stimulus_filter'] = a.stimulus_filter_str()
|
|
988
|
+
log.meta[f'{pre}ljp_correction_mV'] = a.ljp()
|
|
989
|
+
log.meta[f'{pre}voltage_offset_mV'] = a.v_off()
|
|
990
|
+
log.meta[f'{pre}holding_potential_mV'] = a.v_hold()
|
|
991
|
+
if a.c_fast_enabled():
|
|
992
|
+
log.meta[f'{pre}c_fast_compensation_enabled'] = 'true'
|
|
993
|
+
log.meta[f'{pre}c_fast_pF'] = a.c_fast()
|
|
994
|
+
else:
|
|
995
|
+
log.meta[f'{pre}c_fast_compensation_enabled'] = 'false'
|
|
996
|
+
log.meta[f'{pre}c_slow_pF'] = a.c_slow()
|
|
997
|
+
if a.c_slow_enabled():
|
|
998
|
+
log.meta[f'{pre}c_slow_compensation_enabled'] = 'true'
|
|
999
|
+
log.meta[f'{pre}c_slow_range'] = a.c_slow_range()
|
|
1000
|
+
css = a.c_slow_auto_settings()
|
|
1001
|
+
log.meta[f'{pre}c_slow_auto_amplitude_mV'] = css[0]
|
|
1002
|
+
log.meta[f'{pre}c_slow_auto_cycles'] = css[1]
|
|
1003
|
+
log.meta[f'{pre}c_slow_auto_timeout'] = css[2]
|
|
1004
|
+
else:
|
|
1005
|
+
log.meta[f'{pre}c_slow_compensation_enabled'] = 'false'
|
|
1006
|
+
log.meta[f'{pre}r_series_MOhm'] = a.r_series()
|
|
1007
|
+
if a.r_series_enabled():
|
|
1008
|
+
log.meta[f'{pre}r_series_compensation_enabled'] = 'true'
|
|
1009
|
+
log.meta[f'{pre}r_series_compensation_percent'] = round(
|
|
1010
|
+
a.r_series_fraction() * 100, 1)
|
|
1011
|
+
log.meta[f'{pre}r_series_compensation_tau_us'] = \
|
|
1012
|
+
a.r_series_tau()
|
|
1013
|
+
else:
|
|
1014
|
+
log.meta[f'{pre}r_series_compensation_enabled'] = 'false'
|
|
1015
|
+
|
|
1016
|
+
# Add protocol to meta data
|
|
1017
|
+
stimulus = self.stimulus()
|
|
1018
|
+
log.meta['sampling_interval_ms'] = stimulus.sampling_interval() * 1000
|
|
1019
|
+
log.meta['sweep_count'] = stimulus.sweep_count()
|
|
1020
|
+
stimulus_channel = stimulus.supported_channel()
|
|
1021
|
+
if stimulus_channel is not None:
|
|
1022
|
+
log.meta['amplifier_mode'] = str(stimulus_channel.amplifier_mode())
|
|
1023
|
+
log.meta['protocol'] = stimulus.protocol().code()
|
|
1024
|
+
|
|
1025
|
+
# Add completion status
|
|
1026
|
+
if stimulus_channel is None:
|
|
1027
|
+
c = 'Unknown'
|
|
1028
|
+
elif self.is_complete():
|
|
1029
|
+
c = 'All sweeps ran and completed'
|
|
1030
|
+
elif self._intended_sweep_count == len(self):
|
|
1031
|
+
c = 'Final sweep incomplete'
|
|
994
1032
|
else:
|
|
995
|
-
|
|
1033
|
+
c = f'Ran {len(self)} out of {self._intended_sweep_count} sweeps'
|
|
1034
|
+
log.meta['completed'] = c
|
|
996
1035
|
|
|
997
1036
|
return log
|
|
998
1037
|
|
|
@@ -1007,7 +1046,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1007
1046
|
out.append(f' version {self._file.version()}')
|
|
1008
1047
|
out.append(f'Recorded on {self._time}')
|
|
1009
1048
|
out.append(f'{len(self)} sweeps,'
|
|
1010
|
-
f' {len(self._channel_names)} channels
|
|
1049
|
+
f' {len(self._channel_names)} channels')
|
|
1011
1050
|
|
|
1012
1051
|
# Completion status
|
|
1013
1052
|
c = self._stimulus.supported_channel()
|
|
@@ -1022,64 +1061,81 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1022
1061
|
out.append(f'Incomplete recording: {len(self)} out of'
|
|
1023
1062
|
f' {self._intended_sweep_count} ran.')
|
|
1024
1063
|
|
|
1025
|
-
#
|
|
1064
|
+
# Info from amplifier state
|
|
1026
1065
|
a = self.amplifier_state()
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
out.append('
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
out.append(f'
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1066
|
+
amps = [a] if a is not None else self.amplifier_states()
|
|
1067
|
+
for k, a in enumerate(amps):
|
|
1068
|
+
out.append(f'Information from amplifier state {1 + k}:')
|
|
1069
|
+
out.append(f' Current gain: {a.current_gain()} mV/pA')
|
|
1070
|
+
out.append(f' Filter 1: {a.filter1_str()}')
|
|
1071
|
+
out.append(f' Filter 2: {a.filter2_str()}')
|
|
1072
|
+
out.append(f' Stimulus filter: {a.stimulus_filter_str()}')
|
|
1073
|
+
# Voltage info
|
|
1074
|
+
out.append(f' Holding potential: {a.v_hold()} mV')
|
|
1075
|
+
if a.ljp():
|
|
1076
|
+
out.append(f' LJP correction: {round(a.ljp(), 4)} mV')
|
|
1077
|
+
else:
|
|
1078
|
+
out.append(' LJP correction: no correction')
|
|
1079
|
+
out.append(f' Voltage offset: {a.v_off()} mV')
|
|
1080
|
+
# C fast
|
|
1081
|
+
if a.c_fast_enabled():
|
|
1082
|
+
out.append(f' C fast compensation: {a.c_fast()} pF,')
|
|
1083
|
+
else:
|
|
1084
|
+
out.append(' C fast compensation: not enabled')
|
|
1085
|
+
# C slow
|
|
1086
|
+
if a.c_slow_enabled():
|
|
1087
|
+
out.append(f' C slow compensation: {a.c_slow()} pF')
|
|
1088
|
+
amp, cyc, tim = a.c_slow_auto_settings()
|
|
1089
|
+
out.append(f' C slow auto settings: amplitude {amp} mV,'
|
|
1090
|
+
f' cycles {cyc}, timeout {tim} s')
|
|
1091
|
+
else:
|
|
1092
|
+
out.append(' C slow compensation: not enabled')
|
|
1093
|
+
# Rs comp
|
|
1094
|
+
out.append(f' R series: {a.r_series()} MOhm')
|
|
1095
|
+
if a.r_series_enabled():
|
|
1096
|
+
p = round(a.r_series_fraction() * 100, 1)
|
|
1097
|
+
q = round(a.r_series_tau(), 1)
|
|
1098
|
+
out.append(f' R series compensation: {p} %, {q} us')
|
|
1099
|
+
else:
|
|
1100
|
+
out.append(' R series compensation: not enabled')
|
|
1101
|
+
|
|
1102
|
+
# Info from first trace
|
|
1046
1103
|
if len(self) and len(self[0]):
|
|
1047
1104
|
t = self[0][0]
|
|
1048
1105
|
out.append('Information from first trace:')
|
|
1049
|
-
|
|
1050
|
-
out.append(f'
|
|
1051
|
-
out.append(f'
|
|
1052
|
-
out.append(f'
|
|
1053
|
-
out.append(f' after compensation: {t.r_series_remaining()}'
|
|
1054
|
-
f' MOhm.')
|
|
1055
|
-
out.append(f' C slow: {t.c_slow()} pF.')
|
|
1106
|
+
out.append(f' Pipette resistance: {t.r_pipette()} MOhm')
|
|
1107
|
+
out.append(f' Seal resistance: {t.r_seal()} MOhm')
|
|
1108
|
+
out.append(f' Series resistance: {t.r_series()} MOhm')
|
|
1109
|
+
out.append(f' C slow: {t.c_slow()} pF')
|
|
1056
1110
|
|
|
1057
1111
|
# Sweeps and channels
|
|
1058
1112
|
if verbose:
|
|
1059
1113
|
out.append('-' * 60)
|
|
1060
1114
|
for i, sweep in enumerate(self):
|
|
1061
1115
|
out.append(f'Sweep {i}, label: "{sweep.label()}", recorded on'
|
|
1062
|
-
f' {sweep.time()}
|
|
1116
|
+
f' {sweep.time()}')
|
|
1063
1117
|
if i == 0:
|
|
1064
1118
|
for j, trace in enumerate(self[0]):
|
|
1065
1119
|
out.append(f' Trace {j}, label: "{trace.label()}",'
|
|
1066
1120
|
f' in {trace.time_unit()} and'
|
|
1067
|
-
f' {trace.value_unit()}
|
|
1121
|
+
f' {trace.value_unit()}')
|
|
1068
1122
|
|
|
1069
1123
|
# Stimulus
|
|
1070
1124
|
if verbose:
|
|
1071
1125
|
stim = self._stimulus
|
|
1072
1126
|
out.append('-' * 60)
|
|
1073
|
-
out.append(f'Stimulus "{stim.label()}"
|
|
1074
|
-
out.append(f' {stim.sweep_count()} sweeps
|
|
1075
|
-
out.append(
|
|
1076
|
-
|
|
1127
|
+
out.append(f'Stimulus "{stim.label()}"')
|
|
1128
|
+
out.append(f' {stim.sweep_count()} sweeps')
|
|
1129
|
+
out.append(' Delay between sweeps: '
|
|
1130
|
+
f'{stim.sweep_interval() * 1000} ms')
|
|
1131
|
+
out.append(' Sampling interval: '
|
|
1132
|
+
f'{stim.sampling_interval() * 1000} ms')
|
|
1077
1133
|
for i, ch in enumerate(stim):
|
|
1078
1134
|
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()}
|
|
1135
|
+
f' {ch.amplifier_mode()} mode')
|
|
1136
|
+
out.append(f' Stimulus reconstruction: {ch.support_str()}')
|
|
1081
1137
|
z = ch.zero_segment() or '0 (disabled)'
|
|
1082
|
-
out.append(f' Zero segment: {z}
|
|
1138
|
+
out.append(f' Zero segment: {z}')
|
|
1083
1139
|
for j, seg in enumerate(ch):
|
|
1084
1140
|
out.append(f' Segment {j}, {seg.storage()}')
|
|
1085
1141
|
out.append(f' {seg.segment_class()}, {seg}')
|
|
@@ -1280,7 +1336,6 @@ class Trace(TreeNode):
|
|
|
1280
1336
|
# Meta data
|
|
1281
1337
|
self._r_pipette = None
|
|
1282
1338
|
self._r_seal = None
|
|
1283
|
-
self._r_series_comp = None
|
|
1284
1339
|
self._g_series = None
|
|
1285
1340
|
self._c_slow = None
|
|
1286
1341
|
|
|
@@ -1335,8 +1390,8 @@ class Trace(TreeNode):
|
|
|
1335
1390
|
self._c_slow = reader.read1('d')
|
|
1336
1391
|
handle.seek(i + 184) # TrGSeries = 184; (* LONGREAL *)
|
|
1337
1392
|
self._g_series = reader.read1('d')
|
|
1338
|
-
handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1339
|
-
self._r_series_comp = reader.read1('d')
|
|
1393
|
+
#handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1394
|
+
#self._r_series_comp = reader.read1('d')
|
|
1340
1395
|
|
|
1341
1396
|
# Convert unit
|
|
1342
1397
|
self._data_unit = myokit.parse_unit(self._data_unit)
|
|
@@ -1365,17 +1420,27 @@ class Trace(TreeNode):
|
|
|
1365
1420
|
|
|
1366
1421
|
def r_seal(self):
|
|
1367
1422
|
"""
|
|
1368
|
-
Returns the "seal resistance" (MOhm) determined
|
|
1369
|
-
|
|
1423
|
+
Returns the "seal resistance" (MOhm) that was determined the last time
|
|
1424
|
+
that the "amplifier window" was active (so at some indeterminate time
|
|
1425
|
+
before the Trace was aquired).
|
|
1426
|
+
|
|
1427
|
+
The values returned by :meth:`r_seal` and :meth:`r_pipette` are the
|
|
1428
|
+
same measurement ("R-memb") performed at a different time. The value
|
|
1429
|
+
``r_seal`` is updated whenever the "amplifier window" is active. Using
|
|
1430
|
+
a button or a programmed command, the same value can be stored as
|
|
1431
|
+
``r_pipette``, which should be done before breaking the seal.
|
|
1370
1432
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
and I
|
|
1433
|
+
"R-memb" is determined either using a test pulse or from the current
|
|
1434
|
+
V and I values. If a test pulse is used (the default), this is
|
|
1435
|
+
specified as a ``dV`` from the holding potential, and
|
|
1436
|
+
``R-memb = dV / dI`` where ``dV`` is the difference in command
|
|
1437
|
+
potential and ``dI`` is the measured difference in current (I during
|
|
1438
|
+
the step minus I at holding potential).
|
|
1376
1439
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1440
|
+
Users should be careful when interpreting this value, as it depends on
|
|
1441
|
+
(1) the last time that the amplifier window was active, (2) the test
|
|
1442
|
+
pulse settings, (3) the holding potential, (4) any currents active at
|
|
1443
|
+
the holding potential or during the step.
|
|
1379
1444
|
"""
|
|
1380
1445
|
return self._r_seal * 1e-6
|
|
1381
1446
|
|
|
@@ -1384,26 +1449,16 @@ class Trace(TreeNode):
|
|
|
1384
1449
|
Returns the last (uncompensated) series resistance (MOhm) before
|
|
1385
1450
|
acquiring the trace.
|
|
1386
1451
|
"""
|
|
1387
|
-
return 1e-6 / self._g_series
|
|
1388
|
-
|
|
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
|
|
1452
|
+
return 0 if self._g_series == 0 else 1e-6 / self._g_series
|
|
1396
1453
|
|
|
1397
1454
|
def r_pipette(self):
|
|
1398
1455
|
"""
|
|
1399
|
-
Returns the pipette resistance (MOhm)
|
|
1400
|
-
|
|
1456
|
+
Returns the pipette resistance (MOhm) stored with this Trace, but
|
|
1457
|
+
calculated at an earlier point.
|
|
1401
1458
|
|
|
1402
|
-
|
|
1403
|
-
"R-memb
|
|
1404
|
-
|
|
1405
|
-
time. The intended use is to store the resistance of the pipette tip
|
|
1406
|
-
before touching a cell.
|
|
1459
|
+
Like the "seal resistance", the "pipette resistance" is a stored
|
|
1460
|
+
measurement of what patchmaster calls "R-memb". For details, see
|
|
1461
|
+
:meth:`r_seal`.
|
|
1407
1462
|
"""
|
|
1408
1463
|
return self._r_pipette * 1e-6
|
|
1409
1464
|
|
|
@@ -1549,6 +1604,9 @@ class Trace(TreeNode):
|
|
|
1549
1604
|
# sOld3 = 295; (* BYTE *)
|
|
1550
1605
|
#
|
|
1551
1606
|
# sStimFilterHz = 296; (* LONGREAL *)
|
|
1607
|
+
# 2024-11-07 HEKA support says that sStimFilterHz is "antiquated", and this
|
|
1608
|
+
# value (10kHz=off or 100kHz=on) should be ignored.
|
|
1609
|
+
#
|
|
1552
1610
|
# sRsTau = 304; (* LONGREAL *)
|
|
1553
1611
|
# sDacToAdcDelay = 312; (* LONGREAL *)
|
|
1554
1612
|
# sInputFilterTau = 320; (* LONGREAL *)
|
|
@@ -1578,13 +1636,6 @@ class AmplifierState:
|
|
|
1578
1636
|
"""
|
|
1579
1637
|
Describes the state of an amplifier used by PatchMaster.
|
|
1580
1638
|
"""
|
|
1581
|
-
_filter1_options = [
|
|
1582
|
-
'Bessel 100 kHz',
|
|
1583
|
-
'Bessel 30 kHz',
|
|
1584
|
-
'Bessel 10 kHz',
|
|
1585
|
-
'HQ 30 kHz',
|
|
1586
|
-
]
|
|
1587
|
-
|
|
1588
1639
|
def __init__(self, handle, reader):
|
|
1589
1640
|
|
|
1590
1641
|
# Read properties
|
|
@@ -1594,6 +1645,14 @@ class AmplifierState:
|
|
|
1594
1645
|
handle.seek(i + 8) # sCurrentGain = 8; (* LONGREAL *)
|
|
1595
1646
|
self._current_gain = reader.read1('d')
|
|
1596
1647
|
|
|
1648
|
+
# Holding potential and offsets
|
|
1649
|
+
handle.seek(i + 112) # sVHold = 112; (* LONGREAL *)
|
|
1650
|
+
self._holding = reader.read1('d')
|
|
1651
|
+
handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
|
|
1652
|
+
self._voff = reader.read1('d')
|
|
1653
|
+
handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
|
|
1654
|
+
self._ljp = reader.read1('d')
|
|
1655
|
+
|
|
1597
1656
|
# Series resistance and compensation
|
|
1598
1657
|
handle.seek(i + 40) # sRsFraction = 40; (* LONGREAL *)
|
|
1599
1658
|
self._rs_fraction = reader.read1('d')
|
|
@@ -1610,13 +1669,16 @@ class AmplifierState:
|
|
|
1610
1669
|
handle.seek(i + 64) # sCFastAmp2 = 64; (* LONGREAL *)
|
|
1611
1670
|
self._cf_amp2 = reader.read1('d')
|
|
1612
1671
|
handle.seek(i + 72) # sCFastTau = 72; (* LONGREAL *)
|
|
1613
|
-
self.
|
|
1614
|
-
handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1615
|
-
self._cf_enabled = bool(reader.read1('b'))
|
|
1672
|
+
self._cf_tau2 = reader.read1('d')
|
|
1673
|
+
#handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1674
|
+
#self._cf_enabled = bool(reader.read1('b'))
|
|
1616
1675
|
|
|
1617
1676
|
# Slow capacitance correction
|
|
1618
1677
|
handle.seek(i + 80) # sCSlow = 80; (* LONGREAL *)
|
|
1619
1678
|
self._cs = reader.read1('d')
|
|
1679
|
+
handle.seek(i + 241) # sCSlowRange = 241; (* BYTE *)
|
|
1680
|
+
self._cs_range = CSlowRange(reader.read1('b'))
|
|
1681
|
+
|
|
1620
1682
|
# Auto CSlow settings. See 4.9 "EPC 10 USB Menu" in the manual.
|
|
1621
1683
|
handle.seek(i + 152) # sCSlowStimVolts = 152; (* LONGREAL *)
|
|
1622
1684
|
self._cs_auto_stim = reader.read1('d')
|
|
@@ -1625,71 +1687,82 @@ class AmplifierState:
|
|
|
1625
1687
|
handle.seek(i + 210) # sCSlowCycles = 210; (* INT16 *)
|
|
1626
1688
|
self._cs_auto_cycles = reader.read1('h')
|
|
1627
1689
|
|
|
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
1690
|
# Filter 1
|
|
1691
|
+
# Set with a byte that controls type and frequency
|
|
1635
1692
|
handle.seek(i + 281) # sFilter1 = 281; (* BYTE *)
|
|
1636
|
-
self._filter1 = reader.read1('b')
|
|
1637
|
-
|
|
1638
|
-
#
|
|
1639
|
-
#
|
|
1640
|
-
#
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
#
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
#
|
|
1649
|
-
#
|
|
1650
|
-
#
|
|
1651
|
-
#
|
|
1652
|
-
#
|
|
1653
|
-
#
|
|
1654
|
-
|
|
1655
|
-
#
|
|
1656
|
-
|
|
1657
|
-
#
|
|
1658
|
-
#
|
|
1659
|
-
#
|
|
1660
|
-
#
|
|
1661
|
-
#
|
|
1662
|
-
#
|
|
1663
|
-
|
|
1664
|
-
#
|
|
1665
|
-
#
|
|
1666
|
-
#
|
|
1667
|
-
#
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
#
|
|
1671
|
-
|
|
1672
|
-
|
|
1693
|
+
self._filter1 = Filter1Setting(reader.read1('b'))
|
|
1694
|
+
|
|
1695
|
+
# Filter 2
|
|
1696
|
+
# Set with a type and a separate frequency. The user setting for Filter
|
|
1697
|
+
# 2 is the combined bandwidth of Filter 1 and Filter 2.
|
|
1698
|
+
handle.seek(i + 239) # sF2Response = 239; (* BYTE *)
|
|
1699
|
+
self._filter2_type = Filter2Type(reader.read1('b'))
|
|
1700
|
+
handle.seek(i + 24) # sF2Frequency = 24; (* LONGREAL *)
|
|
1701
|
+
self._filter2_freq_solo = reader.read1('d')
|
|
1702
|
+
handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
|
|
1703
|
+
self._filter2_freq_both = reader.read1('d')
|
|
1704
|
+
|
|
1705
|
+
# Suspect this indicates external filter 2
|
|
1706
|
+
#handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
|
|
1707
|
+
#self._temp['sF2Source'] = reader.read1('b')
|
|
1708
|
+
# Don't know what Mode does.
|
|
1709
|
+
#handle.seek(i + 231) # sF2Mode = 231; (* BYTE *)
|
|
1710
|
+
#self._temp['sF2Mode'] = reader.read1('b')
|
|
1711
|
+
|
|
1712
|
+
#self._temp = {}
|
|
1713
|
+
#handle.seek(i + 320) # sInputFilterTau = 320; (* LONGREAL *)
|
|
1714
|
+
#print('InputFilterTau', reader.read1('d'))
|
|
1715
|
+
#self._temp['sInputFilterTau'] = reader.read1('d')
|
|
1716
|
+
#handle.seek(i + 328) # sOutputFilterTau = 328; (* LONGREAL *)
|
|
1717
|
+
#print('OutputFilterTau', reader.read1('d'))
|
|
1718
|
+
#self._temp['sOutputFilterTau'] = reader.read1('d')
|
|
1719
|
+
#handle.seek(i + 384) # sVmonFiltBandwidth = 384; (* LONGREAL *)
|
|
1720
|
+
#self._temp['sVmonFiltBandwidth'] = reader.read1('d')
|
|
1721
|
+
#handle.seek(i + 392) # sVmonFiltFrequency = 392; (* LONGREAL *)
|
|
1722
|
+
#self._temp['sVmonFiltFrequency'] = reader.read1('d')
|
|
1723
|
+
#print(self._temp)
|
|
1724
|
+
#handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1725
|
+
#self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1726
|
+
|
|
1727
|
+
# Stimulus filter
|
|
1728
|
+
handle.seek(i + 282) # sStimFilterOn = 282; (* BYTE *)
|
|
1729
|
+
self._stimulus_filter = StimulusFilterSetting(reader.read1('b'))
|
|
1673
1730
|
|
|
1674
1731
|
def c_fast(self):
|
|
1675
1732
|
"""
|
|
1676
|
-
Return the capacitance (pF) used in fast capacitance correction
|
|
1677
|
-
|
|
1733
|
+
Return the capacitance (pF) used in fast capacitance correction.
|
|
1734
|
+
|
|
1735
|
+
HEKA amplifiers use a two-component fast capacitance cancellation, with
|
|
1736
|
+
an instantaneous part (component 1) and a delayed part (component 2).
|
|
1737
|
+
This method returns the sum of both values. Individual values can be
|
|
1738
|
+
obtained from :meth:`c_fast_detailed`.
|
|
1678
1739
|
"""
|
|
1679
|
-
#
|
|
1680
|
-
#
|
|
1681
|
-
|
|
1682
|
-
return self._cf_amp2 * 1e12
|
|
1740
|
+
# The total fast capacitance correction is a sum of two capacitances.
|
|
1741
|
+
# See the EPC-10 manual for details.
|
|
1742
|
+
return (self._cf_amp1 + self._cf_amp2) * 1e12
|
|
1683
1743
|
|
|
1684
|
-
def
|
|
1744
|
+
def c_fast_detailed(self):
|
|
1685
1745
|
"""
|
|
1686
|
-
Returns the
|
|
1746
|
+
Returns the detailed fast capacitance correction values:
|
|
1747
|
+
``(c_fast1, c_fast2, tau_fast2)`` in (pF, pF, us).
|
|
1748
|
+
|
|
1749
|
+
Here ``c_fast1`` is the capacitance (pF) of the instantaneous component
|
|
1750
|
+
of cancellation, ``c_fast2`` is the capacitance (pF) of the delayed
|
|
1751
|
+
cancellation, and ``tau_fast2`` is a "tau value" (in microseconds)
|
|
1752
|
+
pertaining to the delayed component (the equivalent value for the
|
|
1753
|
+
instantaneous component is 0).
|
|
1687
1754
|
"""
|
|
1688
|
-
|
|
1755
|
+
# The total fast capacitance correction is a sum of two capacitances.
|
|
1756
|
+
# See the EPC-10 manual for details.
|
|
1757
|
+
return (
|
|
1758
|
+
self._cf_amp1 * 1e12, self._cf_amp2 * 1e12, self._cf_tau2 * 1e6)
|
|
1689
1759
|
|
|
1690
1760
|
def c_fast_enabled(self):
|
|
1691
|
-
"""
|
|
1692
|
-
|
|
1761
|
+
"""
|
|
1762
|
+
Returns ``True`` if the fast capacitance compensation is set to a
|
|
1763
|
+
non-zero capacitance.
|
|
1764
|
+
"""
|
|
1765
|
+
return (self._cf_amp1 + self._cf_amp2) > 0
|
|
1693
1766
|
|
|
1694
1767
|
def c_slow(self):
|
|
1695
1768
|
"""
|
|
@@ -1707,6 +1780,18 @@ class AmplifierState:
|
|
|
1707
1780
|
return (self._cs_auto_stim * 1e3, self._cs_auto_cycles,
|
|
1708
1781
|
self._cs_auto_timeout)
|
|
1709
1782
|
|
|
1783
|
+
def c_slow_enabled(self):
|
|
1784
|
+
"""
|
|
1785
|
+
Returns ``True`` if the C-slow range is not set to 'Off'.
|
|
1786
|
+
"""
|
|
1787
|
+
return self._cs_range is not CSlowRange.OFF
|
|
1788
|
+
|
|
1789
|
+
def c_slow_range(self):
|
|
1790
|
+
"""
|
|
1791
|
+
Returns the :class:`CSlowRange` used for slow capacitance correction.
|
|
1792
|
+
"""
|
|
1793
|
+
return self._cs_range
|
|
1794
|
+
|
|
1710
1795
|
def current_gain(self):
|
|
1711
1796
|
"""
|
|
1712
1797
|
The gain setting for current measurements, in mV/pA.
|
|
@@ -1715,9 +1800,50 @@ class AmplifierState:
|
|
|
1715
1800
|
|
|
1716
1801
|
def filter1(self):
|
|
1717
1802
|
"""
|
|
1718
|
-
Returns a
|
|
1803
|
+
Returns a :class:`Filter1Setting` describing filter 1.
|
|
1804
|
+
|
|
1805
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1806
|
+
"""
|
|
1807
|
+
return self._filter1
|
|
1808
|
+
|
|
1809
|
+
def filter1_str(self):
|
|
1810
|
+
"""
|
|
1811
|
+
Returns a string representing the Filter 1 settings.
|
|
1812
|
+
|
|
1813
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1814
|
+
"""
|
|
1815
|
+
return str(self._filter1)
|
|
1816
|
+
|
|
1817
|
+
def filter2(self):
|
|
1818
|
+
"""
|
|
1819
|
+
Returns a tuple ``(type, f_both, f_solo)`` describing the Filter 2
|
|
1820
|
+
settings, where ``type`` is a :class:`Filter2Type`, where ``f_both`` is
|
|
1821
|
+
the frequency in Hz of both filters combined, and when ``f_solo`` is
|
|
1822
|
+
the frequency of Filter 2 alone.
|
|
1823
|
+
|
|
1824
|
+
The frequency shown in PatchMaster is that of both filters combined.
|
|
1825
|
+
If the "bypass" filter is selected, the frequency settings are unused.
|
|
1826
|
+
|
|
1827
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1828
|
+
"""
|
|
1829
|
+
return (
|
|
1830
|
+
self._filter2_type,
|
|
1831
|
+
self._filter2_freq_both,
|
|
1832
|
+
self._filter2_freq_solo,
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
def filter2_str(self):
|
|
1719
1836
|
"""
|
|
1720
|
-
|
|
1837
|
+
Returns a string describing Filter 2.
|
|
1838
|
+
|
|
1839
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1840
|
+
"""
|
|
1841
|
+
if self._filter2_type is Filter2Type.BYPASS:
|
|
1842
|
+
return str(self._filter2_type)
|
|
1843
|
+
fb = round(self._filter2_freq_both * 1e-3, 2)
|
|
1844
|
+
fs = round(self._filter2_freq_solo * 1e-3, 2)
|
|
1845
|
+
fb = int(fb) if fb == int(fb) else fb
|
|
1846
|
+
return f'{self._filter2_type} {fb} kHz combined, {fs} kHz f2-only'
|
|
1721
1847
|
|
|
1722
1848
|
def ljp(self):
|
|
1723
1849
|
"""
|
|
@@ -1763,12 +1889,136 @@ class AmplifierState:
|
|
|
1763
1889
|
"""
|
|
1764
1890
|
return self._rs_tau * 1e6 if self._rs_enabled else 0
|
|
1765
1891
|
|
|
1766
|
-
def
|
|
1892
|
+
def stimulus_filter(self):
|
|
1767
1893
|
"""
|
|
1768
|
-
Returns
|
|
1894
|
+
Returns a :class:`StimulusFilterSetting` descibing the stimulus filter
|
|
1895
|
+
settings.
|
|
1896
|
+
|
|
1897
|
+
For more information, see :class:`StimulusFilterSetting`.
|
|
1898
|
+
"""
|
|
1899
|
+
return self._stimulus_filter
|
|
1900
|
+
|
|
1901
|
+
def stimulus_filter_str(self):
|
|
1769
1902
|
"""
|
|
1903
|
+
Returns a string descibing the stimulus filter settings.
|
|
1904
|
+
|
|
1905
|
+
For more information, see :class:`StimulusFilterSetting`.
|
|
1906
|
+
"""
|
|
1907
|
+
return self._stimulus_filter
|
|
1908
|
+
|
|
1909
|
+
def v_off(self):
|
|
1910
|
+
""" Returns the used voltage offset (in mV), also called V0. """
|
|
1770
1911
|
return self._voff * 1e3
|
|
1771
1912
|
|
|
1913
|
+
def v_hold(self):
|
|
1914
|
+
"""
|
|
1915
|
+
Returns the holding potential (in mV).
|
|
1916
|
+
|
|
1917
|
+
This is the potential last set in the amplifier window, before any
|
|
1918
|
+
experiments were run.
|
|
1919
|
+
"""
|
|
1920
|
+
return self._holding * 1e3
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
class Filter1Setting(enum.Enum):
|
|
1924
|
+
"""
|
|
1925
|
+
Settings for filter 1, which is applied before filter 2.
|
|
1926
|
+
|
|
1927
|
+
Both filter 1 and filter 2 are hardware filters, implemented on the EPC 9
|
|
1928
|
+
and 10. Filter 1 is used in voltage control, while filter 2 is used to
|
|
1929
|
+
perform filtering before digitisation. Filter 1 is always on, but some
|
|
1930
|
+
amplifiers allow filter 2 to be bypassed. The setting for filter 1
|
|
1931
|
+
determines both the type of filter (Bessel etc.) and the bandwidth. The
|
|
1932
|
+
user setting for filter 2 sets the combined bandwidth of both filters.
|
|
1933
|
+
|
|
1934
|
+
Measurements that passed only through filter 1 can be obtained from
|
|
1935
|
+
Imon1, while Imon2 provides output passed through both filters.
|
|
1936
|
+
"""
|
|
1937
|
+
BESSEL_100K = 0
|
|
1938
|
+
BESSEL_30K = 1
|
|
1939
|
+
BESSEL_10K = 2
|
|
1940
|
+
HQ_30K = 3
|
|
1941
|
+
|
|
1942
|
+
def __str__(self):
|
|
1943
|
+
if self is Filter1Setting.BESSEL_100K:
|
|
1944
|
+
return 'Bessel 100 kHz'
|
|
1945
|
+
elif self is Filter1Setting.BESSEL_30K:
|
|
1946
|
+
return 'Bessel 30 kHz'
|
|
1947
|
+
elif self is Filter1Setting.BESSEL_10K:
|
|
1948
|
+
return 'Bessel 10 kHz'
|
|
1949
|
+
else:
|
|
1950
|
+
return 'HQ 30 kHz'
|
|
1951
|
+
|
|
1952
|
+
|
|
1953
|
+
class Filter2Type(enum.Enum):
|
|
1954
|
+
"""
|
|
1955
|
+
Filter type for filter 2, which is applied after filter 1.
|
|
1956
|
+
|
|
1957
|
+
Unlike Filter 1, this filter can be disabled, and the frequency is set
|
|
1958
|
+
separately.
|
|
1959
|
+
|
|
1960
|
+
For more information on the filters, see :class:`Filter1Setting`.
|
|
1961
|
+
"""
|
|
1962
|
+
BESSEL = 0
|
|
1963
|
+
BUTTERWORTH = 1
|
|
1964
|
+
BYPASS = 2
|
|
1965
|
+
#V_BESSEL = 3 Maybe!
|
|
1966
|
+
#V_Butterworth = 4 Maybe!
|
|
1967
|
+
|
|
1968
|
+
def __str__(self):
|
|
1969
|
+
if self is Filter2Type.BESSEL:
|
|
1970
|
+
return 'Bessel'
|
|
1971
|
+
elif self is Filter2Type.BUTTERWORTH:
|
|
1972
|
+
return 'Butterworth'
|
|
1973
|
+
else:
|
|
1974
|
+
return 'Bypass'
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
class CSlowRange(enum.Enum):
|
|
1978
|
+
"""
|
|
1979
|
+
Available options for slow capacitance cancelling range.
|
|
1980
|
+
|
|
1981
|
+
This doubles up as the on/off setting for slow capacitance cancellation.
|
|
1982
|
+
"""
|
|
1983
|
+
OFF = 0
|
|
1984
|
+
pF30 = 1
|
|
1985
|
+
pF100 = 2
|
|
1986
|
+
pF1000 = 3
|
|
1987
|
+
|
|
1988
|
+
def __str__(self):
|
|
1989
|
+
if self is CSlowRange.OFF:
|
|
1990
|
+
return 'Off'
|
|
1991
|
+
elif self is CSlowRange.pF30:
|
|
1992
|
+
return '30 pF'
|
|
1993
|
+
elif self is CSlowRange.pF100:
|
|
1994
|
+
return '100 pF'
|
|
1995
|
+
else:
|
|
1996
|
+
return '1000 pF'
|
|
1997
|
+
|
|
1998
|
+
|
|
1999
|
+
class StimulusFilterSetting(enum.Enum):
|
|
2000
|
+
"""
|
|
2001
|
+
Setting for the stimulus filter: 20 us (on, default), or 2 us (off).
|
|
2002
|
+
|
|
2003
|
+
The stimulus filter is a 2-pole Bessel filter applied over the stimulus
|
|
2004
|
+
signal to reduce fast capacitative currents. It is applied to voltages, the
|
|
2005
|
+
manual is less clear whether it is applied to currents too.
|
|
2006
|
+
|
|
2007
|
+
The quoted values are stated to be the filter's "rise time", which is the
|
|
2008
|
+
time needed for the signal to go from 10% to 90% of its step response.
|
|
2009
|
+
However, measurements indicate real rise times are longer (approximately
|
|
2010
|
+
40us in the 20us setting), so these values should be treated as nominal
|
|
2011
|
+
rather than actual results.
|
|
2012
|
+
"""
|
|
2013
|
+
BW2 = 0
|
|
2014
|
+
BW20 = 1
|
|
2015
|
+
|
|
2016
|
+
def __str__(self):
|
|
2017
|
+
if self is StimulusFilterSetting.BW2:
|
|
2018
|
+
return 'Bessel 2 us (off)'
|
|
2019
|
+
else:
|
|
2020
|
+
return 'Bessel 20 us (on)'
|
|
2021
|
+
|
|
1772
2022
|
|
|
1773
2023
|
#
|
|
1774
2024
|
# From StimFile_v9.txt
|
|
@@ -1885,6 +2135,8 @@ class Stimulus(TreeNode):
|
|
|
1885
2135
|
handle.seek(start + 144) # stNumberSweeps = 144; (* INT32 *)
|
|
1886
2136
|
self._sweep_count = reader.read1('i')
|
|
1887
2137
|
|
|
2138
|
+
# Filterfactor: Determines desired filtering frequency as a function of
|
|
2139
|
+
# sampling rate. Is overruled by autofilter when enabled.
|
|
1888
2140
|
# handle.seek(start + 136) # stFilterFactor = 136; (* LONGREAL *)
|
|
1889
2141
|
# self._filter_factor = reader.read1('d')
|
|
1890
2142
|
|