Claude Code Plugins

Community-maintained marketplace

Feedback

claude-light

@jkitchin/skillz
3
0

Expert assistant for conducting remote experiments with Claude-Light - a web-accessible RGB LED and spectral sensor instrument for statistics, regression, optimization, and design of experiments

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name claude-light
description Expert assistant for conducting remote experiments with Claude-Light - a web-accessible RGB LED and spectral sensor instrument for statistics, regression, optimization, and design of experiments
allowed-tools *

Claude-Light Experimental Skills

You are an expert assistant for designing and conducting experiments with Claude-Light, a remote laboratory instrument that controls RGB LEDs and measures spectral response. Help users perform statistical analysis, regression modeling, optimization, and design of experiments workflows.

What is Claude-Light?

Claude-Light is a Raspberry Pi-based remote experimental instrument that:

  • Controls RGB LEDs (inputs: R, G, B values from 0 to 1)
  • Measures light intensity across 10 spectral channels
  • Provides web interfaces and REST API for remote access
  • Enables hands-on learning of experimental methods and data science

Key Features:

  • No special software required (web browser or Python requests)
  • Automatic logging of all experiments
  • Camera documentation of LED states
  • Multiple sophistication levels (web forms, API, Python scripting)

Installation

No installation needed for basic API usage - just use Python's requests library.

# Basic requirement
pip install requests

# For data analysis
pip install numpy pandas matplotlib scipy scikit-learn

API Access

Endpoint

https://claude-light.cheme.cmu.edu/api

Parameters

  • R: Red channel (0.0 to 1.0)
  • G: Green channel (0.0 to 1.0)
  • B: Blue channel (0.0 to 1.0)

Response Format

Returns JSON with:

  • Input parameters (R, G, B)
  • Spectral measurements at 10 channels:
    • 415nm, 445nm, 480nm, 515nm, 555nm, 590nm, 630nm, 680nm
    • clear (total intensity)
    • nir (near-infrared)

Basic Usage

import requests

# Send experiment
response = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': 0.5, 'G': 0.3, 'B': 0.8})

# Get data
data = response.json()
print(data)

# Access specific wavelength
intensity_515 = data['out']['515nm']

Experimental Workflows

1. Reproducibility and Statistics

Goal: Assess measurement variability and statistical properties

import requests
import numpy as np

# Repeat same measurement multiple times
R, G, B = 0.5, 0.5, 0.5
measurements = []

for i in range(30):
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()
    measurements.append(data['out']['515nm'])

# Calculate statistics
measurements = np.array(measurements)
print(f"Mean: {np.mean(measurements):.2f}")
print(f"Std Dev: {np.std(measurements):.2f}")
print(f"Median: {np.median(measurements):.2f}")
print(f"95% CI: {np.percentile(measurements, [2.5, 97.5])}")

Analysis Tasks:

  • Compute mean, median, standard deviation
  • Plot histograms and assess normality
  • Calculate confidence intervals
  • Determine measurement precision

2. Linear Regression (Single Variable)

Goal: Establish input-output relationship

import requests
import numpy as np
from scipy.stats import linregress

# Vary one input, keep others constant
R_values = np.linspace(0, 1, 11)
outputs = []

for R in R_values:
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': 0, 'B': 0})
    data = resp.json()
    outputs.append(data['out']['630nm'])  # Red wavelength

# Fit linear model
slope, intercept, r_value, p_value, std_err = linregress(R_values, outputs)

print(f"Slope: {slope:.2f}")
print(f"Intercept: {intercept:.2f}")
print(f"R²: {r_value**2:.4f}")
print(f"Std Error: {std_err:.2f}")

# Predict input for target output
target_output = 25000
predicted_R = (target_output - intercept) / slope
print(f"Predicted R for output {target_output}: {predicted_R:.3f}")

Analysis Tasks:

  • Fit linear models
  • Calculate R², RMSE, MAE
  • Quantify parameter uncertainties
  • Validate predictions experimentally

3. Multivariate Regression

Goal: Model multiple inputs affecting multiple outputs

import requests
import numpy as np
from sklearn.linear_model import LinearRegression

# Design of experiments - grid sampling
R_vals = np.linspace(0.1, 0.9, 5)
G_vals = np.linspace(0.1, 0.9, 5)

# Collect data
X = []  # Inputs
y_515 = []  # Output at 515nm
y_630 = []  # Output at 630nm

for R in R_vals:
    for G in G_vals:
        resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                           params={'R': R, 'G': G, 'B': 0})
        data = resp.json()

        X.append([R, G])
        y_515.append(data['out']['515nm'])
        y_630.append(data['out']['630nm'])

X = np.array(X)
y_515 = np.array(y_515)

# Fit model
model = LinearRegression()
model.fit(X, y_515)

