Skip to content

Using Labeled Data

After you annotate a Record in Ephyr, you can load the same workspace from Python and work with signals, sessions, events, periods, and add-on outputs. The example at the bottom of this page shows the same API. Folder layout is described in Files Format.

Main objects

Object Role
Workspace Open a <name>_ephyr folder, list Configs and Records
EphyrSessionManager Open a Record, switch sessions and sources, access record_data and user_session
RecordData source_view plus memmaps[channel_idx] (all segments of the active source, concatenated)
SourceView Sample rate, channels, physical segments, display segments, Record time ↔ local time
UserSession Events, periods, vocabularies, recording description, and gui_setup
GuiSetup View state: active source, navigation mode, visible window, channel groups, filters
events_table Convenience view of events with names, Record time, and overlapping period names

Workspace structures

Defined in ephyr/core/manifests.py and stored in workspace.ephyr, config.ephyr and record.ephyr. They describe the recording itself and never change when you annotate.

ConfigManifest

Field Meaning
config_id, name, uid Folder id, display name, random id
main_source_id Source every Record of this Config is checked against
sources SourceSpec: signature (sample_rate, dtype, channel names and units) and descriptive fields
records Ordered list of Record ids

RecordManifest

Field Meaning
record_id, name, uid Folder id, display name, random id
start_datetime Recording start reported by the source
inputs Original recordings: format, root name, content fingerprint, reader options
sources RecordSource: per-channel calibration, t_start_ms, and segments

A segment is {start_sample_idx, number_of_samples, t_start_ms, label}. Sample i of a segment is at Record time t_start_ms + i * 1000 / sample_rate.

SourceView

Built from the Config source spec and the Record source of the active stream.

Member Meaning
source_id, source_name, source_format Identity of the stream
sample_rate, number_of_channels Sampling and channel count
channel_info Names, units, probes, analog/digital ranges, impedance
physical_segments Segments stored on disk
display_segments What the GUI currently shows (one continuous range, each physical segment, or one window around each period of a label)
local_ms_of_time(segment_idx, t_ms) / time_of_local_ms(...) Record time ↔ position inside a display segment
gap_positions_ms(segment_idx) Interruptions inside a concatenated display segment

Session structures

Defined in ephyr/core/ephyr_session.py. One Record can hold several sessions (folders under sessions/), each with its own labels and view state.

EphyrSessionManager

Member Type Meaning
workspace Optional[Workspace] Open workspace
config / record manifests Current Config and Record
record_dir Optional[Path] Folder of the open Record
record_data Optional[RecordData] Active source samples
source_view Optional[SourceView] Metadata of the active source
current_user_session Optional[UserSession] Active session
user_session UserSession Same as current_user_session, raises when nothing is loaded
session_is_active bool A Record and a session are loaded
other_session_names List[str] Session folders of the Record besides the active one
add_ons_data_folder Path add_ons/data/ of the Record

Loading API:

Call Purpose
open_record(workspace, config_id, record_id) Load a Record of a workspace (no session yet)
new_user_session(session_name) Create an empty session; source annotations become its starting events and periods
switch_sessions(session_name) Load another session of the same Record
save_user_session() Write the active session back to sessions/<name>/
load_source / switch_source(source_id) Change the active source

RecordData

Member Type Meaning
source_view SourceView Active source
memmaps Tuple[np.memmap, ...] Raw int16 samples, indexed [channel_idx], all segments concatenated
from_int16_to_physical(data, channel_idx) method Scale raw samples to physical values in the channel unit
process_data_pipeline(..., segment_idx, ...) method The same filtering / decimation pipeline the GUI draws with
process_data_pipeline_multi_segment(..., segment_indexes, ...) method Same pipeline for several display segments at once

A slice of samples of a physical segment starts at source_view.physical_segments[i].start_sample_idx.

UserSession

Field Type Meaning
session_name str Folder name of this session
changes_saved bool False when the session has unsaved changes
events List[Event] Point labels in Record time
events_vocabulary Dict[int, EventVocabularyEntry] Event names and colors by id
periods List[Period] Interval labels in Record time
periods_vocabulary Dict[int, PeriodVocabularyEntry] Period names and colors by id
experiment_description str Free-text recording description
gui_setup GuiSetup View state
events_table method Events joined with their names, optional segment, and the periods they fall into

Vocabulary helpers: add_event_vocabulary, get_event_vocabulary_name, get_event_vocabulary_color, rename_event_vocabulary, set_event_vocabulary_color, remove_event_vocabulary, and the matching *_period_vocabulary methods.

GuiSetup

