pylsl icon indicating copy to clipboard operation
pylsl copied to clipboard

Metadata `<channels>` Not Received in pylsl 1.17.6 (macOS 15.1.1, Python 3.11)

Open kimit0310 opened this issue 11 months ago • 2 comments

Description
I’m running a minimal script to demonstrate that pylsl 1.17.6 on macOS 15.1.1 (24B91) with Python 3.11 fails to transmit the <channels> metadata. On the sender side, info.as_xml() shows the expected <desc><channels>...<channel><label>Fz</label> etc. But on the receiver side, the same stream’s info.as_xml() prints <desc />, and get_channel_labels() returns None.

Environment

  • OS: macOS 15.1.1 (24B91)
  • Python: 3.11 (fresh conda environment)
  • pylsl: 1.17.6 (installed via pip install pylsl==1.17.6)
  • Nominal SRate: 0.0 (also tested non-zero; same result)

Steps to Reproduce

import time
import threading
from pylsl import StreamInfo, StreamOutlet, StreamInlet, resolve_streams

def sender():
    info = StreamInfo(
        name="LabelTestStream",
        type="EEG",
        channel_count=2,
        nominal_srate=0.0,
        channel_format="float32",
        source_id="channel_label_test_001"
    )
    desc = info.desc().append_child("channels")
    for lbl in ["MyFz", "MyCz"]:
        ch = desc.append_child("channel")
        ch.append_child_value("label", lbl)
        ch.append_child_value("unit", "µV")

    print("=== SENDER: info.as_xml() ===")
    print(info.as_xml())

    outlet = StreamOutlet(info)
    print("Sender: started pushing data...")
    while True:
        outlet.push_sample([1.1, 2.2])
        time.sleep(0.1)

def receiver():
    time.sleep(1)  # allow sender to start
    print("Receiver: resolving streams...")
    streams = resolve_streams()
    sinfo = next((s for s in streams if s.name() == "LabelTestStream"), None)
    if not sinfo:
        print("No LabelTestStream found.")
        return

    print("=== RECEIVER: info.as_xml() ===")
    print(sinfo.as_xml())
    print("Receiver: get_channel_labels() =>", sinfo.get_channel_labels())

    inlet = StreamInlet(sinfo)
    while True:
        sample, ts = inlet.pull_sample(timeout=0.5)
        if sample:
            print("Receiver: sample=", sample, "ts=", ts)
        time.sleep(1.0)

def main():
    threading.Thread(target=sender, daemon=True).start()
    receiver()

if __name__ == "__main__":
    main()

Console Output Excerpts

=== SENDER: info.as_xml() ===
<?xml version="1.0"?>
<info>
  <name>LabelTestStream</name>
  <type>EEG</type>
  <channel_count>2</channel_count>
  <channel_format>float32</channel_format>
  <source_id>channel_label_test_001</source_id>
  <nominal_srate>0.000000000000000</nominal_srate>
  ...
  <desc>
    <channels>
      <channel>
        <label>MyFz</label>
        <unit>µV</unit>
      </channel>
      <channel>
        <label>MyCz</label>
        <unit>µV</unit>
      </channel>
    </channels>
  </desc>
</info>

Sender: started pushing data...

Receiver: resolving streams...
=== RECEIVER: info.as_xml() ===
<?xml version="1.0"?>
<info>
  <name>LabelTestStream</name>
  <type>EEG</type>
  <channel_count>2</channel_count>
  <channel_format>float32</channel_format>
  ...
  <desc />
</info>

Receiver: get_channel_labels() => None
Receiver: sample= [ ... ] ts= ...

Expected Behavior

The receiver's info.as_xml() should contain <desc><channels> with <channel> child elements, and get_channel_labels() should return ["MyFz", "MyCz"].

Actual Behavior

<desc /> is empty on the reciever side, and info.get_channel_labels() is None

kimit0310 avatar Jan 29 '25 21:01 kimit0310