print(f"R coefficient: {model.coef_[0]:.2f}")
print(f"G coefficient: {model.coef_[1]:.2f}")
print(f"Intercept: {model.intercept_:.2f}")
print(f"R² score: {model.score(X, y_515):.4f}")

# Predict inputs for target output
target = 30000
# Solve: target = coef[0]*R + coef[1]*G + intercept

Analysis Tasks:

  • Multi-input, multi-output modeling
  • Feature importance analysis
  • Interaction effects
  • Simultaneous constraint satisfaction

4. Optimization

Goal: Find inputs that produce desired outputs

import requests
import numpy as np
from scipy.optimize import minimize

def objective(inputs):
    """Minimize difference from target output."""
    R, G, B = inputs

    # Constrain to valid range
    R = np.clip(R, 0, 1)
    G = np.clip(G, 0, 1)
    B = np.clip(B, 0, 1)

    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()

    # Target specific outputs at different wavelengths
    target_515 = 30000
    target_630 = 20000

    actual_515 = data['out']['515nm']
    actual_630 = data['out']['630nm']

    # Squared error
    error = (actual_515 - target_515)**2 + (actual_630 - target_630)**2
    return error

# Optimize
initial_guess = [0.5, 0.5, 0.5]
result = minimize(objective, initial_guess,
                 bounds=[(0, 1), (0, 1), (0, 1)],
                 method='Nelder-Mead')

print(f"Optimal R, G, B: {result.x}")
print(f"Final error: {result.fun}")

Optimization Methods:

  • Scipy.optimize (Nelder-Mead, Powell, L-BFGS-B)
  • Bayesian optimization (scikit-optimize)
  • Grid search with interpolation
  • Active learning approaches

5. Design of Experiments (DOE)

Goal: Efficient experimental design for maximum information

import requests
import numpy as np
from scipy.stats import qmc

# Latin Hypercube Sampling
sampler = qmc.LatinHypercube(d=3)  # 3 dimensions: R, G, B
n_samples = 20
sample = sampler.random(n=n_samples)

# Scale to [0, 1]
samples = qmc.scale(sample, [0, 0, 0], [1, 1, 1])

# Run experiments
results = []
for R, G, B in samples:
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()
    results.append({
        'R': R, 'G': G, 'B': B,
        'output_515': data['out']['515nm'],
        'output_630': data['out']['630nm']
    })

# Analyze space-filling design
import pandas as pd
df = pd.DataFrame(results)

DOE Strategies:

  • Latin hypercube sampling
  • Factorial designs (full, fractional)
  • Response surface methodology
  • Optimal design criteria (D-optimal, A-optimal)

6. Machine Learning Approaches

Goal: Use advanced ML models for prediction

import requests
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.neural_network import MLPRegressor
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# Collect training data (use previous methods)
X_train = # Input RGB values
y_train = # Output measurements

# Random Forest
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
print(f"RF R² score: {rf.score(X_train, y_train):.4f}")

# Neural Network
mlp = MLPRegressor(hidden_layer_sizes=(50, 30), max_iter=1000)
mlp.fit(X_train, y_train)
print(f"MLP R² score: {mlp.score(X_train, y_train):.4f}")

# Polynomial regression
poly_model = Pipeline([
    ('poly', PolynomialFeatures(degree=2)),
    ('linear', LinearRegression())
])
poly_model.fit(X_train, y_train)

ML Models to Try:

  • Linear models with regularization (Ridge, Lasso)
  • Decision trees and random forests
  • Neural networks (MLPRegressor)
  • Gaussian processes
  • XGBoost, LightGBM
  • Support vector regression

7. Uncertainty Quantification

Goal: Quantify confidence in predictions

import requests
import numpy as np
from scipy import stats

# Bootstrap uncertainty estimation
def bootstrap_regression(X, y, n_bootstrap=1000):
    slopes = []
    intercepts = []

    for _ in range(n_bootstrap):
        # Resample with replacement
        indices = np.random.choice(len(X), size=len(X), replace=True)
        X_boot = X[indices]
        y_boot = y[indices]

        # Fit model
        slope, intercept, _, _, _ = stats.linregress(X_boot, y_boot)
        slopes.append(slope)
        intercepts.append(intercept)

    return np.array(slopes), np.array(intercepts)

# Use bootstrap results for confidence intervals
slopes, intercepts = bootstrap_regression(R_values, outputs)
print(f"Slope: {np.mean(slopes):.2f} ± {np.std(slopes):.2f}")
print(f"95% CI: {np.percentile(slopes, [2.5, 97.5])}")

Uncertainty Methods:

  • Bootstrap resampling
  • Prediction intervals
  • Parameter covariance matrices
  • Cross-validation
  • Monte Carlo simulation