Field Type Meaning
right_panel_widgets List[RightPanelWidgetEnum] Which side-panel widgets are shown
add_ons Dict[str, AddOnSetup] Per-add-on view / transform toggles
traces_are_shown bool Draw signal traces
channel_names_are_shown bool Draw channel names
channel_infos_are_shown bool Draw ChannelSetup.info on each channel cell
events_are_shown bool Draw events
periods_are_shown bool Draw periods
active_source_id Optional[str] Source on screen
navigation_mode NavigationModeEnum continuous, segment, or period_label
period_label_id Optional[int] Period vocabulary used in period_label mode
pre_ms / post_ms float Window around each period in period_label mode
current_segment_idx int Display segment on screen
overlay_segment_idxs List[int] Other display segments drawn in gray (empty = overlay off)
start_point int First sample of the visible window inside the display segment
duration_ms int Width of the visible window
time_step_ms int Timebar step in milliseconds. In Beginner mode the GUI sets this to half of duration_ms.
autoscroll_step_interval_ms int How often auto-scroll advances, in milliseconds. New sessions default to 1000.
number_of_dots_to_display int Target point count per trace (decimation)
channels_groups List[ChannelGroup] Channel groups in display order
channels_setup Dict[int, ChannelSetup] Per-channel style of the active source
other_sources_setup Dict[str, SourceGuiSetup] Channel groups of inactive sources
visual_attachment str Optional visual attachment (image URL or embedded file)

transferable_copy copies this setup onto another Record of the same Config. Positions inside the data (current_segment_idx, overlay_segment_idxs, start_point) stay those of the target Record.

ChannelGroup

Field Type Meaning
channel_indexes List[int] Channels of the group, in display order
enabled_indexes Set[int] Channels that are actually drawn
filters List[FilterConfig] Filter chain applied to the group
name str Group title
is_shown bool Group visibility
is_auxiliary bool Auxiliary group (per-channel style, no windowing)
cut_traces bool Clip traces to their cell
group_layout GroupLayout Placement of the group on the panel
channels_layout ChannelsLayout Grid of channels inside the group

Helpers: effective_grid(), grid_dims(), visible_window(), visible_cells(), visible_enabled_channels(), visible_channels(), clamp_layout().

ChannelsLayout

Field Type Meaning
columns_num int Columns in the grid
columns_num_to_show int Columns visible at once
cur_column_idx int First visible column
rows_num Optional[int] Rows in the grid (derived when None)
rows_num_to_show int Rows visible at once
cur_row_idx int First visible row
enable_custom_layout bool Use layout_table instead of a plain list
draw_borders bool Draw cell borders
layout_table Optional[List[List[int]]] Channel index per grid cell (-1 for an empty cell)

GroupLayout

Field Type Meaning
layout_row_idx int Row of the group on the panel. Groups with the same row are placed side by side.
layout_column_idx int Position of the group within that row.
height_ratio int Relative height of the row. The row uses the maximum height ratio among its groups.
width_ratio int Relative width of the group among the groups that share its row.

Unoccupied cells are not reserved: space is packed along the row. A group that is the only one in its row takes the full width. Two groups in the top row and one group in the row below means the lower group spans the whole row.

ChannelSetup

Field Type Meaning
scale float Full cell-height amplitude, in the channel's unit
y_offset float Vertical offset
color str Trace color
info str Free-text note, e.g. the brain area

Event, Period, vocabularies and EventTableRow

Structure Fields
Event id: int, event_name_id: int, t_ms: float, is_bad: bool, optional source_id, channel, value
Period id: int, period_name_id: int, start_t_ms: float, end_t_ms: float, optional source_id, channel, value
EventVocabularyEntry name: str, color: str, is_visible: bool
PeriodVocabularyEntry name: str, color: str, is_visible: bool
EventTableRow name: str, t_ms: float, segment_idx: Optional[int], time_in_segment_ms: Optional[float], is_bad: bool, periods: List[str]

AddOnSetup

Field Type Meaning
view_enabled bool Add-on draws on the signal panel
transform_enabled bool Add-on transforms the displayed signal

FilterConfig

FilterConfig is a discriminated union on filter_type; every filter has enabled: bool and order: int besides the fields below.

filter_type Class Own fields
butter_lowpass ButterworthLowPassFilter cutoff_hz
butter_highpass ButterworthHighPassFilter cutoff_hz
butter_bandpass ButterworthBandPassFilter lowcut_hz, highcut_hz
cheby_bandpass ChebyshevBandPassFilter lowcut_hz, highcut_hz, ripple_db
notch NotchFilter notch_freq_hz, q_factor

