Skip to content

Commit 82920fa

Browse files
minor
1 parent c0f1f0a commit 82920fa

2 files changed

Lines changed: 114 additions & 8 deletions

File tree

content/jobs/assistant.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ Undergraduates residing in the UK can apply for a paid 6 weeks (30hr per week) i
5757

5858
More information [**here**](https://southcoastbiosciencesdtp.ac.uk/undergraduate-summer-studentship-programme/).
5959

60+
## International Junior Research Associate (IJRA)
61+
62+
- See here: https://www.sussex.ac.uk/suro/current/ijra
63+
6064

6165
## Other Bursaries
6266

content/post/2026-01-09-EventTriggers/index.md

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Lets start with some basics!
2727

2828
## What does this mean and when is this useful?
2929

30-
Lab streaming layer (LSL) is a system used to receive, synchronise and stream signals from multiple inputs during experiments. LSL is designed to help researchers easily compare their data across multiple technologies, as time synchrony is integral for making valid comparisons.
30+
Lab streaming layer (LSL) is a system used to receive, synchronise and stream signals from multiple inputs during experiments. LSL is designed to help researchers easily compare their data across multiple technologies, as time synchrony is integral for making meaningful analyses.
3131

3232
When collecting data on stimulus response during experiments, it's important that the stimulus onset is recorded precisely, especially if this is mapped onto physiological responses as this has implications on how we interpret our data. One good example of a use case is that you have a Muse headband or any other device that can scream via LSL, and you want to precisely mark events in it.
3333

@@ -41,15 +41,109 @@ This blog will help you understand the set-up for event triggers. For an example
4141

4242
## How to set up event triggers
4343

44-
#### Requirements:
44+
### Requirements
4545

4646
- **Your experiment is in-person:** In order to use these event triggers, you will need to run your experiment on a local host server, which will need to be manually set up for each trial. Therefore, this setup is intended for in-person experiments that are led by a researcher to set up the participant's screen.
4747

48-
- **The researcher and the participant(s) each have a computer:** The participant's computer will send the markers to the researcher's computer, which will be used to record the events (and all other signals using LSL) during the trial.
48+
- **You have two computers, one for the participant, and for recording:** The participant's computer will display the experiment andsend the markers to the researcher's computer, which will be used to capture the LSL streams record the events.
49+
50+
### The LSL bridge script
51+
52+
The LSL bridge Python script is the one responsible for actually sending the markers to LSL- it 'listens' for messages from the browser that the participant is doing the experiment from, and converts them into 'markers' for your recording software (such as LabRecorder) will receive.
53+
54+
<details>
55+
56+
<summary>See an example of a full script</summary>
57+
58+
```python
59+
from http.server import BaseHTTPRequestHandler, HTTPServer
60+
from urllib.parse import urlparse, parse_qs
61+
from mne_lsl.lsl import StreamInfo, StreamOutlet, local_clock
62+
import threading
63+
64+
# ---------------------------------------------------------------------
65+
# CONFIG
66+
# ---------------------------------------------------------------------
67+
LSL_STREAM_NAME = "jsPsychMarkers"
68+
LSL_STREAM_TYPE = "Markers"
69+
LSL_SOURCE_ID = "jspsych-lsl-bridge"
70+
SERVER_HOST = "0.0.0.0"
71+
SERVER_PORT = 5000
72+
# ---------------------------------------------------------------------
73+
74+
# Create an LSL outlet for event markers
75+
info = StreamInfo(
76+
name=LSL_STREAM_NAME,
77+
stype=LSL_STREAM_TYPE,
78+
n_channels=1,
79+
sfreq=0,
80+
dtype="string",
81+
source_id=LSL_SOURCE_ID,
82+
)
83+
84+
desc = info.desc
85+
desc.append_child_value("manufacturer", "jsPsych")
86+
channels = desc.append_child("channels")
87+
ch = channels.append_child("channel")
88+
ch.append_child_value("label", "JsPsychMarker")
89+
ch.append_child_value("unit", "string")
90+
ch.append_child_value("type", "Marker")
91+
92+
outlet = StreamOutlet(info)
93+
94+
# ---------------------------------------------------------------------
95+
# HTTP Request Handler
96+
# ---------------------------------------------------------------------
97+
class MarkerHandler(BaseHTTPRequestHandler):
98+
def do_GET(self):
99+
parsed = urlparse(self.path)
100+
params = parse_qs(parsed.query)
101+
path = parsed.path
102+
103+
if path == "/sync":
104+
# Return current LSL clock to JS
105+
ts = local_clock()
106+
self.send_response(200)
107+
self.end_headers()
108+
self.wfile.write(str(ts).encode())
109+
110+
elif path == "/marker":
111+
value = params.get("value", ["1"])[0]
112+
ts_js = params.get("ts", [None])[0]
113+
114+
if ts_js is not None:
115+
ts = float(ts_js)
116+
else:
117+
ts = local_clock()
118+
119+
outlet.push_sample([value], ts)
120+
print(f"→ Marker {value} @ {ts:.6f}")
121+
122+
self.send_response(200)
123+
self.end_headers()
124+
self.wfile.write(b"OK")
125+
126+
else:
127+
self.send_response(404)
128+
self.end_headers()
129+
130+
# ---------------------------------------------------------------------
131+
def run_server():
132+
server_address = (SERVER_HOST, SERVER_PORT)
133+
httpd = HTTPServer(server_address, MarkerHandler)
134+
print(f"\n[LSL Bridge] Serving on http://{SERVER_HOST}:{SERVER_PORT}")
135+
print(f"[LSL Bridge] Stream '{LSL_STREAM_NAME}' ready for LabRecorder.\n")
136+
httpd.serve_forever()
137+
138+
# ---------------------------------------------------------------------
139+
if __name__ == "__main__":
140+
server_thread = threading.Thread(target=run_server)
141+
server_thread.start()
142+
```
143+
</details>
144+
145+
### Configuration of the Python Script
49146

50-
#### The LSL bridge script:
51-
52-
The LSL bridge will send the markers to LSL- it 'listens' for messages from the browser that the participant is doing the experiment from, and converts them into 'markers' for your recording software (such as LabRecorder) will receive.
53147

54148
1. Configuration
55149

@@ -88,9 +182,9 @@ The LSL bridge will send the markers to LSL- it 'listens' for messages from the
88182
- `run_server()`: Starts the HTTP server and prints a confirmation message that it is ready for LabRecorder.
89183
- `threading.Thread(...)`: Runs the server in a separate thread so it doesn't block the main Python process.
90184

91-
#### Synchronise your event triggers in the html script:
185+
### Configuration of the JsPsych HTML script
92186

93-
This is 'promise-based', meaning it is coded to wait until it receives a signal from the participant's computer. This javascript code is designed to track the exact time it is on the participant's computer and send markers precisely aligned to that time.
187+
The code sending triggers from the browser to the LSL bridge script is written in javascript. It is 'promise-based', meaning it is coded to wait until it receives a signal from the participant's computer. This javascript code is designed to track the exact time it is on the participant's computer and send markers precisely aligned to that time.
94188

95189
1. Synchronisation
96190

@@ -116,4 +210,12 @@ This is 'promise-based', meaning it is coded to wait until it receives a signal
116210

117211
- Send the marker name and this calculated timestamp to the python bridge: `fetch(url)`.
118212

213+
214+
### Usage
215+
216+
1. Open the terminal...
217+
2. Run the expe...
218+
219+
220+
119221
You are now ready to record event triggers during your experiment. For a guide on how to set this up, you can refer to the README.md file of: <https://github.com/OliverACollins/muse-athena-test>.

0 commit comments

Comments
 (0)