This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Goxel is a fast, modern, cross-platform DICOM medical image viewer built with Go and the Fyne UI toolkit. It provides both 2D slice viewing and 3D volume rendering with GPU acceleration using OpenGL.
# Build the application
make build-ctl # Outputs binary to bin/goxel
# Run the application
./bin/goxel goxel -p <path> # View DICOM file or directory
./bin/goxel decode -u <path> # Decode and inspect DICOM metadata
./bin/goxel merge -i <dir> -o <output> # Merge slices into multi-frame file
# Testing
go test -short -v ./pkg/... # Run short tests
go test -v ./pkg/... # Run all tests (including integration)
go test -v ./pkg/<package> # Test a specific package
# Code quality
make lint # Run golangci-lint
make vet # Run go vet
make vulnerability # Check for security vulnerabilities
# Dependencies
make update-deps # Update go.mod and vendor directory
# Clean
make clean # Remove build outputs
make nuke # Full reset (git clean)The codebase is organized into three main functional layers:
1. DICOM Layer (pkg/dicom/)
- Purpose: Pure Go DICOM parsing and generation
- Key files:
dicom.go- Core API (ReadFile, Parse, IsCT/IsMR/IsDX helpers)reader.go- Low-level DICOM parsing with transfer syntax detectionwriter.go- DICOM file generationdataset_builder.go- Fluent API for constructing DICOM datasetsct.go,dx.go,mr.go- IOD (Information Object Definition) implementations for CT, DX, and MR modalitiesmodule/- DICOM Information Modules (Patient, Study, Series, Equipment, CT/MR Image, VOI LUT)tag/- DICOM tag definitions and constantstransfer/- Transfer syntax handling (ExplicitVR, ImplicitVR, compressed)vr/- Value Representation types
- Architecture: Low-level parser (reader.go) -> Dataset model -> High-level IOD wrappers (ct.go/mr.go/dx.go) -> Module structs
2. Compression Layer (pkg/compress/)
- Purpose: Pure Go implementations of medical image compression formats
- Codecs:
jpeg2k/- JPEG 2000 (wavelet-based, excellent compression)jpegli/- JPEG Lossless (traditional DPCM-based)jpegls/- JPEG-LS (LOCO-I algorithm, very efficient for medical images)rle/- Run-Length Encoding (PackBits variant)
- Integration: Codecs are automatically selected by
pkg/dicombased on Transfer Syntax UID - Design: All implementations are pure Go with no CGO dependencies for cross-platform compilation
3. Volume Rendering Layer (pkg/volume/)
- Purpose: GPU-accelerated 3D volume rendering and 2D slice extraction
- Key files:
gpu_ray_caster.go- OpenGL-based ray casting for 3D renderingcpu_ray_caster.go- CPU fallback for systems without GPUconfig.go- Rendering configuration (transfer functions, lighting)transfer_function.go- Opacity/color mapping for volume visualizationtypes.go- Core volume data structures
- Architecture: Uses OpenGL 3.3 with GLSL shaders for real-time volume ray casting
4. UI Layer (pkg/goxel/)
- Purpose: Fyne-based GUI for viewing DICOM images
- Key files:
ui.go- Main UI orchestration and layout managementdicom_loader.go- Bridge between DICOM data and UI (ScanCollection, CompositeVolume)scan_adapter.go- Converts DICOM datasets to UI-compatible pixel datavolume.go- 3D volume rendering widgetinteractive_view.go- 2D slice view with annotation supportdicox.go- Custom image widget for windowing/level adjustments*_slider.go- Custom UI controls for windowing, multi-range selectionbundle.go- Embedded resources (icon, etc.)
- Multi-view support: Side-by-side 2D/3D views, single 2D, single 3D layouts
- Data model:
ScanCollection->CompositeVolume(per series) ->PixelData(for 2D) or OpenGL textures (for 3D) - Performance: Slider-driven renders are debounced (50ms) using
debouncerto avoid expensive re-render storms
Loading DICOM files:
goxel.Load()orgoxel.LoadDICOMDir()reads file(s)dicom.Parse()decodes DICOM structure- Compressed pixel data is decoded using appropriate codec from
pkg/compress/ parseDICOMDataset()converts toScanCollectionwithCompositeVolumeper series- UI receives
ScanCollectionand can display 2D slices or 3D volume
Rendering pipeline:
- 2D:
DICOXImagewidget applies window/level to pixel data and renders to Fyne canvas - 3D:
VolumeRendereruploads voxel data to OpenGL 3D texture, ray caster samples through volume with transfer function
Creating DICOM files:
- Use IOD constructors (
dicom.NewCTImage(),dicom.NewMRImage()) - Set metadata via module structs (Patient, Study, Series, Equipment, Image)
- Call
SetPixelData()with raw pixel values - Optionally enable compression (
UseCompression = true,CompressionCodec = "jpeg-ls") Write(path)generates compliant DICOM file
- Dataset: Low-level DICOM data structure (tag-value pairs)
- IOD (Information Object Definition): High-level wrappers for CT/MR/DX images with typed module access
- ScanCollection: Multi-series container grouping related DICOM files (UI layer)
- CompositeVolume: Single 3D volume with metadata (series name, voxel spacing, Hounsfield units)
- Transfer Syntax: Encoding format (uncompressed, JPEG-LS, JPEG 2000, RLE)
// Low-level
elem, err := dataset.Get(tag.PatientName)
name := elem.GetString()
// High-level (via IOD)
ct := &dicom.CTImage{}
ct.CopyMetadataFrom(dataset)
name := ct.Patient.PatientName- Encoder/decoder automatically selected based on Transfer Syntax UID
- To create compressed DICOM: set
UseCompression = trueandCompressionCodec = "jpeg-ls"/"jpeg-li"/"rle"on IOD - To read compressed DICOM:
dicom.Parse()handles decompression transparently
- Single-frame: One image per file (traditional CT/MR series)
- Multi-frame: Multiple slices in one file (supported via
NumberOfFrames) - Loader automatically detects and handles both formats
- DICOM uses patient coordinate system (Image Orientation Patient, Image Position Patient)
- Volume renderer uses voxel indices (i, j, k)
VoxelSize{X,Y,Z}provides physical spacing for aspect ratio correction
- Short tests (
-short) skip slow integration tests - Use
dicom.ReadFile()with testdata samples frompkg/testdata/ - Compression roundtrip tests verify codec correctness
- Volume rendering tests may require GPU (will skip on headless systems)
- UI: Fyne v2.7.1 (cross-platform toolkit)
- 3D Graphics: go-gl/gl (OpenGL 3.3), go-gl/glfw (windowing)
- CLI: spf13/cobra (command structure)
- No CGO required for DICOM parsing/compression (pure Go)
- Idiomatic Go is primary - follow Go conventions first, see Go Proverbs
- Prefer io.Reader/Writer over file paths for APIs
- No CGO unless necessary - pure Go implementations preferred
- Build artifacts go to
bin/, never root directory - Tagged switch > if/else chains
- Avoid init() functions - prefer explicit registration
- Exported over unexported types - avoid
internal/packages
- TDD for design iteration - write tests to explore APIs
- Unit tests document expected usage - they are living examples
- Self-contained tests - no external data dependencies
- Use testify for assertions and test suites
- No tests in cmd/ - command-line tools tested via integration
- Prefer in-memory over disk I/O in tests
- Minimal logging - use slog context, prefer DEBUG level
- Return early - avoid nested error checks
- No labeled breaks - use immediately-invoked functions that return
- Context in errors - log what operation failed, not just "error occurred"
- Avoid goroutines in APIs - use callbacks to let callers control concurrency
- Prefer channels over waitgroups and mutexes
- context.Context for cancellation over custom channels
- Mutexes only for caches accessed from multiple goroutines