Core Concepts
ionique represents nanopore data as a segment tree — a hierarchy of nested segments where each level has a named rank. Understanding this model is key to using the library effectively.
Segments and ranks
A segment is a contiguous slice of an ionic current trace, defined by
start and end sample indices. Every segment has a rank — a string
label that describes its level in the hierarchy.
Common ranks:
"file"— the full recording (aTraceFile)."vstep"— a voltage step within the file."vstepgap"— a trimmed voltage step (edge artifacts removed)."event"— a detected translocation event.
You can use any string as a rank. Parsers create children at a newrank
you specify.
Segment vs MetaSegment
ionique provides two segment types optimized for different use cases:
Class |
Stores |
Use when |
|---|---|---|
|
Full current array ( |
You need direct access to raw signal values |
|
Only |
You want memory-efficient storage (thousands of events) |
MetaSegment computes statistics on the fly by slicing its ancestor’s
current array:
from ionique.core import Segment, MetaSegment
import numpy as np
# Segment holds its own data
seg = Segment(current=np.random.randn(1000), start=0, end=1000, rank="event")
print(seg.mean) # computed from seg.current
# MetaSegment holds only boundaries
meta = MetaSegment(start=0, end=1000, rank="event", parent=some_file)
print(meta.mean) # computed from parent's current[0:1000]
Convert a Segment to a MetaSegment to free memory:
seg.to_meta()
# seg is now a MetaSegment — the current array is released
Note
MetaSegment.current requires a valid parent chain up to a file-level
segment. It returns None if the chain is broken.
Tree traversal
Going down — traverse_to_rank(rank) recursively collects all
descendants at the given rank:
# Get every event across all voltage steps
events = trace.traverse_to_rank("event")
print(f"{len(events)} events found")
Going up — climb_to_rank(rank) walks up the parent chain:
# From an event, find its parent voltage step
vstep = event.climb_to_rank("vstep")
print(f"Event belongs to vstep at samples {vstep.start}–{vstep.end}")
# Or go all the way to the file
file_seg = event.climb_to_rank("file")
Other traversal helpers:
# Get the root of the tree
root = event.get_top_parent()
# Summary of all ranks and their counts
trace.summary()
# {'file': 1, 'vstep': 5, 'event': 42}
Feature lookup
get_feature(name) searches the segment’s unique_features dictionary,
then climbs to ancestors until found:
# sampling_freq is stored at the file level
trace.unique_features["sampling_freq"] = 100000
# Any descendant can access it
event = trace.traverse_to_rank("event")[0]
fs = event.get_feature("sampling_freq") # returns 100000
This pattern avoids duplicating metadata across thousands of event segments.
You can also store per-segment features:
event.unique_features["blockade_depth"] = 0.45
event.unique_features["dwell_time"] = 0.003
Parsing: creating children
The parse() method subdivides a segment using a parser object:
from ionique.parsers import AutoSquareParser
detector = AutoSquareParser(threshold_baseline=0.7, expected_conductance=1.9)
# Detect events within each voltage step
trace.parse(detector, newrank="event", at_child_rank="vstep")
What happens:
parse()finds all children at rank"vstep".For each vstep, the parser analyzes its current data.
Detected boundaries become new child segments at rank
"event".
The at_child_rank parameter controls which level gets parsed. Without it,
the parser runs on the segment itself.
Chaining parsers — parse at successively deeper ranks:
# First: detect blockade events
trace.parse(event_detector, newrank="event", at_child_rank="vstep")
# Then: segment sub-states within each event
trace.parse(sub_state_splitter, newrank="state", at_child_rank="event")
Building trees manually
You can construct segment trees without parsers:
from ionique.core import MetaSegment
# Create a parent
parent = MetaSegment(start=0, end=100000, rank="vstep")
# Create children
events = [
MetaSegment(start=1000, end=1500, rank="event", parent=parent),
MetaSegment(start=3000, end=3800, rank="event", parent=parent),
MetaSegment(start=7000, end=7200, rank="event", parent=parent),
]
parent.add_children(events)
# Children are sorted by start position
for ev in parent.children:
print(f" event at {ev.start}–{ev.end}")
To remove all children at a rank and re-parse:
parent.clear_children()
parent.parse(new_parser, newrank="event")
Segment statistics
Both Segment and MetaSegment expose computed properties:
event = trace.traverse_to_rank("event")[0]
event.mean # np.mean(current)
event.std # np.std(current)
event.min # np.min(current)
event.max # np.max(current)
event.n # number of samples
event.duration # time span in seconds (requires sampling_freq)
These are computed on every access (not cached), so store results in variables if you need them repeatedly.
Serialization
Segments can be serialized to JSON for storage or transfer:
# To JSON string
json_str = event.to_json()
# To file
event.to_json("event_data.json")
# Reconstruct
from ionique.core import MetaSegment
restored = MetaSegment.from_json("event_data.json")