This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Run all tests
julia --project -e 'using Pkg; Pkg.test()'
# Run tests with coverage
julia --project -e 'using Pkg; Pkg.test(coverage=true)'
# Run specific test interactively (useful for debugging)
julia --project -e 'using MicroscopeControl, Test; @testset "Simulated Camera" begin cam=SimCamera(); @test initialize(cam)===nothing end'
# Load package in REPL for development
julia --project -e 'using Pkg; Pkg.instantiate(); using MicroscopeControl'
# Install as development package
julia -e 'using Pkg; Pkg.develop(url="https://github.com/LidkeLab/MicroscopeControl.jl.git")'Tests use simulated devices only (SimCamera, SimStage, SimLight) - no hardware required. Test sets: "Simulated Camera", "Simulated Stage", "Simulated Light Source", "Export State".
MicroscopeControl.jl uses a three-layer architecture leveraging Julia's multiple dispatch:
┌─────────────────────────────────────────────────────────────┐
│ MicroscopeControl.jl (main module) │
│ Re-exports all types and functions for user convenience │
├─────────────────────────────────────────────────────────────┤
│ hardware_interfaces/ │ hardware_implementations/ │
│ Abstract types + contracts │ Concrete device drivers │
│ - CameraInterface │ - SimulatedCamera, DCAM4 │
│ - StageInterface │ - SimulatedStage, PI, MCL │
│ - LightSourceInterface │ - SimulatedLight, TCube │
│ - DAQInterface │ - NIDAQcard │
│ - SLMInterface │ - OK_XEM (FPGA) │
└─────────────────────────────────────────────────────────────┘
All hardware inherits from AbstractInstrument (defined in instrument.jl):
initialize(device)- setup hardware connectionshutdown(device)- close hardware connectionexport_state(device)- returns(attributes::Dict, data, children::Dict)for HDF5 serialization
Interfaces define method signatures with @error "not implemented" stubs. Implementations provide concrete methods that dispatch on the device type.
h5_file_saving.jl provides save_h5(filename, device) and save_attributes_and_data(group, device) for saving device state to HDF5 files using the export_state tuple format.
- Create directory under
hardware_implementations/your_device/ - Create
types.jlwith struct inheriting from interface type (e.g.,Camera,Stage,LightSource) - Create
interface_methods.jlimplementing required interface functions - Add module to
HardwareImplementations.jl - Re-export types/functions in main
MicroscopeControl.jl
Camera: capture, live, sequence, abort, getlastframe, getdata, setexposuretime, setroi!
Stage: move, getposition, getrange, stopmotion
LightSource: setpower, light_on, light_off
DAQ: showdevices, showchannels, createtask, setvoltage, readvoltage, deletetask
Each interface has optional gui.jl with GLMakie-based control panels. Access via gui(device).
Hardware implementations use ccall for vendor SDKs:
dcam4_camera/dcamapi*.jl- Hamamatsu DCAM4pi_n472/functions_GCS2.jl- PI GCS2 protocoltcube_laser/tcubeapi.jl- Thorlabs TCubeok_xem/functions_okFP.jl- Opal Kelly FrontPanelmcl_stage/*.jl- Mad City Labs NanoDrive- Serial devices (CrystaLaser, Vortran, Triggerscope) use
LibSerialPort
Convention: Image data is stored and displayed as column-major (H, W, N) arrays where data[row, col] = data[y, x].
At DLL boundary (getdata): C SDKs return row-major buffers. Must permute after reshape:
# WRONG: reshape(buffer, (W, H)) - Julia reads column-first, data is transposed
# CORRECT: permutedims(reshape(buffer, (W, H)), (2, 1)) -> (H, W)Display (Makie image/heatmap): Both image() and heatmap() map dim1→x, dim2→y. For (H, W) data:
# Both work the same way:
image(permutedims(data); axis=(yreversed=true,)) # W→x, H→y, origin top-left
heatmap(permutedims(data); axis=(yreversed=true,)) # W→x, H→y, origin top-leftSaving (HDF5): No transform needed if getdata follows convention. Save (H, W, N) directly.
Some hardware modules are commented out in MicroscopeControl.jl while under development:
Triggerscope- external trigger synchronizationMCLMicroPositioner- Mad City Labs microdrive positioner