This portfolio showcases work from PSYCH 403A1: Neuroimaging & Neurostimulation. Browse the assignments below to see the complete coursework.
Course Portfolio
Portfolio Overview
Other Course Work
π Assignment 1: EEG Analysis
π Psych 403 Assignment 1 (2) - Hardi Patel.pdf
View Original PDFSTEP 3: Filtered data
# π― STEP 3: Visualize the raw brain wave data!
# This is like looking at the squiggly lines of your brain's "song"
print(" π Plotting the raw brain wave data...")
print(" π± Mobile users: Pinch-to-zoom to see details!")
# Plot the raw data
# We'll plot 10 seconds (duration=10) starting from the beginning (start=0)
# The 'scalings' parameter adjusts the amplitude of the signals.
# 'auto' tries to find a good default, but you can try manual values like 'eeg' or a dictionary like
dict(eeg=50e-6) for uV.
raw.plot(duration=10, start=0, n_channels=len(raw.ch_names), scalings='auto', show=True,
title='Raw EEG Data')
print(" β
Plot generated! You should see a window with squiggly lines.")
print(" π§ What do you notice about the different channels? Are they all the same?")
STEP 4: Non-filtered data
# π― STEP 4: Visualize the filtered brain wave data!
# This is like listening to your brain's "song" after noise cancellation
print(" π Plotting the alpha-filtered brain wave data...")
print(" π± Mobile users: Pinch-to-zoom to see details!")
# Plot the filtered data
# We'll plot the same duration and channels as the raw data
raw_alpha.plot(duration=10, start=0, n_channels=len(raw_alpha.ch_names), scalings='auto',
show=True, title='Alpha-Filtered EEG Data (8-12 Hz)')
print(" β
Plot generated! Compare this to the raw data plot.")
print(" π§ How does the filtered data look different from the raw data?")
STEP 5: Side by side comparison of the filtered and non filtered plots
# π― STEP 5: Plot raw and filtered data side-by-side
# This is like comparing the original noisy radio signal to the noise-cancelled version
print(" π Plotting raw vs. filtered brain wave data side-by-side...")
# Define the time window to plot (in seconds) - using the same as before
start_time = 0
duration = 10
end_time = start_time + duration
# Get the data from the raw and filtered objects for the specified time window
# We'll select a few channels to keep the plot manageable
n_channels_to_plot = 5
channels_to_plot = raw.ch_names[:n_channels_to_plot]
raw_data, times = raw.get_data(picks=channels_to_plot, start=int(start_time * raw.info['sfreq']),
stop=int(end_time * raw.info['sfreq']), return_times=True)
filtered_data, times = raw_filtered_alpha.get_data(picks=channels_to_plot, start=int(start_time *
raw_filtered_alpha.info['sfreq']), stop=int(end_time * raw_filtered_alpha.info['sfreq']),
return_times=True)
# Create a figure with two subplots (one for raw, one for filtered) arranged side-by-side
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6), sharey=True) # Changed to 1 row, 2
columns
# Plot the raw data on the left subplot
for i in range(n_channels_to_plot):
axes[0].plot(times, raw_data[i, :] + i * 50e-6) # Offset channels for visibility
axes[0].set_title('Raw EEG Data')
axes[0].set_xlabel('Time (s)') # Add x-label back to the left plot
axes[0].set_ylabel('EEG (uV)')
axes[0].set_yticks([]) # Hide y-axis ticks for cleaner look
# Plot the filtered data on the right subplot
for i in range(n_channels_to_plot):
axes[1].plot(times, filtered_data[i, :] + i * 50e-6) # Offset channels for visibility
axes[1].set_title(f'Alpha-Filtered EEG Data ({lower_freq}-{upper_freq} Hz)')
axes[1].set_xlabel('Time (s)')
# axes[1].set_ylabel('EEG (uV)') # Y-label is shared, so no need for the second one
axes[1].set_yticks([]) # Hide y-axis ticks for cleaner look
# Adjust layout and display the plot
plt.tight_layout()
plt.show()
print(" β
Side-by-side plot generated! Compare the squiggles!")
print(" π± Mobile users: Pinch-to-zoom on each plot individually!")
STEP 6: Shows the most strongest wave frequencies in lilac
# π― STEP 4: Filter the data for Alpha waves!
# This is like tuning your radio to a specific station (alpha frequencies)
print(" π§ Applying a band-pass filter for Alpha waves (8-12 Hz)...")
# Apply the band-pass filter
# We'll keep frequencies between 8 Hz (l_freq) and 12 Hz (h_freq)
raw_alpha = raw.copy() # Create a copy to keep the original raw data
raw_alpha.filter(l_freq=8, h_freq=12, fir_design='firwin')
print(" β
Filtering complete!")
print(" π Data info after filtering:")
print(f" π΅ Length: {raw_alpha.times[-1]:.1f} seconds")
print(f" π€ Channels: {len(raw_alpha.ch_names)} EEG electrodes")
print(f" β‘ Sampling rate: {raw_alpha.info['sfreq']:.0f} measurements per second")
print(" π§ Now we have data focused on the Alpha rhythm! π§ ")
π¨ Assignment 2: BrainImation
π HardiPPsych 403-Assignment 2 - Google Docs.pdf
View Original PDFConnect 3: Pattern Recognition vs Critical Thinking
With the BrainImation platform, I have built a simple data collection tool influenced by
an interactive game. The concept of this design is a candy crush style game board that can be
played in two ways: by physically moving the tiles via the keyboard/cursor or by answering math
equations. The idea was to track or measure the fluctuation in alpha and beta levels during the
use of pattern recognition versus critical thinking.
In technicality, a colorful grid is manifested as representing βcandyβ and as one gets a
three-in-a-row, the score increases 10 points per square. When solving the math equation, a
correct answer results in an automatic tile movement that creates a 3-in-a-row. Whereas, an
incorrect answer deducts 5 points. Since both skills render an increase in beta brainwaves, the
attempt was to see what form of neural processing elicits the most focus and active
concentration. The code I have is not fully developed and does not work exactly as desired. As
the 3-in-a-row disappears, the blocks should simply fall and spawn new ones above to fill in the
space. However, the tiles sometimes change colors randomly when filling in the space.
While designing this gamified experiment, I faced multiple challenges in the creation.
The major challenge was creating a code (with the help of AI) that produced any visual results at
all. I constantly got blank screens. To fix this, I tried initiating with a simple black square, then
progressed to a colorful one then multiple colorful ones, etc. to finally have a functioning tile
grid. Another challenge was being able to create a successful alpha and beta levels tracker that
responded to show real-time or simulated EEG data alongside the game board. However, using
the same technique of building the idea from bottom up, I was able to make a functioning tracker
separately which then was incorporated into the game board code. The biggest thing I learned
was that progressing from simple to complex is the best way to yield desired results.
Something I would do differently next time is present two contrasting activities in which
one is sensitive to alertness (increased beta/gamma) and the other to a relaxed state (increased
alpha/theta), such as a calming jigsaw puzzle or paint-the-picture. I would also record the neural
data as a line graph to visualize the peaks and troughs of fluctuating brainwaves for a more
effective contrast. Other limitations to resolve are to allow the inputting of β-β for negative math
answers, being able to delete numbers if a wrong one is accidentally pressed, and labelling the
tracker bars appropriately for the corresponding βalphaβ and βbetaβ levels.
Lastly, a point to take note of is that although my muse device paired with
βBrainimation,β a βdevice was not selected.β Thus, the screen recording is based on simulated
data and does not accurately show the change in brain states as one thinks critically or recognizes
patterns.
CODE:
// --- Match-3 game with BrainImation EEG tracker (alpha/beta) & proper vertical
falling ---
var rows = 8;
var cols = 8;
var size = 40;
var grid = [];
var selected = null;
var cursor = { row: 0, col: 0 };
var score = 0;
// Neurofeedback input (0-1), updated externally
var alphaLevel = 0;
var betaLevel = 0;
// Smooth attention display (used for internal smoothing if needed)
var displayedAttention = 0;
// Flags to prevent continuous triggering
var alphaTriggered = false;
var betaTriggered = false;
// Math question
var mathQuestion = {};
var mathAnswerInput = "";
// Falling tiles tracker
var falling = []; // {row, col, fromY, toY, color}
// --- Random helpers ---
function randInt(min, max) { return Math.floor(Math.random()*(max-min+1))+min; }
function randomPiece() { return randInt(1,5); }
// --- Initialize board ---
for (var r=0;r
for(var c=0;c
// --- Draw loop ---
function draw() {
background(255); // white background
// Score display
fill(0);
textSize(24);
textAlign(CENTER);
text("Score: "+score,width/2,30);
// EEG Tracker (exact stable version)
drawEEGTracker();
// Grid offset
var offsetX = (width - cols*size)/2 + 60;
var offsetY = (height - rows*size)/2;
// Draw grid
for(var r=0;r
var fallTile = falling.find(f => f.row === r && f.col === c);
if(fallTile){
drawPiece(fallTile.color, c*size+offsetX, fallTile.fromY);
// Update fall position
fallTile.fromY += (fallTile.toY - fallTile.fromY) * 0.2; // smooth speed
if(abs(fallTile.toY - fallTile.fromY) < 1){
grid[r][c] = fallTile.color;
falling = falling.filter(f => f !== fallTile);
}
} else {
drawPiece(grid[r][c], c*size+offsetX, r*size+offsetY);
}
}
}
// Highlight selected
if(selected!==null){
noFill();
stroke("black");
strokeWeight(3);
rect(selected.col*size+offsetX, selected.row*size+offsetY, size, size);
strokeWeight(1);
noStroke();
}
// Highlight cursor
noFill();
stroke("orange");
strokeWeight(3);
rect(cursor.col*size+offsetX, cursor.row*size+offsetY, size, size);
strokeWeight(1);
noStroke();
// Draw math question at bottom
fill(0);
textSize(20);
textAlign(CENTER);
text("Solve: "+mathQuestion.text+" = "+mathAnswerInput, width/2, height-40);
// --- Neurofeedback effects ---
updateNeurofeedback();
}
// --- Draw a tile ---
function drawPiece(type,x,y){
if(type===1) fill("red");
else if(type===2) fill("yellow");
else if(type===3) fill("blue");
else if(type===4) fill("green");
else if(type===5) fill("purple");
else fill("white");
rect(x,y,size,size);
}
// --- Mouse click selection ---
function mouseClicked(){
var offsetX = (width - cols*size)/2 + 60;
var offsetY = (height - rows*size)/2;
var c = Math.floor((mouseX - offsetX)/size);
var r = Math.floor((mouseY - offsetY)/size);
if(c<0||c>=cols||r<0||r>=rows) return;
if(selected===null) selected={row:r,col:c};
else { trySwap(selected,{row:r,col:c}); selected=null; }
}
// --- Keyboard controls ---
function keyPressed(){
if(keyCode===LEFT_ARROW) cursor.col=max(0,cursor.col-1);
if(keyCode===RIGHT_ARROW) cursor.col=min(cols-1,cursor.col+1);
if(keyCode===UP_ARROW) cursor.row=max(0,cursor.row-1);
if(keyCode===DOWN_ARROW) cursor.row=min(rows-1,cursor.row+1);
if(keyCode===32||keyCode===ENTER){
if(selected===null) selected={row:cursor.row,col:cursor.col};
else { trySwap(selected,{row:cursor.row,col:cursor.col}); selected=null; }
}
}
// Keyboard input for math answer
function keyTyped(){
if("0123456789.".includes(key)) mathAnswerInput += key;
if(keyCode===8 || key==='Backspace') mathAnswerInput = mathAnswerInput.slice(0,-1);
if(keyCode===13 || key==='Enter'){ // submit
checkMathAnswer();
mathAnswerInput="";
generateMathQuestion();
}
}
// --- Swap & match logic ---
function trySwap(a,b){
if(!areAdjacent(a,b)) return;
var temp=grid[a.row][a.col];
grid[a.row][a.col]=grid[b.row][b.col];
grid[b.row][b.col]=temp;
var matches=findMatches();
if(matches.length>0){
while(matches.length > 0){
removeMatches(matches);
applyGravitySmooth(); // vertical only
fillTopRowHoles();
matches=findMatches();
}
} else {
// Swap back if no matches
var t2=grid[a.row][a.col];
grid[a.row][a.col]=grid[b.row][b.col];
grid[b.row][b.col]=t2;
}
} if(grid[r][c]===0){ alphaTriggered = false; function drawEEGTracker() { else fill(50, 80, 90);
function areAdjacent(a,b){ return Math.abs(a.row-b.row)+Math.abs(a.col-b.col)===1; }
function findMatches(){
var matches=[];
for(var r=0;r
matches.push({r:r,c:c+1}); matches.push({r:r,c:c+2}); }
for(var c=0;c
matches.push({r:r+1,c:c}); matches.push({r:r+2,c:c}); }
return matches;
}
function removeMatches(list){
for(var i=0;i
score += 10;
}
}
// --- Smooth vertical gravity & top row fill ---
function applyGravitySmooth(){
for(let c=0;c
if(grid[r][c]===0){
let rr=r-1;
while(rr>=0 && grid[rr][c]===0) rr--;
if(rr>=0){
// Vertical fall only
falling.push({row:r, col:c, fromY: rr*size + (height - rows*size)/2, toY:
r*size + (height - rows*size)/2, color: grid[rr][c]});
grid[r][c]=grid[rr][c];
grid[rr][c]=0;
}
}
}
}
}
// Fill only top-most empty rows
function fillTopRowHoles(){
for(let c=0;c
let fy = (r-1>=0 ? (r-1)*size + (height - rows*size)/2 : (height -
rows*size)/2 - size);
falling.push({row:r, col:c, fromY: fy, toY:r*size + (height - rows*size)/2,
color: randomPiece()});
grid[r][c] = falling[falling.length-1].color;
} else break; // stop at first non-empty tile
}
}
}
// --- Math question / scoring ---
function generateMathQuestion(){
var ops=["+","-","*","/"];
var op=ops[randInt(0,ops.length-1)];
var a=randInt(1,10);
var b=randInt(1,10);
if(op=="/") a=a*b;
var ans;
if(op=="+") ans=a+b;
if(op=="-") ans=a-b;
if(op=="*") ans=a*b;
if(op=="/") ans=a/b;
mathQuestion={text:a+" "+op+" "+b, answer:ans};
}
function checkMathAnswer(){
if(parseFloat(mathAnswerInput)===mathQuestion.answer){
correctMathAnswer();
} else {
score = Math.max(0, score-5);
}
}
// --- Neurofeedback hooks ---
function updateNeurofeedback(){
if(alphaLevel > 0.5 && !alphaTriggered){
autoClearRow();
score += 5;
alphaTriggered = true;
}
if(alphaLevel <= 0.5){
}
if(betaLevel > 0.5 && !betaTriggered){
accelerateFalling();
betaTriggered = true;
}
if(betaLevel <= 0.5){
betaTriggered = false;
}
}
// --- Example tile effect functions ---
function correctMathAnswer(){
var r = randInt(0, rows-1);
var startCol = randInt(0, cols-3);
var value = randInt(1,5);
for(var i=0;i<3;i++){
grid[r][startCol+i] = value;
}
var matches = findMatches();
while(matches.length>0){
removeMatches(matches);
applyGravitySmooth();
fillTopRowHoles();
matches=findMatches();
}
}
// Placeholder functions for neuro effects
function autoClearRow(){
var r = randInt(0, rows-1);
for(var c=0;c
fillTopRowHoles();
}
function accelerateFalling(){
// Optional speed-up logic
}
// --- BrainImation-compatible EEG tracker with alpha/beta bars and outlines ---
colorMode(HSB, 360, 100, 100);
background(255, 255, 255, 0); // transparent layer over grid
// --- Update from EEG (real or simulated) ---
if (typeof eegData !== 'undefined') {
alphaLevel = eegData.alpha || 0;
betaLevel = eegData.beta || 0;
} else {
// Simulate for testing
alphaLevel = (sin(frameCount * 0.02) + 1) / 2;
betaLevel = (cos(frameCount * 0.03) + 1) / 2;
}
let barWidth = 30;
let barHeight = height * 0.6;
let yTop = (height - barHeight) / 2;
// --- Draw alpha bar outline ---
let xAlpha = 50;
noFill();
stroke(200, 80, 90);
strokeWeight(2);
rect(xAlpha, yTop, barWidth, barHeight);
// --- Draw alpha fill ---
let alphaFill = barHeight * alphaLevel;
if (alphaLevel > 0.5) fill(200, 100, 100);
else fill(200, 80, 90);
noStroke();
rect(xAlpha, yTop + (barHeight - alphaFill), barWidth, alphaFill);
// --- Draw beta bar outline ---
let xBeta = 120;
noFill();
stroke(50, 80, 90);
strokeWeight(2);
rect(xBeta, yTop, barWidth, barHeight);
// --- Draw beta fill ---
let betaFill = barHeight * betaLevel;
if (betaLevel > 0.5) fill(50, 100, 100);
noStroke();
rect(xBeta, yTop + (barHeight - betaFill), barWidth, betaFill);
// --- Labels ---
fill(0, 0, 100);
textSize(14);
textAlign(LEFT, CENTER);
text("Alpha", xAlpha, yTop - 20);
text("Beta", xBeta, yTop - 20);
}
// --- Initialize ---
generateMathQuestion();
π₯ HardiPScreen Recording 2025-12-09 at 9.20.03β―PM.mov
π‘ Videos require Google Drive access. Open in new tab if it doesn't load.
π― Midterm Project
Hardi Patel 1753962 Part 1 - Hardi Patel.jpeg
Hardi Patel 1753962 Part 2 - Hardi Patel.jpeg
π patel_midterm_part2 - Hardi Patel.txt
π‘ Code is embedded in this portfolio - opens instantly in the live BrainImation editor (no internet required!)
π patel_midterm_part1 - Hardi Patel.txt
π‘ Code is embedded in this portfolio - opens instantly in the live BrainImation editor (no internet required!)