Best Practices

Data Management

# Save experiments to CSV
import pandas as pd

experiments = []
for R in np.linspace(0, 1, 10):
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': 0, 'B': 0})
    data = resp.json()
    experiments.append({
        'R': R, 'G': 0, 'B': 0,
        **data['out']  # Unpack all spectral measurements
    })

df = pd.DataFrame(experiments)
df.to_csv('experiments.csv', index=False)

Background Subtraction

# Measure ambient light
def get_background():
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': 0, 'G': 0, 'B': 0})
    return resp.json()['out']

bg = get_background()

# Subtract from measurements
def corrected_measurement(R, G, B):
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()

    corrected = {}
    for wavelength in data['out']:
        corrected[wavelength] = data['out'][wavelength] - bg[wavelength]

    return corrected

Averaging for Noise Reduction

def averaged_measurement(R, G, B, n_repeats=5):
    """Take multiple measurements and return average."""
    measurements = []

    for _ in range(n_repeats):
        resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                           params={'R': R, 'G': G, 'B': B})
        data = resp.json()
        measurements.append(data['out'])

    # Average all wavelengths
    avg = {}
    for wavelength in measurements[0]:
        values = [m[wavelength] for m in measurements]
        avg[wavelength] = np.mean(values)

    return avg

Caching Results

import pickle
from pathlib import Path

def cached_experiment(R, G, B, cache_dir='experiments_cache'):
    """Cache experiments to avoid redundant API calls."""
    Path(cache_dir).mkdir(exist_ok=True)

    # Create unique filename
    cache_file = Path(cache_dir) / f"R{R:.3f}_G{G:.3f}_B{B:.3f}.pkl"

    if cache_file.exists():
        with open(cache_file, 'rb') as f:
            return pickle.load(f)

    # Perform experiment
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()

    # Cache result
    with open(cache_file, 'wb') as f:
        pickle.dump(data, f)

    return data

Visualization

import matplotlib.pyplot as plt

# Plot spectral response
def plot_spectrum(R, G, B):
    resp = requests.get('https://claude-light.cheme.cmu.edu/api',
                       params={'R': R, 'G': G, 'B': B})
    data = resp.json()

    wavelengths = ['415nm', '445nm', '480nm', '515nm',
                   '555nm', '590nm', '630nm', '680nm']
    intensities = [data['out'][wl] for wl in wavelengths]
    wl_values = [int(wl.replace('nm', '')) for wl in wavelengths]

    plt.figure(figsize=(10, 6))
    plt.plot(wl_values, intensities, 'o-')
    plt.xlabel('Wavelength (nm)')
    plt.ylabel('Intensity')
    plt.title(f'Spectrum for R={R}, G={G}, B={B}')
    plt.grid(True)
    plt.show()

# Plot input-output relationship
def plot_response_curve(channel='R', wavelength='515nm'):
    values = np.linspace(0, 1, 20)
    outputs = []

    for val in values:
        params = {'R': 0, 'G': 0, 'B': 0}
        params[channel] = val

        resp = requests.get('https://claude-light.cheme.cmu.edu/api', params=params)
        data = resp.json()
        outputs.append(data['out'][wavelength])

    plt.figure(figsize=(10, 6))
    plt.plot(values, outputs, 'o-')
    plt.xlabel(f'{channel} Input')
    plt.ylabel(f'Output at {wavelength}')
    plt.title(f'{channel} Response at {wavelength}')
    plt.grid(True)
    plt.show()

Common Experimental Patterns

Pattern 1: Single Variable Sweep

# Systematically vary one input
for value in np.linspace(0, 1, 10):
    data = get_measurement(R=value, G=0, B=0)
    analyze(data)

Pattern 2: Factorial Design

# Test all combinations
for R in [0, 0.5, 1]:
    for G in [0, 0.5, 1]:
        for B in [0, 0.5, 1]:
            data = get_measurement(R, G, B)

Pattern 3: Gradient Descent

# Iteratively approach target
current = [0.5, 0.5, 0.5]
learning_rate = 0.1

for iteration in range(10):
    gradient = estimate_gradient(current)
    current = current - learning_rate * gradient

Error Handling

import requests
from requests.exceptions import Timeout, ConnectionError

def safe_experiment(R, G, B, max_retries=3):
    """Robust experiment with retry logic."""
    for attempt in range(max_retries):
        try:
            resp = requests.get(
                'https://claude-light.cheme.cmu.edu/api',
                params={'R': R, 'G': G, 'B': B},
                timeout=10
            )
            resp.raise_for_status()
            return resp.json()

        except (Timeout, ConnectionError) as e:
            if attempt == max_retries - 1:
                raise
            print(f"Attempt {attempt + 1} failed, retrying...")
            continue

Resources