Discovering EEG Biomarkers Through Applications of EEG and Machine Learning for Genetic Epilepsies (SYNGAP1)
Advisor: Allendoerfer
Internship Location: Stanford School of Medicine
Onsite Mentor: Christopher Lee-Messer
My name is Dhruv Sharma, and I’m a 12th grader at BASIS Independent Silicon Valley. My project focuses on discovering novel EEG biomarkers in genetic epilepsies through applications of machine learning and EEG analysis. This project is currently being researched in the Dr. Lee-Messer Lab at Stanford University, where I am an intern specifically working on this study.
The research will begin with library and internet research, during which I will analyze and understand scientific papers to build my prior knowledge. These papers include signal-processing techniques, programs for detecting specific EEG biomarkers such as HFOs, and lectures detailing particular cases of epileptic seizures and how to diagnose them from EEG. From there, I will conduct observations and experiments using machine learning processes and tools such as PyTorch, Morgoth, SpikeNet2 (EEG machine-learning models), and PyHFO to identify novel EEG biomarkers in epileptic mice with the SYNGAP1 genetic mutation.
After extracting features, we will compare healthy mice with SYNGAP1 mice to identify specific novel biomarkers, and then apply the drug nortriptyline to the SYNGAP1 mice to determine whether it affects or reduces epileptic seizures relative to initial hypotheses. As of now, I have been working with PyHFO, a computational framework for detecting high-frequency oscillations (HFOs) in EEG signals, examining how these patterns may identify seizure-generating networks and predict epileptogenic treatment (the start of seizure) responses. Alongside collaborators in the Stanford Knowles Lab, I will then also studying how spike-wave discharges progress over time in SYNGAP1+/− mice and whether drugs like nortriptyline can actually restore normal hippocampal function by reducing pathological HFO activity, (this is one of our hypotheses)
In the coming months, the final product of this research is that we expect to complete this EEG biomarker–discovery project and research paper, and I will be presenting our findings at BASIS and, with my research team, at the American Clinical Neurophysiology Society (ACNS) conference, contributing to the broader scientific effort to identify new EEG biomarkers for epileptic encephalopathies. Furthermore, this research will ignite and deepened my interest in computational neuroscience, a field I'm very passionate in pursuing and learning more into.
Project Blog
Week 1: Introduction and Project Background
Hi everyone, welcome back to my senior project blog! Over the coming months, I'll be building a rigorous scientific and technical foundation in EEG analysis, synaptic physiology, and biomarker discovery, working directly with clinical and preclinical datasets in collaboration with the Dr. Lee-Messer Lab. Being part of this lab has deepened my passion for applying computational tools and bioengineering to neurological disorders, a field I'm committed to contributing to in the future, whether through organizations or companies like Neuralink, Google DeepMind, or beyond.
Each post will walk through a distinct phase of my analysis, from background research and computational modeling to results interpretation and final outputs. My overarching goal is to work alongside my lab members to discover novel EEG biomarkers and develop a spike-detection framework for seizure prediction in both mice and humans with SYNGAP1-related epileptic encephalopathy, a genetic disorder caused by loss-of-function mutations in the SYNGAP1 gene that disrupt synaptic signaling, leading to severe epilepsy and cognitive impairment. But before I get started onto my project, I would like to go over some background information and terminology for this project.
First of all, an electroencephalogram, or more as known as EEG, is a non-invasive technique used to record the electrical activity of the brain. The brain is made up of billions of neurons that constantly communicate with each other by firing electrical signals. So when large groups of neurons fire together, they produce coordinated electrical patterns strong enough to be detected from the surface of the scalp through small sensors called electrodes as you may have seen with EEG headsets before. An EEG captures these patterns over time, producing a continuous waveform that reflects the brain's activity in real time. These waveforms are organized into frequency bands, each associated with different brain states and functions. Delta waves (0.5 to 4 Hz) are linked to deep sleep, theta waves (4 to 8 Hz) to memory and drowsiness, alpha waves (8 to 13 Hz) to relaxed wakefulness, and beta and gamma waves to active thinking and sensory processing. In my research, analyzing the power within these bands, as well as how they interact with one another, is one of the core ways we look for differences between healthy and diseased brains in the search of novel EEG biomarkers predictive of SYNGAP1 disease progression. Currently, one of the main course texts for this research project, EEG for Beginners, is also focused on learning how to detect specific types of EEG spikes in recordings, extract and isolate specific frequency components to find meaningful signals, and preprocess and identify seizure-related EEG patterns, which I'm currently understanding as of now.
For example, our lab has already found that SYNGAP1 diseased mice show significantly elevated functional connectivity along the anterior-posterior axis of the brain, most strongly in the theta band. We measured this using a metric called the weighted phase lag index (wPLI), which quantifies the synchronization between two EEG signals to assess how different brain regions are communicating with one another. In the context of epilepsy, EEG is especially powerful because seizures and other abnormal events like spike-wave discharges leave distinct electrical spikes that can be identified visually or computationally. Beyond these classical frequency bands, we are also investigating activity at much higher frequencies, specifically high-frequency oscillations, or HFOs, which range from around 80 Hz up to 500 Hz and above. These oscillations are thought to represent activity originating from epileptogenic zones, the regions of the brain where seizures begin, and may serve as novel and sensitive EEG biomarkers for detecting and tracking epileptic activity. Together, this makes EEG one of the most important tools in both clinical neurology and neuroscience research.
Now, let me explain what SYNGAP1 is, it's a gene that encodes a protein called synaptic protein critical for regulating communication between neurons at the synapse. The brain constantly adjusts how strong or weak individual synaptic connections are through a process called synaptic plasticity, which underlies learning, memory, and healthy cognitive function. So SYNGAP1 acts like this key brake on this process, and helps regulate the insertion and removal of AMPA receptors, which are these primary receptors responsible for fast excitatory transmission at synapses. So when SYNGAP1 is functioning normally, it keeps synaptic strengthening in check, ensuring that the brain maintains a healthy balance between excitation and inhibition, and if SYNGAP1 gene is missing, where only one functional copy of the gene is present rather than two, this regulation breaks down, making AMPA receptors to prematurely flood synapses earlier, and in greater numbers than they should. This makes these synaptic connections abnormally strong from early in development, and over time the brain loses its ability to adjust and strengthen those connections in response to new experiences. This saturation of synaptic strengthening mechanisms is one of the leading hypotheses for why SYNGAP1 mutations drive elevated seizure activity and significant cognitive impairment.
Based on this prior background research and knowledge, I've started developing the framework that I'll apply to my project, which includes establishing a schedule for my lab visits and laying out the analytical skills I will be building over the coming months. The two major components I will be diving deeper into are automated HFO detection and the application of machine learning models to EEG data, both of which I will cover in much greater detail as the project progresses.
As of this week, I have successfully begun learning how to detect and analyze HFOs systematically using PyHFO, an open-source deep learning-based tool designed for automated HFO detection in EEG recordings. This has been a significant milestone, as it means I can now begin applying this tool directly to our lab's dataset. Specifically, I am working to run PyHFO on EEG recordings from SYNGAP1 diseased mice provided by our collaborator Azin from the Knowles Lab, who has been collecting EEG data from these mice across different age points, specifically at P120 and P150, both before and after nortriptyline treatment, a drug that has shown promise in restoring normal synaptic signaling in SYNGAP1 models. Being able to systematically detect HFOs across these time points will allow us to start quantifying how pathological brain activity changes with disease progression and whether treatment has a measurable effect on these signals.
Now, this first week has been about setting the direction my research is heading, and as the project progresses, there will be increasingly more terminology, EEG signal processing techniques, and machine learning models that I'll explain. Future posts will go much deeper into the practical side of things, including walkthroughs of how to use and apply tools like PyHFO and the results we are generating from our datasets. I'm looking forward to keeping you updated as the project develops over the next few months, and feel free to leave a comment below if there is anything you would like me to elaborate on or explain further!
Week 2: Literature Review & PyHFO Deep Dive
Welcome back to the blog, everyone! This week, I'm diving into my literature review and showing how to use PyHFO, and what I encountered while researching and identifying specific SYNGAP1 biomarkers. I also really enjoyed answering all of your thoughtful questions from last week.
This week's literature review centered on two key papers: a 15-page paper on PyHFO 2.0: An Open-Source Platform for Deep Learning–Based Clinical High-Frequency Oscillations Analysis, and a 120-page foundational reference called EEG for Beginners, which covers everything from basic signal interpretation to complex epilepsy analysis.
In the initial paper surrounding the deep learning pipeline and framework of PyHFO, it introduces PyHFO as a tool that integrates classical signal processing with modern deep learning for automated HFO (High-Frequency Oscillation) biomarker analysis. Since PyHFO requires a graphical interface, it must be run through a virtual desktop environment on a server, and the paper walks through how to set this up for shared use. The deep learning models are sourced from the Hugging Face ecosystem, a platform hosting thousands of pre-trained models, and are specifically configured for two classification tasks: spkHFO detection for identifying spike-associated HFOs, and eHFO identification for identifying epileptogenic HFOs. Importantly, this implementation of PyHFO is dedicated to analyzing EEG recordings from mice with epilepsy, which is directly relevant to my SYNGAP1 research.
In the image above, the paper below illustrates the full PyHFO pipeline. EEG data enters the system and passes through a BandPass Filter, after which the user selects an HFO detector, either STE, Hilbert, or MNI. Detected HFO events are then passed to a Feature Extraction step, followed by a Deep Neural Network classifier loaded from Hugging Face that performs HFO Classification into spkHFO and eHFO categories. The final stage is Annotation, where results are stored and reviewed.
From there, above is the graphical interface of pyHFO and how it is possible to run detections and classifications of mice EEG data, and now let me show how I apply it to the mice datasets. In the top left, I can upload an EDF file (the standard file format for EEG recordings) from our SYNGAP mouse directory and then select specific filter parameters. I then can run a Quick Detection pass over the EEG scan to get a baseline read of HFO events in the recording. From there, in the right panel, I can configure specific detection parameters and re-run detection, which will return a precise count of HFOs detected within the EDF file. After detection, I will press HFO Classification, which outputs a statistical breakdown of spkHFOs, eHFOs, and total HFOs across the recording in the bottom right panel. Finally, I can annotate the detected HFOs for more analysis.
Then from there, there is an annotation window provides four synchronized views for each detected HFO event. Panel (a) shows the EEG Tracing, which is the raw unfiltered signal. Panel (b) shows the Filtered Tracing, which is the bandpass-filtered signal highlighting the HFO. Panel (c) shows the Time-Frequency Plot, a spectrogram showing the HFO's energy distribution across time and frequency. Panel (d) shows the FFT Plot, the Fast Fourier Transform showing the dominant frequency components of the event. Together, these views allow me to precisely classify spike morphology and verify whether a detected event is a true spkHFO or eHFO rather than an artifact.
And now, let me dive into the second paper of our initial literature review for the EEG for Beginners. It teaches the essential groundwork for interpreting EEG signals across different brain states. For example, when awake with eyes closed, the dominant rhythm is posterior alpha (8-12 Hz), maximal at electrodes O1/O2, and it immediately attenuates when the eyes open, which is a key marker of a healthy awake brain. Low-voltage beta activity is also visible anteriorly. In the drowsy state, alpha disappears and is replaced by theta activity, and normal drowsy features include vertex sharp waves at Cz and positive occipital sharp transients of sleep (POSTS), both of which are normal and not epileptiform, though beginners often flag them as abnormal. Then in light sleep (Stage 2), two highly specific normal patterns appear: sleep spindles (12-14 Hz bursts lasting 0.5-3 seconds at central electrodes) and K-complexes (high-amplitude slow waves often preceded by a vertex sharp), both of which are pathognomonic for Stage 2 NREM sleep. In deep sleep (Stages 3-4), high-amplitude delta activity comprising at least 20% of the epoch dominates, and the EEG appears chaotic and slow. Across all of these, it shows images of EEG scans of these scenarios that allow to track patterns in order to analyze and interpret EEG signals in these different brain states.
Then, across from that, beyond normal brain states, the paper also covers how to identify seizure patterns from spikes. The image below illustrates four major seizure morphologies: rhythmical spiking, rhythmically evolving theta/delta activity, spike-wave complexes, and electrodecremental patterns (low voltage fast). The accompanying EEG sample to the right also shows an example of how it teaches to analyze many types of spikes in EEG scans as it shows a left superior frontal seizure beginning with rhythmical spiking.
So this week's blog covered everything I learned through my literature review, from understanding and analyzing EEG scans to a deep dive into PyHFO and its future application to our SYNGAP1 mice datasets. For next week, I will be showing a real walkthrough of debugging an issue in pyHFO followed by a complete detection and classification run across all EEG files in the SYNGAP mouse directory. It has been genuinely exciting to learn how to apply and fine-tune PyHFO to extract meaningful biomarker data from these datasets. Alongside for next week, I'll also be sharing how I plan to integrate Claude Code to accelerate my ML model runs and streamline debugging going forward!
Week 3: Debugging the PyHFO Codebase
Welcome back again to my blog everyone! This week I'm diving into an issue that I ran into while trying to run mice EEG datasets through pyHFO, and how I was able to use coding agents such as Claude Code to assist me in debugging the issues and fix the root causes.
As I explained last week, pyHFO is a tool for detecting and classifying these High-Frequency Oscillations (HFOs) in EEG data. HFOs are brief bursts of electrical activity in the brain that researchers use as biomarkers, biological signals that can indicate neurological conditions. So when I tried loading a mice dataset, the tool would find HFOs just fine (23 HFOs in my image of test file below), but then freeze when trying to classify them. With hundreds of mice datasets to process, a tool that freezes halfway through was a major problem that needed to be fixed. The pyHFO classification models were "hanging", essentially meaning the classifier was never completing, so no HFOs were being sorted into their correct categories and were left everything unlabeled.
In the image above, it shows the problem. When we try to open mice file and load a quick detection of a mice dataset, the tool is only able to detect the total number of HFOs, as seen in the bottom right showing 23 HFOs, but unfortunately is unable to classify which ones are artifacts, real HFOs, spkHFOs, or eHFOs. As seen on the left, it also isn't able to display the EEG waves in the window, leaving it blank and frozen with nothing to annotate. And this is just one mice dataset, with hundreds more that need to be classified, showing that this hang needed to be fixed immediately.
So now, lets start off with how I fixed this, first I was able to incorporate the coding agent Claude Code that gave me ideas of where there could be bugs in the codebase that may need to be fix. Using Claude Code to navigate the codebase, I found that when classification started, the program was trying to download its AI models from the internet (a platform called HuggingFace, which hosts machine learning models that I explained in our previous blog about how pyHFO works) before checking if copies were already saved locally on the machine. With no internet access as it is on a server, it would just wait forever for a download that was never coming with no timeout and no error message. The fix was to tell the program to always check for local model files first, and only go online if nothing is found locally.
After figuring that out and fixing that, a another new error appeared saying the program couldn't find a module called "src". Essentially, the problem was that the locally saved model files were created back when the software package was named "src". The package was later renamed to "pyhfo2app", but the saved files still had the old name baked into them. So when the program tried loading in those files, it went looking for "src", and then couldn't find it, and crashed. The fix was to add a small redirect in the code that says "if you're looking for "src", it's actually called "pyhfo2app" now, essentially this alias so the old files could still be read correctly.
Then the next error came: "Please load artifact model first!", even though the models had just been loaded. The real issue was that the program had these three internal variables (model_a, model_s, model_e) that were never given a starting value of "empty" when the program launched. So when the code checked whether a model was loaded by asking "is this variable empty?", it crashed because the variable didn't exist at all yet, like asking for the value of a box that was never put on the shelf. The fix was simply to initialize all three variables to None (meaning "nothing loaded yet") right at startup when running a quick detection for pyHFO. From there, during its GUI testing (Graphical User Interface), classification would reach 0% and freeze again. This time though with a warning about timers being started from the wrong thread. To understand this, the program was trying to practically speed things up by running multiple tasks simultaneously with parallel processing. However, once a graphical interface like the pyHFO window or a GPU has been started up, bringing new parallel processes causes a deadlock, and nothing moves forward. So the fix was to detect when this situation could occur and switch to running tasks one at a time instead of in parallel, sacrificing a little speed to avoid the freeze entirely.
After finding and fixing all four of these errors, everything eventually worked. I loaded the test file, ran detection (23 HFOs found), and classification completed successfully, sorting them into 19 artifacts, 4 real HFOs, 1 spkHFO (an HFO occurring alongside a spike in brain activity), and 3 eHFOs (a specific subtype of HFO). The GUI displayed "Classification finished!" and the EEG waveforms to the left rendered fully and were ready to annotate, as shown in the image below.
So this week’s blog covered the process in how I used Claude Code as this debugging agent to systematically work through a tricky, multi-layered bug in the pyHFO codebase. For next week, I will be diving deeper into now running the detector and classifier, and maybe even analyzing the EEG waveforms across a full batch of SYNGAP1 files, getting closer to meaningful results in biomarker development in this research!
Week 4: Running Detections and Classifiers on the Mouse Directory
Hi everyone, welcome back to my blog! This week, I worked on creating a systematic process for running and analyzing the pyHFO codebase's detectors and classifiers on our wild-type SYNGAP mouse directory. Using the "Markitdown" utility, I was able to build a structured workflow for tracking the types of classifications applied to High-Frequency Oscillations (HFOs).
In creating this process, classification in pyHFO works as this three-stage process. The first stage is the HFO artifact classifier, which determines whether a detected event is a real HFO or an artifact. It outputs 1 for a real HFO, 0 for an artifact, and -1 for events that were skipped because they fell within the ignore region (the first and last N seconds of a recording). Then, the second stage is the spike classifier (spkHFO), which asks whether a given HFO is spike-associated. This stage only runs on events that passed the first stage, that is, events where HFO = 1. It uses the same output scheme with 1 for spike-associated, 0 for not spike-associated, and -1 for skipped. The third stage is the eHFO classifier, which evaluates whether an HFO is epileptogenic, and follows the same logic, it only processes events that were classified as real HFOs in stage one. So if you see a row where HFO = 0 and spkHFO = -1, it means the event was classified as an artifact in the first stage, and the spike classifier was never run on it.
In the image above, the Markitdown utility has converted the .xlsx output from the pyHFO pipeline into a readable table, displayed directly inside our terminal editor Emacs. Markitdown is practically this lightweight Python tool that converts spreadsheets and other file formats into plain text, which made it much easier to quickly scan through classification results without opening each file manually in Excel.
The table shown above in the image is from the Syngap mice recording, where each row represents one detected HFO candidate event. The channels listed on the left, EEG_1_SA-B, EEG_2_SA-B, and EEG_3_SA-B, are these three EEG electrode pairs placed at different sites on the mouse's scalp, all recording simultaneously. Looking at the results, every row shows HFO = 0 and spkHFO = -1, which means the classifier decided that all detected candidates on these channels were artifacts, so the spike classifier never even ran on them.
To process our full SYNGAP dataset across all four age groups, P60, P90, P120, and P150, which refer to the postnatal day the mouse was recorded, we created and ran the following batch command on our server, which was this: "for ff in data/*.edf; do echo python scripts/pyhfo_quick_detect.py $ff --classify --device "cuda:0" --xlsx --output-dir data; done". This loops over every recording file, runs the pyHFO detector and classifier on each one, and saves the results as an .xlsx file. Markitdown then converted those spreadsheets into the table format shown above, letting us review everything right in the terminal.
So this week's blog was able to explain how we created a more technical, systematic process to assess for HFOs in SYNGAP1 as a biomarker on this mouse directory. We have successfully run the detector and classifiers on some of the full P60 mouse datasets, but it seems there is another hang in the pyHFO codebase that still needs to be fixed. So for next week, I will be diving into that and attempting to run the detectors and classifiers across different mouse datasets, and hopefully showing you guys the outputs.
Week 5: Fixing Bugs on the PyHFO Codebase
Hey everyone, welcome back to my blog! This week, I was able to successfully run the HFO detector on the Syngap files and save the output data in the same folder as the EDF files (so that we can load them back in and run classification once we troubleshoot that step), fix a processing issue in the PyHFO codebase that was slowing down the analysis of longer mouse recordings, and continue my literature review by reading a research paper on a nine-day continuous EEG recording study alongside two hours of high-density EEG under chronic sleep restriction conditions in mice.
Starting off, the first main task this week was running the STE (Short-Term Energy) detections manually across all the Syngap files and saving the output data in the same folder where the EDF files live, without including the NPZ files, since those act like duplicates and clutter things up. EDF and NPZ, by the way, are some of the standard file formats used for storing EEG recordings, and keeping all the outputs organized alongside them is important for the next classification step. From there, in the first image below, you can see the set of Syngap post-treatment EDF files at postnatal day 120 (P120) being stored and organized in the codebase. The bottom section of the terminal shows all the post-treatment EDF files that are queued up and going to be used in the detections, and you can also see the cleanup process happening in real time, where we're working to remove those NPZ duplicate files to keep the directory clean and ready to go for detections.
After that, however an huge issue was coming up with the file size. The long recordings, anywhere from four to six hours, were causing the pipeline to hang and fail to process correctly, not letting the detections run. On all the shorter ten-minute files, the detections ran completely smoothly without a hitch, but on the longer segments it would slow way down and eventually stall out. So the solution was clear: we needed to slice those longer EDF files into shorter segments before running the detector on them.
To solve this, we wrote a quick shell script to do exactly that: "for ff in data/*.edf; do python scripts/edf_segment.py --start 0 --duration 600 -o $ff.10min.edf $ff; done". This loops through every EDF file in the data folder, calls a Python segmentation script, and outputs a clean ten-minute (600-second) version of each file, and it worked. The hang was completely gone through making shorter file for a short-term solution of running the longer recordings.
In the second and third images above, you can see the results of those shorter ten-minute detections running smoothly on the Syngap p60 files. The output shows channel-level statistics with total detections, HFO counts, spkHFO, eHFO, and event-level breakdowns across each EEG channel, and as you can see in the results for SynKI9-1(p60), the pipeline was producing clean, consistent HFO detections across all three EEG channels, which is exactly what we were hoping to see as the bug was fixed for running some of the longer EEG recordings of the mice.
After getting some of the detections running, I dove back into my literature review and read through a research paper that's directly relevant to the experimental procedures we're looking to apply for sleep-related activity detection and classification. The paper presents an EEG dataset recorded from nine mice going through a sleep deprivation paradigm, collecting nine days of continuous frontal and parietal EEG alongside accelerometer data, plus two hours of high-density EEG under both sleep-deprived and normal conditions. The researchers ran 18-hour sleep deprivation sessions across five consecutive days, and stored everything in a standardized BIDS format (Brain Imaging Data Structure) files like EDFs. This research also was quite important in helping me understand how to apply specific data analyses designed to help track oscillatory activity during sleep, things like slow waves and spindles, across different cortical regions and levels of sleep pressure, and they even included Python code for detecting those exact features. Across this literature review, I also learned about a three-channel EEG/EMG tethered mouse system that allows researchers to record three channels of EEG or EMG data simultaneously, which I realized directly connects to what we've been seeing in the PyHFO program outputs, where our recordings also come in across three EEG channels.
So this week's blog covered how I was able to slice up some long EDF EEG segments using a short but effective shell script to get around a tricky processing bug, run the detectors manually, clean results at the bandpass filtering frequency, successfully save the detection outputs right alongside their source files in an organized way, and my main takeaways from my literature review. Then for next week, I'll be diving into building a full EDF segmentation script that can handle creating many shorter and longer segments across a whole list of EDF files, and integrating Hydra for cleaner machine learning configuration management across our codebase. We're getting much closer to having the full detection and classification workflow running end to end, and getting closer to finding significant patterns and potential biomarkers across the Syngap mice!
Week 6: Creating Scripts to Run Detections on Syngap Mice
Hi everyone, welcome back to the blog once again! For this week, I was able to create a long, full EEG segmentation script on our server, and I also set up storage for different types of EEG segment files, ranging from 10-minute chunks all the way up to 4-hour chunks, that will be run through our detection pipeline. On top of that, I also integrated and learned about applying Hydra, a machine learning framework that helps to simplify the configuration of complex models and tasks in our PyHFO codebase.
So last week, we were able to create a short-term solution for running the longer EEG Syngap data by slicing them down into shorter, quicker files, such as 10 and 20 minute Syngap EEG data. Sometimes these files would run smoothly and quickly, but they would be too short for finding useful results, as a 10-minute window doesn't always capture enough HFO events to say anything meaningful about a given mouse. So the main goal this week was to also find right-sized slices of these long files, such as 4-hour-long data, so they are able to run still at a smooth pace, but we can still iterate through them and go into depth in the data to understand it and find valuable data in them.
From this, in our PyHFO server for running quick detection and batch runs, we created an EDF file segmenter script. The way it works is pretty straightforward: you give it an input file, an output file, and when to start along with its duration, and from there, we can go through our list of EDF files and create shorter files that properly segment all of these EDF files into different lengths, such as 4-hour EDF or 2-hour EDF files, which can now give us significant results if we then try to start running them. In the image below, it shows a part of the edf_segment.py script that we created in the pyHFO codebase. It uses "argparse" to take command-line arguments and the "edfio" library to read and write EDFs. The --duration and --end flags are set up as a mutually exclusive group, so the user passes one or the other, and the script clamps the end time against the file's total duration so we never ask for more signal than actually exists, segmenting the files.
Also, when we were trying to segment the EDFs, we realized that the EDF data is stored in many directories under sub-directories, so when also doing this, we listed out all of the source EDFs. As you see in the image below, we wrote a small piece of code to list out all the EDFs, which we then put into the HFO codebase and into the output data, creating new directories initially called data_10min and data_4hrs for EDFs, and then eventually put those data in there and running them.
The whole script in the image below is basically one line, "pathlib.Path().rglob('*.edf')", which recursively walks the current directory and yields every .edf file it finds. But the output is what's really useful: you can see a long list of PosixPath entries showing EDFs scattered across multiple subdirectories in the bottom section of the image. Each path follows a pattern like "SynKI8,9/P60/edfs/SynKI9-6(p60)_2024-06-17_18_10_29.edf". The top-level folder groups by experimental cohort (SynKI8,9), then by time-point (P60, P150-PostTreatment, and P90 folders), and inside each of those is an "edfs/" folder holding the actual recordings. The filenames themselves encode the mouse ID (SynKI9-6), the age at recording (p60, p150, p90), and a full timestamp. This structure matters a lot, because without "rglob" we would have had to either hardcode paths or walk the tree manually, and with mice spread across three time-points and two genotypes (Syngap and wildtype), that would have gotten messy fast. So having the full list in one place made it easy to batch-feed every EDF into the segmentation script.
Across doing this, I also learned the importance and application of Hydra, a very handy framework system for running experiments across different configurations, for example, running a specific classifier through a set of models with a specific type of bandpass frequency, and Hydra keeps track of that for you. This application is very important because in the future it allows us to test the different types of segmentation of our Syngap data, and keep track of that with 2-hour segments and then even 8-hour segments, and then trying out different frequencies and tracking files across different configurations, which is quite useful. So, instead of editing the script by hand every time we want to change a parameter, we can just use the Hydra framework and it handles the configuration, each run gets its own output folder with the exact configuration it used, which makes it much easier to compare results across experiments without losing track of what was what.
So this week's blog explained how I was able to create long, functional EEG segmentation scripts that can be used to now find significant results in specific types of EEG recordings of different timings. So for next week, after running the data with the 4-hour or other timed data, the plan is to figure out which ones are Syngap and which are wildtype, see a comparison in the number of detections or in its classification, and look for differences in postnatal treatment, specifically, how the detection profile should change after treatment, and it should get better as postnatal date increases. Next week I'm hoping to finally get some real outputs and results from the pyHFO codebase and ML models, potential biomarkers for the mice!
Week 7: Attaining Some Patterns and Results
Hi everyone, welcome back to my blog! For this week, we finished running manual detections on all Syngap KI7 mice and put together some preliminary plots for each mouse in the lab, comparing the number of detections across post-natal conditions to look for trends. We will later compare these manual detections against the 4-hour data segments once those are analyzed (I'll walk through all of this below).
So last week, we created the EDF segmentation script and set up the process for running the 4-hour EDF chunks. (In case I haven't explained it before: EDF stands for European Data Format, a widely used file format for storing EEG, EMG, and other physiological recordings, made up of a text header plus its binary signal data.) The main goal of this week was to store all of our new manual detections into our HFO_biomarker_proj folder, since we were also able to finish running the detection across all Syngap KI7 mice and gather some results from the analysis of HFO detections. Alongside this, these detections are scanning and running analysis on 11 mice which are both female and male mice, and wild-type and heterozygous with the Syngap mutation. So in running all the detections manually, we were able to create some plots as shown below.
In the image above, there's a plot from one of the 11 mice with the Syngap mutation. In the legend, EEG_1_SA-B and EEG_2_SA-B scan for HFOs in the somatosensory areas of the brain, EEG_3_SA-B scans for HFOs in the frontal area, and the EMG channel scans for HFOs in the neck muscles. The x-axis shows the four time points of the mice's post-natal treatment dates (P60, P90, P120, P150), and the y-axis is the total number of HFO detections per condition. Across all the detections, the plots indicate that HFO detections fluctuate quite a lot. In the detections, P150-postTRT (post-treatment) clearly has the highest number of detections across all channels in this mouse as we see in the image above, but the trend is not a clean upward progression as several channels actually dip at P90 and P120 before spiking at P150. That's something we're still trying to figure out.
This is a second mouse from the same group, and the pattern looks pretty different. The somatosensory channels (EEG_1_SA-B and EEG_2_SA-B) climb steadily from P60 to P150, while the frontal channel (EEG_3_SA-B) stays almost flat the entire time, and the EMG line bounces up and down. We've created plots like these for all of the mice so we can compare across animals and pull out patterns in the HFO detections between heterozygous and wild-type mice.
From these initial detections, the next step is processing all of the data into 4-hour chunks using the EDF segmentation script we built last week, so we have proper, significant data with real HFO events to compare these manual detections to. After sourcing all the EDF files across multiple cohorts and ages (P60, P90, P120, P150), I'll be building a pipeline in the codebase so the resulting numbers are comparable across animals and conditions. As I mentioned earlier, running the heavy detector on the full 24 hours per animal would take days of GPU time, so segmenting the EDFs down to 4-hour windows gives us valuable, manageable data while still capturing meaningful events.
This week's blog dove into starting to attain initial significant patterns and results that we can now deeply analyze and compare against our future heavy-detection 4-hour data to answer questions like: do Syngap KI mice show higher pathological HFO rates than wild-types, does HFO rate change with age (P60 to P150), and is HFO rate an early biomarker that shows up before overt seizures in Syngap mice?
So for next week, I'll be running all 150 of the 4-hour EDF data segments of all the mice through the EDF segmentation script and pyHFO detector we built, then summarizing each run's HFO detections through the Markdown utility, which will take quite some time given the GPU load per chunk. From there, I'll be writing a Python script to pull those classifications into one place and compare HFO detection rates across the four post-natal treatment time points (P60, P90, P120, P150) and between Syngap heterozygous and wild-type mice, channel by channel. Next week I'm hoping that we are getting closer to identifying HFO patterns that could serve as early biomarkers in Syngap mice!
Week 8: Segmenting the EDF Data for Results
Hi everyone, welcome back to my blog! Thank you everyone for all of the curious questions you've asked me, they're really great and interesting questions that I'm looking forward to answering and discussing. This week was my heaviest workload yet, and I'm excited to share that we finally have significant results: I segmented the 24-hour data into 4-hour EDF data segments that can now be run in the pyHFO detection codebase, and I explored the classifications of each of the 150 4-hour EDF data segments into their specific Syngap1 mouse groups (heterozygous or wild-type with their treatments).
Over the past few weeks, after trying to run some manual detections on the original data which was talked about in last week's blog, I created a script that I'm now using to handle this automatically. I split up the data into categories and used the EDF segmentation script we built last week to run all 150 of the 4-hour data chunks, summarizing each one's HFO detections through the Markdown utility (which converts the .xlsx outputs into readable Markdown tables). The full run was planned to take over 12+ hours, and those outputs are now being analyzed for significant patterns, which I'll dive into below.
To start off, I built a pipeline in the codebase so the resulting numbers are comparable across animals and conditions. From there, first, I applied the pyHFO segmentation script we built earlier, a single-purpose tool in which it opens an EDF, slices out a time window of 14400 seconds (4 hours), and then writes a new 4-hour EDF file. Then I set up a loop that reads each line of all the EDFs, creates the root between the files and pyHFO codebase, and applies the 4 hour segmentation script for every file. This roughly ran through all the EDFs and produced 150 segmented 4-hour EDFs in data_4hrs/ folder with 0 failures, taking up about 26 GB.
From there, across all of the 150 segmented 4-hour EDFs, we are now running detection with the pyHFO detection tool. For each EDF, it does three things: it filters the signal to focus on the HFO band frequency range (between 80 Hz and 500 Hz), it picks out possible specific types of HFO events using the STE algorithm (its algorithms which find short bursts of strong signal energy for spikes, the default detector pyHFO ships with), and then it sorts each candidate event into one of three categories using trained AI models on the GPU (artifact / spkHFO / eHFO), which we can then go into on the pyHFO application to accept whether that EEG signal is correctly represented as one of those categories. So through the Markdown utility, the pipeline would then also write two output files per 4-hour input: a *.4hr.npz file, which holds the raw event data (start and end times + the AI's confidence scores), and a *.4hr.xlsx file, which is a readable two-tab spreadsheet for analysis where the Channels tab has per-channel summary counts and the Events tab has one row per detected event.
Before touching all 150 files, I initially ran into quite a number of issues surrounding the pyHFO detector needing specific editions of torch 2.8.0+cu128 (an ML framework) with open GPU compute so that it could run. After fixing those issues, I was finally able to start running the EDF files, but first I ran an initial test on one file (SynKI9-6(p60)_..._4hr.edf) end-to-end. It took 3 min 9 s, found 276 events, and wrote .xlsx and .npz cleanly. That gave us the per-file timing we needed to plan: 3 min × 150 ≈ 7.5 hours of GPU time. As I mentioned in earlier weeks' blog posts about the use of the Markdown utility, the .xlsx file-type would now have this feature applied to the outputs of the detector. From there, I now created the batch setup, in which two parallel background loops were running: loop 1 detects all of the HFO events in the 4-hour segments, and loop 2 converts all of the data into the Markdown format, which scans and analyzes for different types of HFO events and their results.
In the image above, it shows the finish of all 4-hour segments being run and detections being run through for all the files, and then we see the Markdown utility being applied to one of the many files that summarizes in tables the amount of different types of HFO detections and events across that specific 4-hour file. In that image, it shows the outputs of this 4-hour P60 EDF file, in which EEG_1_SA-B channel had 364 total detections (19 HFO, 6 spkHFO, 0 eHFO), EEG_2_SA-B channel had 242, EEG_3_SA-B channel had 336, and EMG had the highest at 427 total (414 HFO, 30 spkHFO).
Now that all of the 4-hour EDF files have been created with data and results for the total number of types of HFO detections, we are creating a script that will summarize our data into graphs and plots to compare all types of detections by age in specific channels such as EEG_2_SA-B, and the results should be promising if they show a progressive increase in all various types of HFO across the post-natal date increase, and then when treatment is applied.
So for next week, I will finally have some amazing plots to show, and we'll compare across all the types of mice and figure out any issues that come up around detecting specific types of HFO like epileptic HFOs. We'll see whether the number of HFO detections increases across the post-natal dates with all of the mice, including all types of genotypes such as wild-type with the treatment or heterozygous without the treatment. Next week, I look forward to showing and explaining the process of generating these plots and explaining their significance as biomarkers for Syngap1 mice!
Week 9: Creating Analysis Plots on the Syngap1 Mice
Hi everyone, welcome back to my blog once again! This week we were able to create lots of analysis plots on the Syngap1 and wild-type mice, building on the 4-hour data we segmented last week. These analysis plots include the progression of HFO (high-frequency oscillation) detections as postnatal age increases, a comparison of HFO detections between nortriptyline-treated wild-type mice versus nortriptyline-treated heterozygous (Syngap1) mice, and the progression of HFO detections across the different EEG channels. These plots were created through two new scripts called explore_classifications.py and explore_wt_het.py. I was also able to put together a CSV file that organizes the metadata of all the mice examined in the project.
After creating and running the 4-hour data we segmented last week without any errors, we wanted to truly summarize and examine the data for any significant patterns across the different types of HFO detections in all our mice. So initially, we created analysis plots of HFO detections by channel, starting with the EEG_1_SA-B channel and continuing on through the other three, and these channels specifically examined HFO detections in the heterozygous (Syngap1-affected) mice.
The image above displays one of many analysis plots for the different types of HFO detections across channels in the heterozygous mice. It breaks down four detection categories at each post-natal age bin (p60, p90, p120, and p150). The first panel shows total_detections, which represents every detection the pipeline picked up. The second shows pure HFOs, meaning ripples without spikes. The third shows spkHFO, which are spike-associated HFOs where an HFO co-occurs with an epileptic spike. The fourth shows eHFO, the epileptic HFOs. In this image, the plot shows a great progression of HFOs increasing as postnatal age increases, which is a good sign, since total detections, HFOs, and spkHFOs all roughly double from p60/p90 to p120/p150, with several high-end outliers appearing at p150. However, the eHFO panel is completely flat at zero across every age group, which immediately flagged a problem in the detection pipeline that I will return to in the next week section.
In one of my recent calls with my lab, I came to understand more about the impact of nortriptyline and its correlation to HFOs. Nortriptyline is originally an antidepressant, also commonly used to treat neurological chronic pain associated with conditions like Syngap1. But it doesn't have a direct, one-way correlation to HFOs or epileptiform activity, since it can hypothetically push them up or down depending on context. Clinically, however, the broader pattern is that patients tend to have fewer seizures and progressively aren't getting worse. Aside from this note, the general upward progression of all HFO types in the plots shows that epileptiform activity is occurring, and the drug is one of the tools we're testing to potentially manage it.
On top of those types of analysis plots, we also created plots between heterozygous and wild-type mice to see whether there are variations or patterns among the HFO detections as postnatal age progresses.
In this image above, here is one of the 50+ analysis plots we created to find significant variations and important data we could extract. However as we see in the image, in the analysis of HFO detections across different treatments in both heterozygous and wild-type mice, the HFO detections fluctuate quite a lot, not being able to give us a significant track or result to lead us now on the effects of the treatment or to test out the biomarkers. Some of this noise is likely due to small group sizes, since several post-natal dates have only n=2 or n=3, and part of it could be due to these models being trained on human data instead of mouse data, which may lead to variations. That said, a few patterns do stand out when zooming out, since Het-Nortrip at p150 with n=12 shows a wide spread with a fairly high median, suggesting nortriptyline-treated heterozygous mice still carry substantial epileptiform activity at older ages, while Wt-Veh (control with water) and Wt-Nortrip stay somewhat lower on average, though the small sample sizes make any firm conclusion premature.
With the new scripts explore_classifications.py and explore_wt_het.py that helped to create these plots, the scripts also produce classification summaries we can examine to improve the visualization of the EEG, and then from there, in the call, I was able to organize all of the metadata for the 54 mice in the project and add it into a CSV file that can be queried going forward. From there, we finally have some early results that show nortriptyline as having some effect on HFO detections in mice with the Syngap1 mutation, with specific HFO subtypes looking promising as biomarkers.
So for next week, as we saw in one of the images above, the epileptic HFOs (eHFOs) weren't showing up as we ran the code and they are very important as a biomarker for epileptiform activity in all of the mice variants. So in next week's blog, I will dive into how I fixed that bug so eHFOs would now be properly detected. Then, to find more patterns and biomarkers across these different treatments of mice, I will be segmenting and processing the 12 to 18 hour mark of the 24-hour EEG EDF recording files, so that we can analyze and detect the amount of HFO detections across the mice with much more detail and find proper, significant results and biomarkers! With deeper data and the eHFO pipeline fixed, we should finally examine the mice and impact of nortriptyline as a treatment even more for Syngap1.
Week 10: Debugging eHFO Bug and Processing Longer EDF Files
Hi everyone, welcome back again to one of my final blogs! This week we were able to successfully debug and fix the epileptic HFOs bug that I discussed about in last week's blog. From there, I re-ran the 4-hour segmented EDF files (which took 8+ hours again), and now the epileptic HFOs should finally show up when we create the 4-hour data analysis plots from the mouse EEG recordings. After completing that, we kicked off the new batch of segmented EDF files from the 24-hour EEG recordings, with now we're currently running detections and classifications on the 12 to 18 hour segment of that 24-hour EEG recording.
In last week's blog, we saw that the eHFO detections weren't showing up in the analysis plots, and we had to fix this immediately since eHFO and spkHFO are two of the most important biomarkers, they signify when an HFO occurs in the epileptogenic zone. While fixing this, I actually realized the issue wasn't in the creation of the analysis plot itself, but in running the detections and classifications across all 150 of our 4-hour EDF files. Every recording's eHFO column was 0 across all 150 of our 4-hour xlsx files when I went through them, like in the sample of a previous dataset shown below:
So I traced through the code and found that the eHFO model file exists and gets loaded, it initializes the classifier, and when pyHFO script: quick_detect.py --classify --device cuda:0 runs, the eHFO model is loaded onto the GPU. The eHFO inference method exists too, but then I found the actual error: the script pyhfo_quick_detect.py never actually calls it. Essentially, the eHFO classifier was ready and loaded, but the third method, classify_ehfos(), was always missing from the chain. So pyHFO was paying the cost of loading the eHFO model on every run, but never actually running it. The eHFO column in the output xlsx then just got a default zero value because the classifier never wrote any predictions.
The fix was to actually classify the eHFOs with adding lines added to script pyhfo_quick_detect.py, right after the existing classify_spikes() block: "print(" Classifying eHFOs ..."), backend.classify_ehfos()". So after debugging this, I ran a smoke test of one of the 4-hour detections and classifier, and compared to its previous 4-hour detection, the eHFO column went from 0/0 across two mice EEG channels to now 3 and 150 out of 124 and 152 HFO events respectively as seen in the image below, which exactly what we'd expect once the classifier was actually firing.
Once the smoke test confirmed the eHFO classification was working, I had to re-run all of the 4-hour EDF recordings once again so we could finally see eHFO data across every EEG mouse recording in the 4-hour segments. The batch detections and classifications took around 8+ hours again, I walked through the process of running batch detections in one of my past blogs, so feel free to check that out too! After the batch detections and classifications were finished, the next step was creating all of the analysis plots of HFO detections from the segmented EEG data to find significant and important patterns. As of now, we're still in the middle of creating those plots, so I'll likely cover some of them in next week's blog.
From there, we wanted to continue finding much more significant patterns and biomarkers from the 24-hour EEG recordings across all the mice at their different phases. Initially we only ran the first 4 hours of each 24-hour recording, but now we want to see what patterns show up between the 12 hour and 18 hour mark. That means segmenting 150 6-hour EDF files, so now the detections and classifications on those have been running for a while now.
Since this is one of the final weeks, I'll likely update the next blog with the analysis plots and any significant results from the 6-hour EDF files. It's been a great time getting to explain parts of my research project, and I've really enjoyed answering interesting questions and writing fun, fascinating blogs along the way! Across the summer, I'll be heavily continuing this project, finding and analyzing true, significant biomarkers and the effects of nortriptyline, which we'll be able to explain more deeply in our future research.
Week 11: Closing Thoughts
Hi everyone, welcome back everyone to my final blog! This past weekend, I had the incredible opportunity to present and discuss my senior project at the Senior Project Presentation Showcase, walking through everything I've learned, debugged, and researched over the past few months. It was such a pleasure to share the work I've poured so much time into, talk through my initial research findings and future plans for this biomarker discovery project, and answer some really thoughtful questions from everyone who stopped by.
Looking ahead, I'm excited to keep pushing this research forward. Over the next few months, I'll be diving deeper into EEG, learning more about signal processing, running additional deep learning models on EEG data, and getting better results with segmenting longer recordings. Right now, we're continuing to generate analysis plots, diving deeper into specific HFO detections, and segmenting longer mouse EEG files to surface more potential biomarkers. From there, the plan is to branch into other biomarkers like spike-wave discharges and continue to apply machine learning tools to help identify significant biomarkers for SYNGAP1 mice.
Before I sign off, I want to say a huge thank you to Dr. Lee-Messer for giving me this opportunity, guiding me throughout the project, and deepening my passion for computational neuroscience. I'd also like to give an enormous thank you to Dr. Allendoerfer, who met with me every single week, asked the kinds of questions that always sparked my curiosity, followed along with every blog post, and pushed me to think harder about my work. Beyond this project, she's been an amazing teacher over the years, and I've learned so much from her.
Writing these blogs has been a genuine pleasure, and I'm hopeful that I'll get the chance to present this research at conferences in the future!
Signing out,
Dhruv