Spike sets written by add-ons

The Spike utils package (distributed separately) stores every spike set — detected by Spike detection or imported from a WEEGIT .spk file by Spike importer — in one shared folder of the Record:

add_ons/data/spike_sets/{set_name}/spike_set_meta.json
add_ons/data/spike_sets/{set_name}/{segment_idx}.spikes.json

Files are named by the segment index used at write time (the current display segment for detection, a physical segment for .spk import). Older {n}.spikes.json payloads that still have sweep_idx are accepted and rewritten as segment_idx on load.

spike_set_meta.json (SpikeSetMeta):

Field Meaning
source detected or imported
detector_name Detection / sorting method, e.g. mad, adaptive_mad, kilosort
group_key, group_name Channel group the set was detected on (empty for imported sets)
preprocessing_pipeline, threshold, adaptive_sigma Detection parameters
clusters Cluster labels present in the set (0 means unclustered)
source_file, created_at Origin of an imported set

{segment_idx}.spikes.json (SpikesPayload):

Field Meaning
segment_idx Segment the spikes belong to
sample_rate Sample rate the times were computed with
source, detector_name, threshold, polarity flags, merge window, ignore rules How the set was produced
group_key, group_name Channel group of the set
source_file .spk file for imported sets
spikes_by_channel Map of channel index → list of spikes

Each spike (SpikePoint):

Field Meaning
sample_idx Sample index inside the segment (may be null)
time_ms Time inside the segment
value Peak amplitude, negative for downward spikes
polarity negative or positive
cluster Sorting label inside the set; 0 means unclustered

Reading these files with the standard library json module keeps analysis scripts independent of the add-on package. Add-on authors can instead use SpikesPayload / read_spikes_payload from the Spike utils shared helpers.

Example script

import json
from pathlib import Path

from ephyr.core.ephyr_session import EphyrSessionManager
from ephyr.core.workspace import Workspace

workspace = Workspace.open(Path("/path/to/study_ephyr"))
config = workspace.configs()[0]
record = workspace.records(config.config_id)[0]

session = EphyrSessionManager()
session.open_record(workspace, config.config_id, record.record_id)
session.switch_sessions(session.other_session_names[0])

view = session.source_view
print(view.source_name, view.sample_rate, view.number_of_channels)

start, n = view.physical_segments[0].start_sample_idx, min(10_000, view.physical_segments[0].number_of_samples)
for ch_idx in range(view.number_of_channels):
    channel_data = session.record_data.memmaps[ch_idx][start:start + n]
    print(
        f"Channel {view.channel_info.name[ch_idx]} max value: ",
        max(session.record_data.from_int16_to_physical(channel_data, ch_idx)),
        view.channel_unit_label(ch_idx),
    )

print(session.user_session.gui_setup)

for event in session.user_session.events_table(view):
    if "PERIOD_NAME" in event.periods:
        continue
    print(f"event={event.name} t_ms={event.t_ms} bad={event.is_bad} periods={event.periods}")

for period in session.user_session.periods:
    period_name = session.user_session.get_period_vocabulary_name(period.period_name_id)
    print(f"period={period_name} start={period.start_t_ms} ms end={period.end_t_ms} ms")

spike_sets_root = session.add_ons_data_folder / "spike_sets"
if spike_sets_root.exists():
    for set_dir in sorted(spike_sets_root.iterdir()):
        if not set_dir.is_dir():
            continue
        meta_path = set_dir / "spike_set_meta.json"
        meta = json.loads(meta_path.read_text(encoding="utf-8")) if meta_path.exists() else {}
        print(f"spike set={set_dir.name} source={meta.get('source')} clusters={meta.get('clusters')}")
        for spikes_path in sorted(set_dir.glob("*.spikes.json")):
            payload = json.loads(spikes_path.read_text(encoding="utf-8"))
            for ch_idx, spikes in payload["spikes_by_channel"].items():
                print("  file", spikes_path.name, "channel", ch_idx, "spikes:", len(spikes))

Notes

  • Indexing for memmaps is [channel_idx], then a sample slice over the concatenated source. Physical-segment bounds come from source_view.physical_segments.
  • from_int16_to_physical scales using the channel’s analog/digital range only: values are in the channel unit (for example uV, mV, pA), exactly as the GUI pipeline draws them.
  • session.user_session and session.current_user_session refer to the active session after switch_sessions / new_user_session.
  • Development add-ons write to dev_-prefixed folders, so spike sets of a development build live in add_ons/data/dev_spike_sets/.
  • Run analysis scripts in the same Python environment where ephyr is installed (pip install ephyr).