myokit 1.36.1__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/__init__.py +6 -19
- myokit/_aux.py +4 -0
- myokit/_datablock.py +55 -65
- myokit/_datalog.py +42 -7
- myokit/_err.py +26 -3
- myokit/_expressions.py +241 -127
- myokit/_model_api.py +19 -13
- myokit/_myokit_version.py +1 -1
- myokit/_sim/jacobian.py +3 -3
- myokit/_sim/openclsim.py +5 -5
- myokit/_sim/rhs.py +1 -1
- myokit/formats/__init__.py +4 -9
- myokit/formats/ansic/_ewriter.py +4 -20
- 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 +345 -115
- myokit/formats/opencl/_ewriter.py +3 -42
- myokit/formats/opencl/template/minilog.py +1 -1
- myokit/formats/sympy/_ereader.py +2 -1
- myokit/formats/wcp/_wcp.py +3 -3
- myokit/gui/datalog_viewer.py +28 -9
- myokit/lib/markov.py +2 -2
- myokit/lib/plots.py +4 -4
- myokit/tests/data/formats/wcp-file-empty.wcp +0 -0
- 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 +16 -16
- myokit/tests/test_datalog.py +24 -1
- myokit/tests/test_expressions.py +532 -251
- myokit/tests/test_formats_ansic.py +6 -18
- myokit/tests/test_formats_cpp.py +0 -5
- myokit/tests/test_formats_cuda.py +7 -15
- myokit/tests/test_formats_diffsl.py +728 -0
- myokit/tests/test_formats_easyml.py +4 -9
- myokit/tests/test_formats_exporters_run.py +3 -0
- myokit/tests/test_formats_latex.py +10 -11
- myokit/tests/test_formats_matlab.py +0 -8
- myokit/tests/test_formats_opencl.py +0 -29
- myokit/tests/test_formats_python.py +2 -19
- myokit/tests/test_formats_stan.py +0 -13
- myokit/tests/test_formats_sympy.py +3 -3
- myokit/tests/test_formats_wcp.py +15 -0
- myokit/tests/test_model.py +20 -20
- myokit/tests/test_parsing.py +19 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/METADATA +1 -1
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/RECORD +65 -58
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/LICENSE.txt +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/WHEEL +0 -0
- {myokit-1.36.1.dist-info → myokit-1.37.1.dist-info}/entry_points.txt +0 -0
- {myokit-1.36.1.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
|
|
|
@@ -928,6 +935,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
928
935
|
include_da = False
|
|
929
936
|
|
|
930
937
|
# Populate log
|
|
938
|
+
log.set_time_key('time')
|
|
931
939
|
if join_sweeps:
|
|
932
940
|
# Join sweeps
|
|
933
941
|
offsets = (self._sweep_starts_r if use_real_start_times
|
|
@@ -964,31 +972,61 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
964
972
|
log.cmeta[name]['unit'] = self._da_units[0]
|
|
965
973
|
|
|
966
974
|
# Add meta data
|
|
967
|
-
log.
|
|
975
|
+
log.meta['time'] = self._time.strftime(myokit.DATE_FORMAT)
|
|
968
976
|
a = self.amplifier_state()
|
|
977
|
+
t = self[0][0] if len(self) and len(self[0]) else None
|
|
969
978
|
log.meta['current_gain_mV_per_pA'] = a.current_gain()
|
|
979
|
+
log.meta['filter1'] = a.filter1_str()
|
|
980
|
+
log.meta['filter2'] = a.filter2_str()
|
|
981
|
+
log.meta['stimulus_filter'] = a.stimulus_filter_str()
|
|
970
982
|
log.meta['ljp_correction_mV'] = a.ljp()
|
|
971
|
-
log.meta['
|
|
983
|
+
log.meta['voltage_offset_mV'] = a.v_off()
|
|
984
|
+
log.meta['holding_potential_mV'] = a.v_hold()
|
|
985
|
+
if t is not None:
|
|
986
|
+
log.meta['r_pipette_MOhm'] = t.r_pipette()
|
|
987
|
+
log.meta['r_seal_MOhm'] = t.r_seal()
|
|
972
988
|
if a.c_fast_enabled():
|
|
973
989
|
log.meta['c_fast_compensation_enabled'] = 'true'
|
|
974
990
|
log.meta['c_fast_pF'] = a.c_fast()
|
|
975
|
-
log.meta['
|
|
991
|
+
log.meta['c_fast_tau_us'] = a.c_fast_tau()
|
|
976
992
|
else:
|
|
977
993
|
log.meta['c_fast_compensation_enabled'] = 'false'
|
|
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'
|
|
978
1004
|
log.meta['r_series_MOhm'] = a.r_series()
|
|
979
1005
|
if a.r_series_enabled():
|
|
980
1006
|
log.meta['r_series_compensation_enabled'] = 'true'
|
|
981
1007
|
log.meta['r_series_compensation_percent'] = round(
|
|
982
1008
|
a.r_series_fraction() * 100, 1)
|
|
1009
|
+
log.meta['r_series_compensation_tau_us'] = a.r_series_tau()
|
|
983
1010
|
else:
|
|
984
1011
|
log.meta['r_series_compensation_enabled'] = 'false'
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
log.meta['
|
|
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
|
|
992
1030
|
|
|
993
1031
|
return log
|
|
994
1032
|
|
|
@@ -1003,7 +1041,7 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1003
1041
|
out.append(f' version {self._file.version()}')
|
|
1004
1042
|
out.append(f'Recorded on {self._time}')
|
|
1005
1043
|
out.append(f'{len(self)} sweeps,'
|
|
1006
|
-
f' {len(self._channel_names)} channels
|
|
1044
|
+
f' {len(self._channel_names)} channels')
|
|
1007
1045
|
|
|
1008
1046
|
# Completion status
|
|
1009
1047
|
c = self._stimulus.supported_channel()
|
|
@@ -1018,62 +1056,79 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1018
1056
|
out.append(f'Incomplete recording: {len(self)} out of'
|
|
1019
1057
|
f' {self._intended_sweep_count} ran.')
|
|
1020
1058
|
|
|
1021
|
-
#
|
|
1059
|
+
# Info from amplifier state
|
|
1022
1060
|
a = self.amplifier_state()
|
|
1023
1061
|
out.append('Information from amplifier state:')
|
|
1024
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')
|
|
1025
1068
|
if a.ljp():
|
|
1026
|
-
out.append(' LJP correction
|
|
1027
|
-
|
|
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
|
|
1028
1074
|
if a.c_fast_enabled():
|
|
1029
1075
|
out.append(f' C fast compensation: {a.c_fast()} pF,'
|
|
1030
|
-
f' {round(a.c_fast_tau(), 4)} us
|
|
1076
|
+
f' {round(a.c_fast_tau(), 4)} us')
|
|
1031
1077
|
else:
|
|
1032
|
-
out.append(' C fast compensation: not enabled
|
|
1033
|
-
|
|
1034
|
-
|
|
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')
|
|
1085
|
+
else:
|
|
1086
|
+
out.append(' C slow compensation: not enabled')
|
|
1087
|
+
# Rs comp
|
|
1088
|
+
out.append(f' R series: {a.r_series()} MOhm')
|
|
1035
1089
|
if a.r_series_enabled():
|
|
1036
1090
|
p = round(a.r_series_fraction() * 100, 1)
|
|
1037
|
-
|
|
1091
|
+
q = round(a.r_series_tau(), 1)
|
|
1092
|
+
out.append(f' R series compensation: {p} %, {q} us')
|
|
1038
1093
|
else:
|
|
1039
1094
|
out.append(' R series compensation: not enabled')
|
|
1095
|
+
|
|
1096
|
+
# Info from first trace
|
|
1040
1097
|
if len(self) and len(self[0]):
|
|
1041
1098
|
t = self[0][0]
|
|
1042
1099
|
out.append('Information from first trace:')
|
|
1043
1100
|
|
|
1044
|
-
out.append(f' Pipette resistance: {t.r_pipette()} MOhm
|
|
1045
|
-
out.append(f' Seal resistance: {t.r_seal()} MOhm
|
|
1046
|
-
out.append(f' Series resistance: {t.r_series()} MOhm
|
|
1047
|
-
out.append(f'
|
|
1048
|
-
f' MOhm.')
|
|
1049
|
-
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')
|
|
1050
1105
|
|
|
1051
1106
|
# Sweeps and channels
|
|
1052
1107
|
if verbose:
|
|
1053
1108
|
out.append('-' * 60)
|
|
1054
1109
|
for i, sweep in enumerate(self):
|
|
1055
1110
|
out.append(f'Sweep {i}, label: "{sweep.label()}", recorded on'
|
|
1056
|
-
f' {sweep.time()}
|
|
1111
|
+
f' {sweep.time()}')
|
|
1057
1112
|
if i == 0:
|
|
1058
1113
|
for j, trace in enumerate(self[0]):
|
|
1059
1114
|
out.append(f' Trace {j}, label: "{trace.label()}",'
|
|
1060
1115
|
f' in {trace.time_unit()} and'
|
|
1061
|
-
f' {trace.value_unit()}
|
|
1116
|
+
f' {trace.value_unit()}')
|
|
1062
1117
|
|
|
1063
1118
|
# Stimulus
|
|
1064
1119
|
if verbose:
|
|
1065
1120
|
stim = self._stimulus
|
|
1066
1121
|
out.append('-' * 60)
|
|
1067
|
-
out.append(f'Stimulus "{stim.label()}"
|
|
1068
|
-
out.append(f' {stim.sweep_count()} sweeps
|
|
1069
|
-
out.append(f' Delay between sweeps: {stim.sweep_interval()} s
|
|
1070
|
-
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')
|
|
1071
1126
|
for i, ch in enumerate(stim):
|
|
1072
1127
|
out.append(f' Channel {i}, in {ch.unit()}, amplifier in'
|
|
1073
|
-
f' {ch.amplifier_mode()} mode
|
|
1074
|
-
out.append(f' Stimulus reconstruction: {ch.support_str()}
|
|
1128
|
+
f' {ch.amplifier_mode()} mode')
|
|
1129
|
+
out.append(f' Stimulus reconstruction: {ch.support_str()}')
|
|
1075
1130
|
z = ch.zero_segment() or '0 (disabled)'
|
|
1076
|
-
out.append(f' Zero segment: {z}
|
|
1131
|
+
out.append(f' Zero segment: {z}')
|
|
1077
1132
|
for j, seg in enumerate(ch):
|
|
1078
1133
|
out.append(f' Segment {j}, {seg.storage()}')
|
|
1079
1134
|
out.append(f' {seg.segment_class()}, {seg}')
|
|
@@ -1274,7 +1329,6 @@ class Trace(TreeNode):
|
|
|
1274
1329
|
# Meta data
|
|
1275
1330
|
self._r_pipette = None
|
|
1276
1331
|
self._r_seal = None
|
|
1277
|
-
self._r_series_comp = None
|
|
1278
1332
|
self._g_series = None
|
|
1279
1333
|
self._c_slow = None
|
|
1280
1334
|
|
|
@@ -1329,8 +1383,8 @@ class Trace(TreeNode):
|
|
|
1329
1383
|
self._c_slow = reader.read1('d')
|
|
1330
1384
|
handle.seek(i + 184) # TrGSeries = 184; (* LONGREAL *)
|
|
1331
1385
|
self._g_series = reader.read1('d')
|
|
1332
|
-
handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1333
|
-
self._r_series_comp = reader.read1('d')
|
|
1386
|
+
#handle.seek(i + 192) # TrRsValue = 192; (* LONGREAL *)
|
|
1387
|
+
#self._r_series_comp = reader.read1('d')
|
|
1334
1388
|
|
|
1335
1389
|
# Convert unit
|
|
1336
1390
|
self._data_unit = myokit.parse_unit(self._data_unit)
|
|
@@ -1359,17 +1413,27 @@ class Trace(TreeNode):
|
|
|
1359
1413
|
|
|
1360
1414
|
def r_seal(self):
|
|
1361
1415
|
"""
|
|
1362
|
-
Returns the "seal resistance" (MOhm) determined
|
|
1363
|
-
|
|
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.
|
|
1364
1425
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
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).
|
|
1370
1432
|
|
|
1371
|
-
|
|
1372
|
-
|
|
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.
|
|
1373
1437
|
"""
|
|
1374
1438
|
return self._r_seal * 1e-6
|
|
1375
1439
|
|
|
@@ -1380,24 +1444,14 @@ class Trace(TreeNode):
|
|
|
1380
1444
|
"""
|
|
1381
1445
|
return 1e-6 / self._g_series
|
|
1382
1446
|
|
|
1383
|
-
def r_series_remaining(self):
|
|
1384
|
-
"""
|
|
1385
|
-
Returns the series resistance (MOhm) remaining after compensation.
|
|
1386
|
-
"""
|
|
1387
|
-
# "Absolute fraction of the compensated R-series value. The value
|
|
1388
|
-
# depends on the % of R-series compensation."
|
|
1389
|
-
return (1 / self._g_series - self._r_series_comp) * 1e-6
|
|
1390
|
-
|
|
1391
1447
|
def r_pipette(self):
|
|
1392
1448
|
"""
|
|
1393
|
-
Returns the pipette resistance (MOhm)
|
|
1394
|
-
|
|
1449
|
+
Returns the pipette resistance (MOhm) stored with this Trace, but
|
|
1450
|
+
calculated at an earlier point.
|
|
1395
1451
|
|
|
1396
|
-
|
|
1397
|
-
"R-memb
|
|
1398
|
-
|
|
1399
|
-
time. The intended use is to store the resistance of the pipette tip
|
|
1400
|
-
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`.
|
|
1401
1455
|
"""
|
|
1402
1456
|
return self._r_pipette * 1e-6
|
|
1403
1457
|
|
|
@@ -1572,13 +1626,6 @@ class AmplifierState:
|
|
|
1572
1626
|
"""
|
|
1573
1627
|
Describes the state of an amplifier used by PatchMaster.
|
|
1574
1628
|
"""
|
|
1575
|
-
_filter1_options = [
|
|
1576
|
-
'Bessel 100 kHz',
|
|
1577
|
-
'Bessel 30 kHz',
|
|
1578
|
-
'Bessel 10 kHz',
|
|
1579
|
-
'HQ 30 kHz',
|
|
1580
|
-
]
|
|
1581
|
-
|
|
1582
1629
|
def __init__(self, handle, reader):
|
|
1583
1630
|
|
|
1584
1631
|
# Read properties
|
|
@@ -1588,6 +1635,14 @@ class AmplifierState:
|
|
|
1588
1635
|
handle.seek(i + 8) # sCurrentGain = 8; (* LONGREAL *)
|
|
1589
1636
|
self._current_gain = reader.read1('d')
|
|
1590
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
|
+
|
|
1591
1646
|
# Series resistance and compensation
|
|
1592
1647
|
handle.seek(i + 40) # sRsFraction = 40; (* LONGREAL *)
|
|
1593
1648
|
self._rs_fraction = reader.read1('d')
|
|
@@ -1605,12 +1660,15 @@ class AmplifierState:
|
|
|
1605
1660
|
self._cf_amp2 = reader.read1('d')
|
|
1606
1661
|
handle.seek(i + 72) # sCFastTau = 72; (* LONGREAL *)
|
|
1607
1662
|
self._cf_tau = reader.read1('d')
|
|
1608
|
-
handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1609
|
-
self._cf_enabled = bool(reader.read1('b'))
|
|
1663
|
+
#handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1664
|
+
#self._cf_enabled = bool(reader.read1('b'))
|
|
1610
1665
|
|
|
1611
1666
|
# Slow capacitance correction
|
|
1612
1667
|
handle.seek(i + 80) # sCSlow = 80; (* LONGREAL *)
|
|
1613
1668
|
self._cs = reader.read1('d')
|
|
1669
|
+
handle.seek(i + 241) # sCSlowRange = 241; (* BYTE *)
|
|
1670
|
+
self._cs_range = CSlowRange(reader.read1('b'))
|
|
1671
|
+
|
|
1614
1672
|
# Auto CSlow settings. See 4.9 "EPC 10 USB Menu" in the manual.
|
|
1615
1673
|
handle.seek(i + 152) # sCSlowStimVolts = 152; (* LONGREAL *)
|
|
1616
1674
|
self._cs_auto_stim = reader.read1('d')
|
|
@@ -1619,71 +1677,78 @@ class AmplifierState:
|
|
|
1619
1677
|
handle.seek(i + 210) # sCSlowCycles = 210; (* INT16 *)
|
|
1620
1678
|
self._cs_auto_cycles = reader.read1('h')
|
|
1621
1679
|
|
|
1622
|
-
# Voltage offsets
|
|
1623
|
-
handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
|
|
1624
|
-
self._voff = reader.read1('d')
|
|
1625
|
-
handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
|
|
1626
|
-
self._ljp = reader.read1('d')
|
|
1627
|
-
|
|
1628
1680
|
# Filter 1
|
|
1681
|
+
# Set with a byte that controls type and frequency
|
|
1629
1682
|
handle.seek(i + 281) # sFilter1 = 281; (* BYTE *)
|
|
1630
|
-
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')
|
|
1631
1701
|
|
|
1632
|
-
#TODO: Add these are proper properties with a docstring'd method that
|
|
1633
|
-
# returns them and says what the units are etc. or stop reading them.
|
|
1634
1702
|
# self._temp = {}
|
|
1635
|
-
# handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1636
|
-
# self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1637
|
-
|
|
1638
|
-
# handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
|
|
1639
|
-
# self._temp['sF2Bandwidth'] = reader.read1('d')
|
|
1640
|
-
# handle.seek(i + 24) # sF2Frequency = 24; (* LONGREAL *)
|
|
1641
|
-
# self._temp['sF2Frequency'] = reader.read1('d')
|
|
1642
|
-
# handle.seek(i + 230) # sHasF2Bypass = 230; (* BYTE *)
|
|
1643
|
-
# self._temp['sHasF2Bypass'] = reader.read1('b')
|
|
1644
|
-
# handle.seek(i + 231) # sF2Mode = 231; (* BYTE *)
|
|
1645
|
-
# self._temp['sF2Mode'] = reader.read1('b')
|
|
1646
|
-
# handle.seek(i + 239) # sF2Response = 239; (* BYTE *)
|
|
1647
|
-
# self._temp['sF2Response'] = reader.read1('b')
|
|
1648
|
-
# handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
|
|
1649
|
-
# self._temp['sF2Source'] = reader.read1('b')
|
|
1650
|
-
|
|
1651
1703
|
# handle.seek(i + 296) # sStimFilterHz = 296; (* LONGREAL *)
|
|
1652
|
-
#
|
|
1704
|
+
# print('STIM', reader.read1('d'))
|
|
1653
1705
|
# handle.seek(i + 320) # sInputFilterTau = 320; (* LONGREAL *)
|
|
1706
|
+
# print('InputFilterTau', reader.read1('d'))
|
|
1654
1707
|
# self._temp['sInputFilterTau'] = reader.read1('d')
|
|
1655
1708
|
# handle.seek(i + 328) # sOutputFilterTau = 328; (* LONGREAL *)
|
|
1709
|
+
# print('OutputFilterTau', reader.read1('d'))
|
|
1656
1710
|
# self._temp['sOutputFilterTau'] = reader.read1('d')
|
|
1657
|
-
|
|
1658
1711
|
# handle.seek(i + 384) # sVmonFiltBandwidth = 384; (* LONGREAL *)
|
|
1659
1712
|
# self._temp['sVmonFiltBandwidth'] = reader.read1('d')
|
|
1660
1713
|
# handle.seek(i + 392) # sVmonFiltFrequency = 392; (* LONGREAL *)
|
|
1661
1714
|
# self._temp['sVmonFiltFrequency'] = reader.read1('d')
|
|
1715
|
+
# handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1716
|
+
# self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1662
1717
|
|
|
1663
|
-
#
|
|
1664
|
-
#
|
|
1665
|
-
|
|
1666
|
-
# 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'))
|
|
1667
1721
|
|
|
1668
1722
|
def c_fast(self):
|
|
1669
1723
|
"""
|
|
1670
|
-
Return the capacitance (pF) used in fast capacitance correction
|
|
1671
|
-
|
|
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.
|
|
1672
1731
|
"""
|
|
1673
|
-
#
|
|
1674
|
-
#
|
|
1675
|
-
|
|
1676
|
-
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
|
|
1677
1735
|
|
|
1678
1736
|
def c_fast_tau(self):
|
|
1679
1737
|
"""
|
|
1680
|
-
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`.
|
|
1681
1743
|
"""
|
|
1682
1744
|
return self._cf_tau * 1e6
|
|
1683
1745
|
|
|
1684
1746
|
def c_fast_enabled(self):
|
|
1685
|
-
"""
|
|
1686
|
-
|
|
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
|
|
1687
1752
|
|
|
1688
1753
|
def c_slow(self):
|
|
1689
1754
|
"""
|
|
@@ -1701,6 +1766,18 @@ class AmplifierState:
|
|
|
1701
1766
|
return (self._cs_auto_stim * 1e3, self._cs_auto_cycles,
|
|
1702
1767
|
self._cs_auto_timeout)
|
|
1703
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
|
+
|
|
1704
1781
|
def current_gain(self):
|
|
1705
1782
|
"""
|
|
1706
1783
|
The gain setting for current measurements, in mV/pA.
|
|
@@ -1709,9 +1786,50 @@ class AmplifierState:
|
|
|
1709
1786
|
|
|
1710
1787
|
def filter1(self):
|
|
1711
1788
|
"""
|
|
1712
|
-
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):
|
|
1713
1796
|
"""
|
|
1714
|
-
|
|
1797
|
+
Returns a string representing the Filter 1 settings.
|
|
1798
|
+
|
|
1799
|
+
For more information on Filter 1 and 2, see :class:`Filter1Setting`.
|
|
1800
|
+
"""
|
|
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'
|
|
1715
1833
|
|
|
1716
1834
|
def ljp(self):
|
|
1717
1835
|
"""
|
|
@@ -1757,12 +1875,122 @@ class AmplifierState:
|
|
|
1757
1875
|
"""
|
|
1758
1876
|
return self._rs_tau * 1e6 if self._rs_enabled else 0
|
|
1759
1877
|
|
|
1760
|
-
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`.
|
|
1884
|
+
"""
|
|
1885
|
+
return self._stimulus_filter
|
|
1886
|
+
|
|
1887
|
+
def stimulus_filter_str(self):
|
|
1761
1888
|
"""
|
|
1762
|
-
Returns
|
|
1889
|
+
Returns a string descibing the stimulus filter settings.
|
|
1890
|
+
|
|
1891
|
+
For more information, see :class:`StimulusFilterSetting`.
|
|
1763
1892
|
"""
|
|
1893
|
+
return self._stimulus_filter
|
|
1894
|
+
|
|
1895
|
+
def v_off(self):
|
|
1896
|
+
""" Returns the used voltage offset (in mV), also called V0. """
|
|
1764
1897
|
return self._voff * 1e3
|
|
1765
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
|
+
|
|
1766
1994
|
|
|
1767
1995
|
#
|
|
1768
1996
|
# From StimFile_v9.txt
|
|
@@ -1879,6 +2107,8 @@ class Stimulus(TreeNode):
|
|
|
1879
2107
|
handle.seek(start + 144) # stNumberSweeps = 144; (* INT32 *)
|
|
1880
2108
|
self._sweep_count = reader.read1('i')
|
|
1881
2109
|
|
|
2110
|
+
# Filterfactor: Determines desired filtering frequency as a function of
|
|
2111
|
+
# sampling rate. Is overruled by autofilter when enabled.
|
|
1882
2112
|
# handle.seek(start + 136) # stFilterFactor = 136; (* LONGREAL *)
|
|
1883
2113
|
# self._filter_factor = reader.read1('d')
|
|
1884
2114
|
|