Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
kimit0310 opened this issue Jan 29, 2025 · 2 comments

Comments

@kimit0310
Copy link

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

@cboulay
Copy link
Contributor

cboulay commented Jan 29, 2025

Sorry, this is an unintuitive and not well documented aspect, but the info objects returned by the resolver are incomplete. You need to use them to create an inlet first, then you can ask the inlet for the full info. Maybe they should be different classes or there should be a flag on the info object to indicate it is partial only.

Maybe we can at least make it so the getters raise an error with a useful message when attempting to retrieve metadata that is known to be absent in the partial info. What do you think?

@kimit0310
Copy link
Author

Thank you so much for the quick response! I confirmed that calling inlet.info() provides the full metadata, and I can now retrieve my channel labels as expected.

A flag or short note in the documentation to indicate that resolve_streams() returns only a partial StreamInfo would be really helpful, and throwing a more descriptive error when accessing the missing metadata might be even more user-friendly, but either approach would solve the confusion. Again, thanks for the quick help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants