dcio 0.1.0__tar.gz

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.
dcio-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Remis Lape
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dcio-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,169 @@
1
+ Metadata-Version: 2.4
2
+ Name: dcio
3
+ Version: 0.1.0
4
+ Summary: I/O and record layer for electrophysiology file formats used by the DCPROGS suite
5
+ Author: Remis Lape
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/DCPROGS/dcio
8
+ Project-URL: Repository, https://github.com/DCPROGS/dcio
9
+ Project-URL: Issues, https://github.com/DCPROGS/dcio/issues
10
+ Keywords: electrophysiology,single-channel,ion-channel,patch-clamp,scn,dcprogs
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=2.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.4; extra == "dev"
25
+ Requires-Dist: pytest-cov; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # dcio
29
+
30
+ I/O library for electrophysiology file formats used by the
31
+ [DCProgs](http://www.ucl.ac.uk/Pharmacology/dcpr95.html) single-channel
32
+ analysis suite.
33
+
34
+ ## Supported formats
35
+
36
+ | Format | Extension | Read | Write | Description |
37
+ |--------|-----------|------|-------|-------------|
38
+ | SCAN | `.scn` | ✓ | ✓ | Idealised single-channel records |
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install -e ".[dev]"
44
+ ```
45
+
46
+ Requires Python ≥ 3.10 and NumPy ≥ 1.24.
47
+
48
+ ## Quick start
49
+
50
+ ```python
51
+ from dcio.formats.scn import read, write
52
+ import numpy as np
53
+
54
+ # --- Load an existing file ---
55
+ rec = read("myrecording.scn")
56
+ print(rec)
57
+ # SCNRecord('myrecording.scn')
58
+ # version : -103 (simulated)
59
+ # total : 1842 intervals
60
+ # usable : 1841
61
+ # open / shut : 921 / 920
62
+ # mean open : 2.3147 ms
63
+ # mean shut : 8.0412 ms
64
+ # calfac2 : 1 pA/ADC
65
+ # filter : 3000 Hz
66
+
67
+ # Intervals are in seconds; amplitudes in pA
68
+ print(rec.intervals[:5]) # array([0.01031, 0.00198, 0.02047, ...])
69
+ print(rec.amplitudes[:5]) # array([0., -48., 0., -48., 0.])
70
+
71
+ # Filter out unusable intervals
72
+ from dcio.formats.scn import FLAG_UNUSABLE
73
+ good = rec.usable_mask
74
+ open_durations_ms = rec.intervals[rec.open_mask & good] * 1e3
75
+
76
+ # --- Write a new file ---
77
+ intervals_s = np.array([0.010, 0.002, 0.020, 0.001, 0.050])
78
+ amplitudes = np.array([0, -48, 0, -48, 0 ], dtype=float)
79
+ flags = np.zeros(5, dtype=np.int8)
80
+
81
+ write("output.scn", intervals_s, amplitudes, flags,
82
+ title="My simulated record", ffilt=3000.0)
83
+ ```
84
+
85
+ ## SCNRecord attributes
86
+
87
+ | Attribute | Type | Description |
88
+ |-----------|------|-------------|
89
+ | `intervals` | `ndarray[float64]` | Interval durations in **seconds** |
90
+ | `amplitudes` | `ndarray[float64]` | Amplitudes in **pA** (0 = shut) |
91
+ | `flags` | `ndarray[int8]` | Property flags; `& 8 != 0` → unusable |
92
+ | `header` | `SCNHeader` | Parsed file metadata |
93
+ | `path` | `Path` | Source file path |
94
+ | `open_mask` | `ndarray[bool]` | True for open intervals |
95
+ | `shut_mask` | `ndarray[bool]` | True for shut intervals |
96
+ | `usable_mask` | `ndarray[bool]` | True when `flags & 8 == 0` |
97
+
98
+ ## Record analysis
99
+
100
+ Beyond file I/O, `dcio.analysis` carries the record layer shared by the rest of
101
+ the DCPROGS stack: applying a dead time, grouping the result into open and shut
102
+ periods, and cutting it into bursts.
103
+
104
+ ```python
105
+ from dcio.formats.scn import read
106
+ from dcio.analysis import from_scn, bursts_from_record
107
+
108
+ record = from_scn(read("myrecording.scn"), tres=25e-6) # 25 us dead time
109
+ lengths, n_openings = bursts_from_record(record, tcrit=4e-3)
110
+
111
+ print(len(lengths), "bursts,", n_openings.mean(), "openings each")
112
+ ```
113
+
114
+ `bursts_from_record` segments the record's **periods**, not its resolved
115
+ intervals. `impose_resolution` emits a fresh open interval at every change of
116
+ fitted amplitude, so a record idealised with sub-conductance levels contains
117
+ runs of consecutive openings; segmenting those directly gives bursts that do
118
+ not alternate open/shut. Burst count and lengths are the same either way, the
119
+ number of openings per burst is not.
120
+
121
+ Two conventions are worth stating, because implementations in this stack have
122
+ differed on them:
123
+
124
+ - no gap longer than `tcrit` is required before the first burst -- the first
125
+ defined opening starts one;
126
+ - an unusable interval ends the burst before it, and its (meaningless)
127
+ duration is never compared with `tcrit`.
128
+
129
+ Both follow SCAN's time-course fitting, which leaves the final interval of a
130
+ record with no defined length. Dropping the runs at each end instead loses two
131
+ bursts from every record.
132
+
133
+ ### Dwell-time histograms
134
+
135
+ `dcio.analysis.histogram` carries the log-binning arithmetic -- bin counts, bin
136
+ edges, and staircase coordinates -- and nothing else. Drawing stays with the
137
+ caller, because EKDIST, HJCFIT and SCALCS legitimately want different figures
138
+ over the same bins.
139
+
140
+ ```python
141
+ import numpy as np
142
+ from dcio.analysis.histogram import log_bin_histogram, staircase
143
+
144
+ counts, edges, nbdec = log_bin_histogram(record.periods.open_intervals, tres=25e-6)
145
+ x, y = staircase(edges, counts)
146
+ ax.semilogx(x, np.sqrt(y)) # Sigworth-Sine square-root ordinate
147
+ ```
148
+
149
+ Bins start at the resolution and each is `10 ** (1/nbdec)` wider than the last,
150
+ with `nbdec` chosen from the sample size (5 / 8 / 10 / 12). The last edge is
151
+ rounded up to a whole decade, so no interval falls outside the bins -- writing
152
+ that round-up with a natural log instead, as earlier code in this stack did,
153
+ gives a power of e and lets `numpy.histogram` drop the tail of the distribution
154
+ without saying so.
155
+
156
+ ## Running tests
157
+
158
+ ```bash
159
+ cd dcio # from the dcprogs root
160
+ pytest
161
+ ```
162
+
163
+ Tests cover write validation, round-trip fidelity, flag handling, edge cases,
164
+ and optional smoke tests against legacy SCN files in `examples/scn/`.
165
+
166
+ ## Documentation
167
+
168
+ See [`docs/scn_format.md`](docs/scn_format.md) for a complete description of
169
+ the binary file format, header fields, and unit conventions.
dcio-0.1.0/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # dcio
2
+
3
+ I/O library for electrophysiology file formats used by the
4
+ [DCProgs](http://www.ucl.ac.uk/Pharmacology/dcpr95.html) single-channel
5
+ analysis suite.
6
+
7
+ ## Supported formats
8
+
9
+ | Format | Extension | Read | Write | Description |
10
+ |--------|-----------|------|-------|-------------|
11
+ | SCAN | `.scn` | ✓ | ✓ | Idealised single-channel records |
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ Requires Python ≥ 3.10 and NumPy ≥ 1.24.
20
+
21
+ ## Quick start
22
+
23
+ ```python
24
+ from dcio.formats.scn import read, write
25
+ import numpy as np
26
+
27
+ # --- Load an existing file ---
28
+ rec = read("myrecording.scn")
29
+ print(rec)
30
+ # SCNRecord('myrecording.scn')
31
+ # version : -103 (simulated)
32
+ # total : 1842 intervals
33
+ # usable : 1841
34
+ # open / shut : 921 / 920
35
+ # mean open : 2.3147 ms
36
+ # mean shut : 8.0412 ms
37
+ # calfac2 : 1 pA/ADC
38
+ # filter : 3000 Hz
39
+
40
+ # Intervals are in seconds; amplitudes in pA
41
+ print(rec.intervals[:5]) # array([0.01031, 0.00198, 0.02047, ...])
42
+ print(rec.amplitudes[:5]) # array([0., -48., 0., -48., 0.])
43
+
44
+ # Filter out unusable intervals
45
+ from dcio.formats.scn import FLAG_UNUSABLE
46
+ good = rec.usable_mask
47
+ open_durations_ms = rec.intervals[rec.open_mask & good] * 1e3
48
+
49
+ # --- Write a new file ---
50
+ intervals_s = np.array([0.010, 0.002, 0.020, 0.001, 0.050])
51
+ amplitudes = np.array([0, -48, 0, -48, 0 ], dtype=float)
52
+ flags = np.zeros(5, dtype=np.int8)
53
+
54
+ write("output.scn", intervals_s, amplitudes, flags,
55
+ title="My simulated record", ffilt=3000.0)
56
+ ```
57
+
58
+ ## SCNRecord attributes
59
+
60
+ | Attribute | Type | Description |
61
+ |-----------|------|-------------|
62
+ | `intervals` | `ndarray[float64]` | Interval durations in **seconds** |
63
+ | `amplitudes` | `ndarray[float64]` | Amplitudes in **pA** (0 = shut) |
64
+ | `flags` | `ndarray[int8]` | Property flags; `& 8 != 0` → unusable |
65
+ | `header` | `SCNHeader` | Parsed file metadata |
66
+ | `path` | `Path` | Source file path |
67
+ | `open_mask` | `ndarray[bool]` | True for open intervals |
68
+ | `shut_mask` | `ndarray[bool]` | True for shut intervals |
69
+ | `usable_mask` | `ndarray[bool]` | True when `flags & 8 == 0` |
70
+
71
+ ## Record analysis
72
+
73
+ Beyond file I/O, `dcio.analysis` carries the record layer shared by the rest of
74
+ the DCPROGS stack: applying a dead time, grouping the result into open and shut
75
+ periods, and cutting it into bursts.
76
+
77
+ ```python
78
+ from dcio.formats.scn import read
79
+ from dcio.analysis import from_scn, bursts_from_record
80
+
81
+ record = from_scn(read("myrecording.scn"), tres=25e-6) # 25 us dead time
82
+ lengths, n_openings = bursts_from_record(record, tcrit=4e-3)
83
+
84
+ print(len(lengths), "bursts,", n_openings.mean(), "openings each")
85
+ ```
86
+
87
+ `bursts_from_record` segments the record's **periods**, not its resolved
88
+ intervals. `impose_resolution` emits a fresh open interval at every change of
89
+ fitted amplitude, so a record idealised with sub-conductance levels contains
90
+ runs of consecutive openings; segmenting those directly gives bursts that do
91
+ not alternate open/shut. Burst count and lengths are the same either way, the
92
+ number of openings per burst is not.
93
+
94
+ Two conventions are worth stating, because implementations in this stack have
95
+ differed on them:
96
+
97
+ - no gap longer than `tcrit` is required before the first burst -- the first
98
+ defined opening starts one;
99
+ - an unusable interval ends the burst before it, and its (meaningless)
100
+ duration is never compared with `tcrit`.
101
+
102
+ Both follow SCAN's time-course fitting, which leaves the final interval of a
103
+ record with no defined length. Dropping the runs at each end instead loses two
104
+ bursts from every record.
105
+
106
+ ### Dwell-time histograms
107
+
108
+ `dcio.analysis.histogram` carries the log-binning arithmetic -- bin counts, bin
109
+ edges, and staircase coordinates -- and nothing else. Drawing stays with the
110
+ caller, because EKDIST, HJCFIT and SCALCS legitimately want different figures
111
+ over the same bins.
112
+
113
+ ```python
114
+ import numpy as np
115
+ from dcio.analysis.histogram import log_bin_histogram, staircase
116
+
117
+ counts, edges, nbdec = log_bin_histogram(record.periods.open_intervals, tres=25e-6)
118
+ x, y = staircase(edges, counts)
119
+ ax.semilogx(x, np.sqrt(y)) # Sigworth-Sine square-root ordinate
120
+ ```
121
+
122
+ Bins start at the resolution and each is `10 ** (1/nbdec)` wider than the last,
123
+ with `nbdec` chosen from the sample size (5 / 8 / 10 / 12). The last edge is
124
+ rounded up to a whole decade, so no interval falls outside the bins -- writing
125
+ that round-up with a natural log instead, as earlier code in this stack did,
126
+ gives a power of e and lets `numpy.histogram` drop the tail of the distribution
127
+ without saying so.
128
+
129
+ ## Running tests
130
+
131
+ ```bash
132
+ cd dcio # from the dcprogs root
133
+ pytest
134
+ ```
135
+
136
+ Tests cover write validation, round-trip fidelity, flag handling, edge cases,
137
+ and optional smoke tests against legacy SCN files in `examples/scn/`.
138
+
139
+ ## Documentation
140
+
141
+ See [`docs/scn_format.md`](docs/scn_format.md) for a complete description of
142
+ the binary file format, header fields, and unit conventions.
@@ -0,0 +1,18 @@
1
+ """
2
+ dcio
3
+ ======
4
+ I/O library for electrophysiology file formats used by the DCProgs suite.
5
+
6
+ Supported formats
7
+ -----------------
8
+ * **SCN** – SCAN idealised single-channel records (read + write).
9
+
10
+ Quick start
11
+ -----------
12
+ >>> from dcio.formats.scn import read, write
13
+ >>> rec = read("myfile.scn")
14
+ >>> print(rec)
15
+ """
16
+
17
+ __version__ = "0.1.0"
18
+ __author__ = "Remis Lape"
@@ -0,0 +1,35 @@
1
+ """Single-channel record analysis."""
2
+
3
+ from dcio.analysis.histogram import (
4
+ bins_per_decade,
5
+ log_bin_edges,
6
+ log_bin_histogram,
7
+ staircase,
8
+ )
9
+ from dcio.analysis.bursts import (
10
+ bursts_from_record,
11
+ extract_burst_intervals,
12
+ extract_bursts,
13
+ )
14
+ from dcio.analysis.record import (
15
+ Periods,
16
+ SingleChannelRecord,
17
+ from_scn,
18
+ impose_resolution,
19
+ set_periods,
20
+ )
21
+
22
+ __all__ = [
23
+ "Periods",
24
+ "SingleChannelRecord",
25
+ "from_scn",
26
+ "impose_resolution",
27
+ "set_periods",
28
+ "extract_bursts",
29
+ "extract_burst_intervals",
30
+ "bursts_from_record",
31
+ "bins_per_decade",
32
+ "log_bin_edges",
33
+ "log_bin_histogram",
34
+ "staircase",
35
+ ]
@@ -0,0 +1,243 @@
1
+ """Burst segmentation of an idealised single-channel record.
2
+
3
+ A *burst* is a run of openings and short shuttings delimited by shut intervals
4
+ longer than a critical time ``tcrit``. Segmentation sits directly on top of
5
+ :mod:`dcio.analysis.record`: it takes the resolved intervals a dead time
6
+ produced and cuts them into bursts.
7
+
8
+ Typical workflow::
9
+
10
+ from dcio.formats.scn import read
11
+ from dcio.analysis.record import from_scn
12
+ from dcio.analysis.bursts import extract_bursts
13
+
14
+ scr = from_scn(read("file.scn"), tres=25e-6)
15
+ lengths, n_openings = extract_bursts(
16
+ scr.resolved_intervals, scr.resolved_amplitudes,
17
+ tcrit=4e-3, flags=scr.resolved_flags,
18
+ )
19
+
20
+ or, equivalently, straight from the record::
21
+
22
+ from dcio.analysis.bursts import bursts_from_record
23
+
24
+ lengths, n_openings = bursts_from_record(scr, tcrit=4e-3)
25
+
26
+ Convention
27
+ ----------
28
+ The convention is the one EKDIST states in ``Bursts.slice_bursts`` and dcpyps
29
+ followed:
30
+
31
+ 1. no gap longer than ``tcrit`` is required before the first burst of a
32
+ record -- the first defined opening is a valid burst start;
33
+ 2. an unusable interval is a valid end of burst.
34
+
35
+ Both matter. Time-course fitting in SCAN leaves the last interval of a record
36
+ with no defined length, flagged unusable; it still ends the burst before it. A
37
+ leading interval may likewise be bad, and is discarded, but the first defined
38
+ opening after it starts a real burst. Dropping the runs at both ends -- as
39
+ earlier versions of this code did -- loses two bursts from every record, which
40
+ at 30 uM in the Burzomato 2004 set is a third of the data.
41
+ """
42
+
43
+ from __future__ import annotations
44
+
45
+ import numpy as np
46
+
47
+ from dcio.formats.scn import FLAG_UNUSABLE
48
+
49
+ __all__ = [
50
+ "extract_bursts",
51
+ "extract_burst_intervals",
52
+ "bursts_from_record",
53
+ ]
54
+
55
+
56
+ def _burst_segments(intervals, amplitudes, tcrit, flags=None):
57
+ """Split a record into bursts and return them as (interval, amplitude) pairs.
58
+
59
+ Shared by :func:`extract_bursts` and :func:`extract_burst_intervals`, which
60
+ differ only in what they report about one and the same segmentation.
61
+
62
+ Parameters
63
+ ----------
64
+ intervals, amplitudes : array_like
65
+ Alternating interval record (ideal or apparent). Durations in
66
+ seconds, amplitudes in pA with 0 meaning shut.
67
+ tcrit : float
68
+ Critical shut time separating within- from between-burst gaps [s].
69
+ Its magnitude only -- see :func:`bursts_from_record` for the sign
70
+ convention that HJCFIT places on the same number.
71
+ flags : array_like of int, optional
72
+ Per-interval SCN property flags. An interval is unusable when
73
+ ``flags & FLAG_UNUSABLE`` is set. Without them no interval is treated
74
+ as unusable, which is right for a simulated record and wrong for an
75
+ experimental one.
76
+
77
+ Returns
78
+ -------
79
+ list of list of (float, float)
80
+ One list of ``(interval, amplitude)`` pairs per burst, each starting
81
+ and ending on an opening.
82
+ """
83
+ intervals = np.asarray(intervals, float)
84
+ amplitudes = np.asarray(amplitudes, float)
85
+ if intervals.size == 0:
86
+ return []
87
+
88
+ if flags is None:
89
+ unusable = np.zeros(intervals.shape, dtype=bool)
90
+ else:
91
+ unusable = (np.asarray(flags, int) & FLAG_UNUSABLE) != 0
92
+
93
+ # A burst ends at a between-burst gap or at an interval of unknown length.
94
+ # An unusable interval has no measured duration, so it is never compared
95
+ # with tcrit.
96
+ # Strictly greater: tcrit is the time such that gaps *longer* than it are
97
+ # between-burst. EKDIST's Bursts.slice_bursts uses the same test, and a
98
+ # shut interval exactly equal to tcrit is within-burst by that reading.
99
+ separator = ((amplitudes == 0.0) & (intervals > tcrit) & ~unusable) | unusable
100
+
101
+ # Trim to the first and last defined opening, so the record begins and
102
+ # ends on one. What lies outside is a shut interval or an unusable one,
103
+ # and neither belongs to a burst.
104
+ opening = (amplitudes != 0.0) & ~unusable
105
+ if not opening.any():
106
+ return []
107
+ lo = int(np.argmax(opening))
108
+ hi = int(len(opening) - np.argmax(opening[::-1]))
109
+
110
+ raw, seg = [], []
111
+ for t, a, sep in zip(intervals[lo:hi], amplitudes[lo:hi], separator[lo:hi]):
112
+ if sep:
113
+ if seg:
114
+ raw.append(seg)
115
+ seg = []
116
+ else:
117
+ seg.append((t, a))
118
+ if seg:
119
+ raw.append(seg)
120
+
121
+ bursts = []
122
+ for seg in raw:
123
+ while seg and seg[0][1] == 0.0: # trim leading shut
124
+ seg = seg[1:]
125
+ while seg and seg[-1][1] == 0.0: # trim trailing shut
126
+ seg = seg[:-1]
127
+ if seg:
128
+ bursts.append(seg)
129
+ return bursts
130
+
131
+
132
+ def extract_bursts(intervals, amplitudes, tcrit, flags=None):
133
+ """Split a record into bursts at shut intervals longer than ``tcrit``.
134
+
135
+ Each burst is trimmed to start and end on an opening.
136
+
137
+ Parameters
138
+ ----------
139
+ intervals, amplitudes : array_like
140
+ Alternating interval record (ideal or apparent).
141
+ tcrit : float
142
+ Critical shut time separating within- from between-burst gaps [s].
143
+ flags : array_like of int, optional
144
+ Per-interval SCN property flags; see :func:`_burst_segments`.
145
+
146
+ Returns
147
+ -------
148
+ lengths : ndarray
149
+ Burst lengths [s] (first opening start to last opening end).
150
+ n_openings : ndarray of int
151
+ Number of (apparent) openings in each burst.
152
+
153
+ See Also
154
+ --------
155
+ extract_burst_intervals : the same bursts, as interval sequences.
156
+ """
157
+ bursts = _burst_segments(intervals, amplitudes, tcrit, flags)
158
+ lengths = [sum(t for t, _ in seg) for seg in bursts]
159
+ nops = [sum(1 for _, a in seg if a != 0.0) for seg in bursts]
160
+ return np.array(lengths), np.array(nops, dtype=int)
161
+
162
+
163
+ def extract_burst_intervals(intervals, amplitudes, tcrit, flags=None):
164
+ """Split a record into bursts and return the intervals of each.
165
+
166
+ The segmentation is that of :func:`extract_bursts`, which reduces each
167
+ burst to its length and its number of openings. Maximum-likelihood
168
+ fitting of missed-events mechanisms needs the interval sequences
169
+ themselves: the HJC likelihood is a product of matrices, one per interval,
170
+ so the order and the individual durations both matter.
171
+
172
+ Parameters
173
+ ----------
174
+ intervals, amplitudes : array_like
175
+ Alternating interval record (ideal or apparent).
176
+ tcrit : float
177
+ Critical shut time separating within- from between-burst gaps [s].
178
+ flags : array_like of int, optional
179
+ Per-interval SCN property flags; see :func:`_burst_segments`.
180
+
181
+ Returns
182
+ -------
183
+ list of ndarray
184
+ One array of interval durations [s] per burst, alternating open and
185
+ shut and both starting and ending with an opening -- so every array
186
+ has odd length, which is what the missed-events likelihood requires.
187
+
188
+ See Also
189
+ --------
190
+ extract_bursts : the same bursts, as lengths and opening counts.
191
+ """
192
+ return [np.array([t for t, _ in seg], dtype=float)
193
+ for seg in _burst_segments(intervals, amplitudes, tcrit, flags)]
194
+
195
+
196
+ def bursts_from_record(record, tcrit, intervals_only=False):
197
+ """Segment a :class:`~dcio.analysis.record.SingleChannelRecord` into bursts.
198
+
199
+ Convenience over :func:`extract_bursts` that takes the intervals,
200
+ amplitudes and flags from the record itself, so a caller can get neither
201
+ the flags nor the input series wrong.
202
+
203
+ Segmentation runs on the record's **periods**, not on its resolved
204
+ intervals, and that distinction is not cosmetic.
205
+ :func:`~dcio.analysis.record.impose_resolution` emits a fresh open
206
+ interval whenever the fitted amplitude changes, so a record idealised with
207
+ sub-conductance levels contains runs of consecutive open intervals: the
208
+ experimental example shipped with dcio has 1770 such adjacencies in 3232
209
+ resolved intervals. Segmenting those directly gives bursts that do not
210
+ alternate open/shut, and counts each sub-level as a separate opening.
211
+ :func:`~dcio.analysis.record.set_periods` merges them, which is what makes
212
+ the burst an alternating sequence the missed-events likelihood can consume.
213
+
214
+ Burst count and burst lengths are identical either way; the number of
215
+ openings per burst is not.
216
+
217
+ Parameters
218
+ ----------
219
+ record : SingleChannelRecord
220
+ A record with a dead time already applied.
221
+ tcrit : float
222
+ Critical shut time [s]. Only its magnitude is used. The sign carries
223
+ no meaning here, but it does downstream: HJCFIT reads a negative
224
+ ``tcrit`` as a flag selecting equilibrium vectors (Colquhoun & Hawkes
225
+ 1982) over CHS vectors (Colquhoun, Hawkes & Srodzinski 1996), and the
226
+ same number is passed to both. Taking the magnitude here means a
227
+ caller can hand the same value to either without thinking about it.
228
+ intervals_only : bool, default False
229
+ Return the per-burst interval sequences instead of lengths and
230
+ opening counts.
231
+
232
+ Returns
233
+ -------
234
+ lengths, n_openings : ndarray, ndarray
235
+ When *intervals_only* is False.
236
+ list of ndarray
237
+ When *intervals_only* is True.
238
+ """
239
+ periods = record.periods
240
+ args = (periods.intervals, periods.amplitudes, abs(tcrit), periods.flags)
241
+ if intervals_only:
242
+ return extract_burst_intervals(*args)
243
+ return extract_bursts(*args)