myokit 1.38.0__py3-none-any.whl → 1.39.0__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 +5 -0
- myokit/_datablock.py +6 -5
- myokit/_expressions.py +6 -1
- myokit/_model_api.py +44 -18
- myokit/_myokit_version.py +1 -1
- myokit/_parsing.py +8 -2
- myokit/_sim/cvodessim.py +26 -0
- myokit/formats/__init__.py +37 -0
- myokit/formats/ansic/_ewriter.py +1 -1
- myokit/formats/axon/_abf.py +43 -9
- myokit/formats/cellml/v1/__init__.py +5 -5
- myokit/formats/cellml/v1/_api.py +220 -122
- myokit/formats/cellml/v1/_parser.py +91 -87
- myokit/formats/cellml/v1/_writer.py +13 -6
- myokit/formats/cellml/v2/__init__.py +5 -8
- myokit/formats/cellml/v2/_api.py +182 -106
- myokit/formats/cellml/v2/_parser.py +68 -64
- myokit/formats/cellml/v2/_writer.py +7 -3
- myokit/formats/heka/_patchmaster.py +71 -14
- myokit/formats/mathml/_parser.py +106 -67
- myokit/gui/source.py +18 -12
- myokit/lib/hh.py +21 -37
- myokit/tests/test_cellml_v1_api.py +227 -33
- myokit/tests/test_cellml_v1_parser.py +48 -17
- myokit/tests/test_cellml_v1_writer.py +14 -4
- myokit/tests/test_cellml_v2_api.py +132 -114
- myokit/tests/test_cellml_v2_parser.py +31 -1
- myokit/tests/test_cellml_v2_writer.py +8 -1
- myokit/tests/test_datalog.py +17 -0
- myokit/tests/test_expressions.py +61 -0
- myokit/tests/test_formats.py +99 -0
- myokit/tests/test_formats_mathml_content.py +97 -37
- myokit/tests/test_formats_python.py +1 -1
- myokit/tests/test_model_building.py +2 -0
- myokit/tests/test_parsing.py +32 -0
- myokit/tests/test_simulation_cvodes.py +10 -4
- myokit/tests/test_variable.py +10 -7
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/METADATA +22 -7
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/RECORD +43 -43
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/WHEEL +1 -1
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/entry_points.txt +0 -0
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info/licenses}/LICENSE.txt +0 -0
- {myokit-1.38.0.dist-info → myokit-1.39.0.dist-info}/top_level.txt +0 -0
|
@@ -53,7 +53,7 @@ class CellMLParsingError(myokit.ImportError):
|
|
|
53
53
|
if element is not None:
|
|
54
54
|
try: # pragma: no cover
|
|
55
55
|
line = str(element.sourceline)
|
|
56
|
-
message = 'Error on line
|
|
56
|
+
message = f'Error on line {line}. {message}'
|
|
57
57
|
except AttributeError:
|
|
58
58
|
pass
|
|
59
59
|
super().__init__(message)
|
|
@@ -89,8 +89,7 @@ class CellMLParser:
|
|
|
89
89
|
if element.text is not None:
|
|
90
90
|
if element.text.strip():
|
|
91
91
|
raise CellMLParsingError(
|
|
92
|
-
'Text found in
|
|
93
|
-
element)
|
|
92
|
+
f'Text found in {self._tag(element, name)}.', element)
|
|
94
93
|
|
|
95
94
|
# Check child elements
|
|
96
95
|
allowed = set([self._join(x) for x in children])
|
|
@@ -102,17 +101,16 @@ class CellMLParser:
|
|
|
102
101
|
# Check for trailing text
|
|
103
102
|
if child.tail is not None and child.tail.strip():
|
|
104
103
|
raise CellMLParsingError(
|
|
105
|
-
'Text found in
|
|
106
|
-
|
|
107
|
-
child)
|
|
104
|
+
f'Text found in {self._tag(element, name)}, after'
|
|
105
|
+
f' {self._tag(child)} element.', child)
|
|
108
106
|
|
|
109
107
|
# Check if allowed
|
|
110
108
|
if str(child.tag) in allowed:
|
|
111
109
|
continue
|
|
112
110
|
|
|
113
111
|
raise CellMLParsingError(
|
|
114
|
-
'Unexpected content type in
|
|
115
|
-
|
|
112
|
+
f'Unexpected content type in {self._tag(element, name)}, found'
|
|
113
|
+
f' element of type {self._tag(child)}.', child)
|
|
116
114
|
|
|
117
115
|
# Check attributes
|
|
118
116
|
allowed = set(attributes)
|
|
@@ -128,8 +126,8 @@ class CellMLParser:
|
|
|
128
126
|
ns, at = split(key)
|
|
129
127
|
key = self._item(ns, at)
|
|
130
128
|
raise CellMLParsingError(
|
|
131
|
-
'Unexpected attribute
|
|
132
|
-
|
|
129
|
+
f'Unexpected attribute {key} found in'
|
|
130
|
+
f' {self._tag(element, name)}.', element)
|
|
133
131
|
|
|
134
132
|
def _check_id(self, element, obj=None):
|
|
135
133
|
"""
|
|
@@ -148,7 +146,7 @@ class CellMLParser:
|
|
|
148
146
|
|
|
149
147
|
# Check uniqueness
|
|
150
148
|
if xid in self._ids:
|
|
151
|
-
raise CellMLParsingError('Duplicate id "
|
|
149
|
+
raise CellMLParsingError(f'Duplicate id "{xid}".', element)
|
|
152
150
|
|
|
153
151
|
# Store and return
|
|
154
152
|
self._ids[xid] = obj
|
|
@@ -165,11 +163,11 @@ class CellMLParser:
|
|
|
165
163
|
if ns is None:
|
|
166
164
|
return item
|
|
167
165
|
elif ns == self._ns:
|
|
168
|
-
return 'cellml:'
|
|
166
|
+
return f'cellml:{item}'
|
|
169
167
|
elif ns == cellml.NS_MATHML:
|
|
170
|
-
return 'mathml:'
|
|
168
|
+
return f'mathml:{item}'
|
|
171
169
|
else:
|
|
172
|
-
return '{
|
|
170
|
+
return f'{{{ns}}}{item}'
|
|
173
171
|
|
|
174
172
|
def _join(self, element, namespace=None):
|
|
175
173
|
"""
|
|
@@ -177,7 +175,7 @@ class CellMLParser:
|
|
|
177
175
|
"""
|
|
178
176
|
if namespace is None:
|
|
179
177
|
namespace = self._ns
|
|
180
|
-
return '{
|
|
178
|
+
return f'{{{namespace}}}{element}'
|
|
181
179
|
|
|
182
180
|
def parse(self, root):
|
|
183
181
|
"""
|
|
@@ -207,7 +205,7 @@ class CellMLParser:
|
|
|
207
205
|
parser = etree.XMLParser(remove_comments=True)
|
|
208
206
|
tree = etree.parse(path, parser=parser)
|
|
209
207
|
except Exception as e:
|
|
210
|
-
raise CellMLParsingError('Unable to parse XML: '
|
|
208
|
+
raise CellMLParsingError(f'Unable to parse XML: {e}')
|
|
211
209
|
|
|
212
210
|
# Parse content
|
|
213
211
|
return self.parse(tree.getroot())
|
|
@@ -222,7 +220,7 @@ class CellMLParser:
|
|
|
222
220
|
try:
|
|
223
221
|
root = etree.fromstring(text)
|
|
224
222
|
except Exception as e:
|
|
225
|
-
raise CellMLParsingError('Unable to parse XML: '
|
|
223
|
+
raise CellMLParsingError(f'Unable to parse XML: {e}')
|
|
226
224
|
|
|
227
225
|
# Parse content
|
|
228
226
|
return self.parse(root)
|
|
@@ -236,8 +234,7 @@ class CellMLParser:
|
|
|
236
234
|
name = element.attrib['name']
|
|
237
235
|
except KeyError:
|
|
238
236
|
raise CellMLParsingError(
|
|
239
|
-
'Component element must have a name attribute.',
|
|
240
|
-
element)
|
|
237
|
+
'Component element must have a name attribute.', element)
|
|
241
238
|
|
|
242
239
|
# Create component (validates name and checks uniqueness)
|
|
243
240
|
try:
|
|
@@ -259,8 +256,18 @@ class CellMLParser:
|
|
|
259
256
|
element, ['variable'], ['name'], name, math=True)
|
|
260
257
|
|
|
261
258
|
# Create variables and set interfaces
|
|
259
|
+
initial_values = []
|
|
262
260
|
for child in element.findall(self._join('variable')):
|
|
263
|
-
self._parse_variable(child, component)
|
|
261
|
+
init = self._parse_variable(child, component)
|
|
262
|
+
if init is not None:
|
|
263
|
+
initial_values.append(init)
|
|
264
|
+
|
|
265
|
+
# Set initial values
|
|
266
|
+
for child, variable, value in initial_values:
|
|
267
|
+
try:
|
|
268
|
+
variable.set_initial_value(value)
|
|
269
|
+
except myokit.formats.cellml.v2.CellMLError as e:
|
|
270
|
+
raise CellMLParsingError(str(e), child)
|
|
264
271
|
|
|
265
272
|
def _parse_connection(self, element, model, connected):
|
|
266
273
|
"""
|
|
@@ -288,8 +295,7 @@ class CellMLParser:
|
|
|
288
295
|
if c1 == c2:
|
|
289
296
|
raise CellMLParsingError(
|
|
290
297
|
'The component_1 and component_2 attributes in a connection'
|
|
291
|
-
' element must be different, got "'
|
|
292
|
-
+ '" twice.', element)
|
|
298
|
+
f' element must be different, got "{c1}" twice.', element)
|
|
293
299
|
|
|
294
300
|
# Get components
|
|
295
301
|
try:
|
|
@@ -297,22 +303,20 @@ class CellMLParser:
|
|
|
297
303
|
except KeyError:
|
|
298
304
|
raise CellMLParsingError(
|
|
299
305
|
'A map_components component_1 attribute must refer to a'
|
|
300
|
-
' component in the current model, got "
|
|
301
|
-
element)
|
|
306
|
+
f' component in the current model, got "{c1}".', element)
|
|
302
307
|
try:
|
|
303
308
|
c2 = model.component(c2)
|
|
304
309
|
except KeyError:
|
|
305
310
|
raise CellMLParsingError(
|
|
306
311
|
'A map_components component_2 attribute must refer to a'
|
|
307
|
-
' component in the current model, got "
|
|
308
|
-
element)
|
|
312
|
+
f' component in the current model, got "{c2}".', element)
|
|
309
313
|
|
|
310
314
|
# Check components are not yet connected
|
|
311
315
|
if (c1, c2) in connected:
|
|
312
316
|
raise CellMLParsingError(
|
|
313
317
|
'Each connection in a model must connect a unique pair of'
|
|
314
|
-
' components, found multiple for "
|
|
315
|
-
|
|
318
|
+
f' components, found multiple for "{c1.name()}" and'
|
|
319
|
+
f' "{c2.name()}".', element)
|
|
316
320
|
connected.add((c1, c2))
|
|
317
321
|
connected.add((c2, c1))
|
|
318
322
|
|
|
@@ -359,15 +363,13 @@ class CellMLParser:
|
|
|
359
363
|
except KeyError:
|
|
360
364
|
raise CellMLParsingError(
|
|
361
365
|
'A map_variables variable_1 attribute must refer to a'
|
|
362
|
-
' variable in component_1, got "
|
|
363
|
-
element)
|
|
366
|
+
f' variable in component_1, got "{v1}".', element)
|
|
364
367
|
try:
|
|
365
368
|
v2 = c2.variable(v2)
|
|
366
369
|
except KeyError:
|
|
367
370
|
raise CellMLParsingError(
|
|
368
371
|
'A map_variables variable_2 attribute must refer to a'
|
|
369
|
-
' variable in component_2, got "
|
|
370
|
-
element)
|
|
372
|
+
f' variable in component_2, got "{v1}".', element)
|
|
371
373
|
|
|
372
374
|
# Connect variables
|
|
373
375
|
model = c1.model()
|
|
@@ -428,8 +430,7 @@ class CellMLParser:
|
|
|
428
430
|
except KeyError:
|
|
429
431
|
raise CellMLParsingError(
|
|
430
432
|
'A component_ref\'s component attribute must reference a'
|
|
431
|
-
' component in the same model, got "
|
|
432
|
-
element)
|
|
433
|
+
f' component in the same model, got "{component}".', element)
|
|
433
434
|
|
|
434
435
|
# Check allowed content
|
|
435
436
|
self._check_allowed_content(element, ['component_ref'], ['component'])
|
|
@@ -441,9 +442,8 @@ class CellMLParser:
|
|
|
441
442
|
if component.parent() is not None:
|
|
442
443
|
raise CellMLParsingError(
|
|
443
444
|
'A component can only have a single encapsulation parent:'
|
|
444
|
-
' found
|
|
445
|
-
|
|
446
|
-
element)
|
|
445
|
+
f' found {component} with parents {component.parent()} and'
|
|
446
|
+
f' and {parent}.', element)
|
|
447
447
|
|
|
448
448
|
# Set parent (won't raise CellMLErrors)
|
|
449
449
|
component.set_parent(parent)
|
|
@@ -498,8 +498,8 @@ class CellMLParser:
|
|
|
498
498
|
units = model.find_units(units)
|
|
499
499
|
except myokit.formats.cellml.v2.CellMLError:
|
|
500
500
|
raise CellMLParsingError(
|
|
501
|
-
'Unknown unit "
|
|
502
|
-
'
|
|
501
|
+
f'Unknown unit "{units}" referenced inside a MathML'
|
|
502
|
+
' equation.', element)
|
|
503
503
|
|
|
504
504
|
# Create and return
|
|
505
505
|
return myokit.Number(value, units.myokit_unit())
|
|
@@ -516,36 +516,36 @@ class CellMLParser:
|
|
|
516
516
|
if ns != cellml.NS_MATHML:
|
|
517
517
|
raise CellMLParsingError(
|
|
518
518
|
'The contents of a mathml:math element must be in the'
|
|
519
|
-
' mathml namespace, found "
|
|
520
|
-
|
|
519
|
+
f' mathml namespace, found "{child.tag}" inside'
|
|
520
|
+
f' {component}.', child)
|
|
521
521
|
|
|
522
522
|
# If it isn't these it must be an apply
|
|
523
523
|
if el != 'apply':
|
|
524
524
|
raise CellMLParsingError(
|
|
525
525
|
'Unexpected contents in mathml:math. Expecting'
|
|
526
|
-
' mathml:apply but found mathml:
|
|
527
|
-
'
|
|
526
|
+
f' mathml:apply but found mathml:{el} inside maths for'
|
|
527
|
+
f' {component}.', child)
|
|
528
528
|
|
|
529
529
|
# Parse
|
|
530
530
|
eq = p.parse(child)
|
|
531
531
|
if not isinstance(eq, myokit.Equal):
|
|
532
532
|
raise CellMLParsingError(
|
|
533
533
|
'Unexpected element in MathML, expecting a list of'
|
|
534
|
-
' equations, got
|
|
534
|
+
f' equations, got f{self._tag(child)}.', child)
|
|
535
535
|
lhs, rhs = eq
|
|
536
536
|
|
|
537
537
|
# Check lhs
|
|
538
538
|
if not isinstance(lhs, myokit.LhsExpression):
|
|
539
539
|
raise CellMLParsingError(
|
|
540
540
|
'Invalid expression found on the left-hand side of an'
|
|
541
|
-
' equation:
|
|
541
|
+
f' equation: {self._dae_message}.', child)
|
|
542
542
|
|
|
543
543
|
# Check variable is undefined
|
|
544
544
|
var = lhs.var()
|
|
545
545
|
if var.has_equation():
|
|
546
546
|
raise CellMLParsingError(
|
|
547
|
-
'Overdefined variable:
|
|
548
|
-
|
|
547
|
+
f'Overdefined variable: {var}: Two defining equations.',
|
|
548
|
+
child)
|
|
549
549
|
|
|
550
550
|
# Set equations
|
|
551
551
|
try:
|
|
@@ -642,7 +642,7 @@ class CellMLParser:
|
|
|
642
642
|
except myokit.formats.cellml.v2.CellMLError as e:
|
|
643
643
|
raise CellMLParsingError(
|
|
644
644
|
'Models that take derivatives with respect to more than one'
|
|
645
|
-
' variable are not supported (
|
|
645
|
+
f' variable are not supported ({e}).')
|
|
646
646
|
|
|
647
647
|
# Read any rdf annotations
|
|
648
648
|
# TODO: Allow RDF annotations from external files
|
|
@@ -697,9 +697,9 @@ class CellMLParser:
|
|
|
697
697
|
children = element.findall(self._join('unit'))
|
|
698
698
|
if not children:
|
|
699
699
|
warnings.warn(
|
|
700
|
-
'Unable to parse definition for units "
|
|
701
|
-
'
|
|
702
|
-
'
|
|
700
|
+
f'Unable to parse definition for units "{name}", using'
|
|
701
|
+
' `dimensionless instead. (Defining new base units is not'
|
|
702
|
+
' supported.)')
|
|
703
703
|
|
|
704
704
|
# Parse content
|
|
705
705
|
myokit_unit = myokit.units.dimensionless
|
|
@@ -716,6 +716,10 @@ class CellMLParser:
|
|
|
716
716
|
"""
|
|
717
717
|
Parses a variable ``element`` and adds a variable to the given
|
|
718
718
|
``component``.
|
|
719
|
+
|
|
720
|
+
If an initial value needs to be set, returns a tuple
|
|
721
|
+
``(element, variable, initial_value_string)``. Otherwise returns
|
|
722
|
+
``None``.
|
|
719
723
|
"""
|
|
720
724
|
# Check name is present
|
|
721
725
|
try:
|
|
@@ -752,13 +756,15 @@ class CellMLParser:
|
|
|
752
756
|
]
|
|
753
757
|
self._check_allowed_content(element, [], attr, name)
|
|
754
758
|
|
|
755
|
-
#
|
|
756
|
-
|
|
757
|
-
element.attrib.get('initial_value', None))
|
|
759
|
+
# Pass back initial value for delayed processing
|
|
760
|
+
init = element.attrib.get('initial_value', None)
|
|
758
761
|
|
|
759
762
|
except myokit.formats.cellml.v2.CellMLError as e:
|
|
760
763
|
raise CellMLParsingError(str(e), element)
|
|
761
764
|
|
|
765
|
+
if init is not None:
|
|
766
|
+
return (element, variable, init)
|
|
767
|
+
|
|
762
768
|
def _sort_units(self, element):
|
|
763
769
|
"""
|
|
764
770
|
Returns all units elements in ``element``, sorted in an order that
|
|
@@ -794,14 +800,14 @@ class CellMLParser:
|
|
|
794
800
|
# Check doesn't shadow an si unit
|
|
795
801
|
if name in si_units:
|
|
796
802
|
raise CellMLParsingError(
|
|
797
|
-
'Units name "
|
|
798
|
-
'
|
|
803
|
+
f'Units name "{name}" overlaps with a predefined name in'
|
|
804
|
+
f' {self._tag(element)}.', element)
|
|
799
805
|
|
|
800
806
|
# Check for duplicates
|
|
801
807
|
if name in local_units:
|
|
802
808
|
raise CellMLParsingError(
|
|
803
|
-
'Duplicate units definition "
|
|
804
|
-
|
|
809
|
+
f'Duplicate units definition "{name}" in'
|
|
810
|
+
f' {self._tag(element)}.', element)
|
|
805
811
|
local_units[name] = units
|
|
806
812
|
|
|
807
813
|
# Determine dependencies
|
|
@@ -811,8 +817,7 @@ class CellMLParser:
|
|
|
811
817
|
dep = unit.attrib['units']
|
|
812
818
|
except KeyError:
|
|
813
819
|
raise CellMLParsingError(
|
|
814
|
-
'Unit elements must have a units attribute.',
|
|
815
|
-
element)
|
|
820
|
+
'Unit elements must have a units attribute.', element)
|
|
816
821
|
deps.add(dep)
|
|
817
822
|
unresolved[name] = deps
|
|
818
823
|
|
|
@@ -835,9 +840,8 @@ class CellMLParser:
|
|
|
835
840
|
for name, deps in unresolved.items():
|
|
836
841
|
deps.difference_update(fresh)
|
|
837
842
|
else:
|
|
838
|
-
raise CellMLParsingError(
|
|
839
|
-
|
|
840
|
-
+ self._tag(element) + '.', element)
|
|
843
|
+
raise CellMLParsingError('Unable to resolve network of units'
|
|
844
|
+
f' in {self._tag(element)}.', element)
|
|
841
845
|
return ordered
|
|
842
846
|
|
|
843
847
|
def _tag(self, element, name=None):
|
|
@@ -854,6 +858,6 @@ class CellMLParser:
|
|
|
854
858
|
ns, el = split(element.tag)
|
|
855
859
|
tag = self._item(ns, el)
|
|
856
860
|
if ns == self._ns and name is not None:
|
|
857
|
-
tag += '[@name="
|
|
861
|
+
tag += f'[@name="{name}"]'
|
|
858
862
|
return tag
|
|
859
863
|
|
|
@@ -279,9 +279,13 @@ class CellMLWriter:
|
|
|
279
279
|
|
|
280
280
|
# Add initial value
|
|
281
281
|
if variable is variable.initial_value_variable():
|
|
282
|
-
value =
|
|
283
|
-
if value
|
|
284
|
-
value = value
|
|
282
|
+
value = variable.initial_value()
|
|
283
|
+
if isinstance(value, myokit.Name):
|
|
284
|
+
value = value.var().name()
|
|
285
|
+
else:
|
|
286
|
+
value = myokit.float.str(value).strip()
|
|
287
|
+
if value[-4:] == 'e+00':
|
|
288
|
+
value = value[:-4]
|
|
285
289
|
element.attrib['initial_value'] = value
|
|
286
290
|
|
|
287
291
|
def write(self, model):
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# This module reads files in HEKA Patchmaster format.
|
|
3
3
|
#
|
|
4
|
-
# Specifically, it targets the 2x90.2 format.
|
|
5
|
-
#
|
|
6
4
|
# Notes:
|
|
7
5
|
# - HEKA publishes a lot of information about its file format on its FTP
|
|
8
6
|
# server: server.hekahome.de
|
|
@@ -159,9 +157,10 @@ class PatchMasterFile:
|
|
|
159
157
|
except ValueError:
|
|
160
158
|
pass
|
|
161
159
|
|
|
162
|
-
if not self._version.startswith('v2x90.2')
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
if not (self._version.startswith('v2x90.2')
|
|
161
|
+
or self._version.startswith('v2x73')):
|
|
162
|
+
warnings.warn(
|
|
163
|
+
'Untested PatchMaster format version {self._version}.')
|
|
165
164
|
|
|
166
165
|
# Endianness
|
|
167
166
|
f.seek(52)
|
|
@@ -1021,6 +1020,9 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1021
1020
|
a.r_series_tau()
|
|
1022
1021
|
else:
|
|
1023
1022
|
log.meta[f'{pre}r_series_compensation_enabled'] = 'false'
|
|
1023
|
+
log.meta[f'{pre}current_clamp_holding_current_pA'] = a.i_hold()
|
|
1024
|
+
log.meta[f'{pre}current_clamp_stimulus_gain'] = \
|
|
1025
|
+
a.current_clamp_stimulus_gain()
|
|
1024
1026
|
|
|
1025
1027
|
# Add protocol to meta data
|
|
1026
1028
|
stimulus = self.stimulus()
|
|
@@ -1110,6 +1112,10 @@ class Series(TreeNode, myokit.formats.SweepSource):
|
|
|
1110
1112
|
out.append(f' R series compensation: {p} %, {q} us')
|
|
1111
1113
|
else:
|
|
1112
1114
|
out.append(' R series compensation: not enabled')
|
|
1115
|
+
# Current-clamp info
|
|
1116
|
+
out.append(f' Current clamp holding current: {a.i_hold()} pA')
|
|
1117
|
+
out.append(' Current clamp stimulus gain: '
|
|
1118
|
+
f'{a.current_clamp_stimulus_gain()} pA/mV')
|
|
1113
1119
|
|
|
1114
1120
|
# Info from first trace
|
|
1115
1121
|
if len(self) and len(self[0]):
|
|
@@ -1491,7 +1497,6 @@ class Trace(TreeNode):
|
|
|
1491
1497
|
not perform this zeroing, but it can be applied by setting
|
|
1492
1498
|
``ignore_zero_segment`` to ``False``.
|
|
1493
1499
|
"""
|
|
1494
|
-
|
|
1495
1500
|
if self._data_interleave_size == 0 or self._data_interleave_skip == 0:
|
|
1496
1501
|
# Read continuous data
|
|
1497
1502
|
d = np.memmap(self._handle, self._data_type, 'r',
|
|
@@ -1666,18 +1671,25 @@ class AmplifierState:
|
|
|
1666
1671
|
# Read properties
|
|
1667
1672
|
i = handle.tell()
|
|
1668
1673
|
|
|
1669
|
-
# Current gain (V/A)
|
|
1674
|
+
# Current gain (the "gain" in VC mode) (V/A)
|
|
1670
1675
|
handle.seek(i + 8) # sCurrentGain = 8; (* LONGREAL *)
|
|
1671
1676
|
self._current_gain = reader.read1('d')
|
|
1672
1677
|
|
|
1673
1678
|
# Holding potential and offsets
|
|
1674
1679
|
handle.seek(i + 112) # sVHold = 112; (* LONGREAL *)
|
|
1675
|
-
self.
|
|
1680
|
+
self._vhold = reader.read1('d')
|
|
1676
1681
|
handle.seek(i + 128) # sVpOffset = 128; (* LONGREAL *)
|
|
1677
1682
|
self._voff = reader.read1('d')
|
|
1678
1683
|
handle.seek(i + 136) # sVLiquidJunction = 136; (* LONGREAL *)
|
|
1679
1684
|
self._ljp = reader.read1('d')
|
|
1680
1685
|
|
|
1686
|
+
# Current clamp settings
|
|
1687
|
+
handle.seek(i + 144) # sCCIHold = 144; (* LONGREAL *)
|
|
1688
|
+
self._ihold = reader.read1('d')
|
|
1689
|
+
# Stimulus scaling in current clamp mode (a byte / enum)
|
|
1690
|
+
handle.seek(i + 243) # sCCGain = 243; (* BYTE *)
|
|
1691
|
+
self._cc_gain = reader.read1('b')
|
|
1692
|
+
|
|
1681
1693
|
# Series resistance and compensation
|
|
1682
1694
|
handle.seek(i + 40) # sRsFraction = 40; (* LONGREAL *)
|
|
1683
1695
|
self._rs_fraction = reader.read1('d')
|
|
@@ -1730,6 +1742,10 @@ class AmplifierState:
|
|
|
1730
1742
|
handle.seek(i + 16) # sF2Bandwidth = 16; (* LONGREAL *)
|
|
1731
1743
|
self._filter2_freq_both = reader.read1('d') * 1e-3
|
|
1732
1744
|
|
|
1745
|
+
# Stimulus filter
|
|
1746
|
+
handle.seek(i + 282) # sStimFilterOn = 282; (* BYTE *)
|
|
1747
|
+
self._stimulus_filter = StimulusFilterSetting(reader.read1('b'))
|
|
1748
|
+
|
|
1733
1749
|
# Suspect this indicates external filter 2
|
|
1734
1750
|
#handle.seek(i + 287) # sF2Source = 287; (* BYTE *)
|
|
1735
1751
|
#self._temp['sF2Source'] = reader.read1('b')
|
|
@@ -1752,9 +1768,16 @@ class AmplifierState:
|
|
|
1752
1768
|
#handle.seek(i + 264) # sImon1Bandwidth = 264; (* LONGREAL *)
|
|
1753
1769
|
#self._temp['sImon1Bandwidth'] = reader.read1('d')
|
|
1754
1770
|
|
|
1755
|
-
#
|
|
1756
|
-
|
|
1757
|
-
|
|
1771
|
+
#handle.seek(i + 242) # sCCRange = 242; (* BYTE *)
|
|
1772
|
+
#print('CC range', reader.read1('b'))
|
|
1773
|
+
#handle.seek(i + 285) # sCCCFastOn = 285; (* BYTE *)
|
|
1774
|
+
#print('CC fast on', reader.read1('b'))
|
|
1775
|
+
#handle.seek(i + 286) # sCCFastSpeed = 286; (* BYTE *)
|
|
1776
|
+
#print('CC fast speed', reader.read1('b'))
|
|
1777
|
+
#handle.seek(i + 376) # sCCStimDacScale = 376; (* LONGREAL *)
|
|
1778
|
+
#print('CC stim dac scale', reader.read1('d'))
|
|
1779
|
+
#handle.seek(i + 160) # sCCTrackVHold = 160; (* LONGREAL *)
|
|
1780
|
+
#print('CCTrackVHold', reader.read1('d'))
|
|
1758
1781
|
|
|
1759
1782
|
def c_fast(self):
|
|
1760
1783
|
"""
|
|
@@ -1820,9 +1843,37 @@ class AmplifierState:
|
|
|
1820
1843
|
"""
|
|
1821
1844
|
return self._cs_range
|
|
1822
1845
|
|
|
1846
|
+
def current_clamp_stimulus_gain(self):
|
|
1847
|
+
"""
|
|
1848
|
+
Returns the "CC-Gain" setting, in pA/mV.
|
|
1849
|
+
|
|
1850
|
+
For current-clamp measurements, this is one of two values (along with
|
|
1851
|
+
the "CC Stim. Scale", which is currently not provided by this class)
|
|
1852
|
+
that determines the scaling of applied stimuli.
|
|
1853
|
+
|
|
1854
|
+
**This value is provided for information only**: stimuli returned by
|
|
1855
|
+
the :class:`PatchMasterFile` methods are already scaled appropriately.
|
|
1856
|
+
"""
|
|
1857
|
+
gain_values = (0.1, 1, 10, 100)
|
|
1858
|
+
try:
|
|
1859
|
+
return gain_values[self._cc_gain]
|
|
1860
|
+
except IndexError: # pragma: no-cover
|
|
1861
|
+
raise NotImplementedError(
|
|
1862
|
+
'Unexpected value found for current clamp gain'
|
|
1863
|
+
f' ({self._cc_gain}).')
|
|
1864
|
+
|
|
1823
1865
|
def current_gain(self):
|
|
1824
1866
|
"""
|
|
1825
|
-
The gain setting
|
|
1867
|
+
The gain setting of the "current monitor" output, in mV/pA.
|
|
1868
|
+
|
|
1869
|
+
For voltage-clamp measurements, this is the main "gain" setting used to
|
|
1870
|
+
record currents.
|
|
1871
|
+
|
|
1872
|
+
**This value is provided for information only**: the currents returned
|
|
1873
|
+
by the :class:`PatchMasterFile` methods are already scaled
|
|
1874
|
+
appropriately. The chosen gain value is mainly interesting because it
|
|
1875
|
+
determines the feedback resistor used in the headstage. See the
|
|
1876
|
+
Patchmaster and amplifier manuals for details.
|
|
1826
1877
|
"""
|
|
1827
1878
|
return self._current_gain * 1e-9
|
|
1828
1879
|
|
|
@@ -1875,6 +1926,12 @@ class AmplifierState:
|
|
|
1875
1926
|
fb = int(fb) if fb == int(fb) else fb
|
|
1876
1927
|
return f'{self._filter2_type} {fb} kHz combined, {fs} kHz f2-only'
|
|
1877
1928
|
|
|
1929
|
+
def i_hold(self):
|
|
1930
|
+
"""
|
|
1931
|
+
Returns the current-clamp holding current, in pA.
|
|
1932
|
+
"""
|
|
1933
|
+
return self._ihold * 1e12
|
|
1934
|
+
|
|
1878
1935
|
def ljp(self):
|
|
1879
1936
|
"""
|
|
1880
1937
|
Returns the liquid junction potential (LJP, in mV) used in the LJP
|
|
@@ -1942,12 +1999,12 @@ class AmplifierState:
|
|
|
1942
1999
|
|
|
1943
2000
|
def v_hold(self):
|
|
1944
2001
|
"""
|
|
1945
|
-
Returns the holding potential (in mV).
|
|
2002
|
+
Returns the voltage-clamp holding potential (in mV).
|
|
1946
2003
|
|
|
1947
2004
|
This is the potential last set in the amplifier window, before any
|
|
1948
2005
|
experiments were run.
|
|
1949
2006
|
"""
|
|
1950
|
-
return self.
|
|
2007
|
+
return self._vhold * 1e3
|
|
1951
2008
|
|
|
1952
2009
|
|
|
1953
2010
|
class Filter1Setting(enum.Enum):
|