Eise.appEasy Image Stacker Engine

Technical Architecture

This page documents the internal processing pipelines and algorithms used by Eise.app. Useful for contributors or anyone curious about how it works.

High-Level Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                            FILE INPUT                                    │
│    SER / AVI / Video / RAW photos (CR2, NEF, ARW, RAF, DNG) / Images    │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                         FORMAT DETECTION                                 │
│                        (FileUploader.vue)                                │
│                                                                          │
│   Routes to appropriate reader based on file type and codec             │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            │                       │                       │
            ▼                       ▼                       ▼
   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
   │  Raw Bayer Path │    │   Video Path    │    │  RGB/MJPEG Path │
   │                 │    │                 │    │                 │
   │ useSerParser    │    │ useFFmpegReader │    │  useAviReader   │
   │ useAviParser    │    │                 │    │  useImageReader │
   │       ↓         │    │    FFmpeg.js    │    │                 │
   │ useDebayerReader│    │       ↓         │    │  Already RGB    │
   │       ↓         │    │   PNG frames    │    │  (no demosaic)  │
   │  GPU Demosaic   │    │       ↓         │    │                 │
   └────────┬────────┘    └────────┬────────┘    └────────┬────────┘
            │                       │                       │
            └───────────────────────┼───────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                         GPU ANALYSIS                                     │
│                   (webgpu_analyze_worker.js)                             │
│                                                                          │
│   1. Crop Detection    - Sample frames, find planet bounds              │
│   2. Per-frame Analyze - Sharpness scoring, per-frame centering         │
│   3. Frame Selection   - Keep best N% by sharpness                      │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                         GPU STACKING                                     │
│              (webgpu_template_match.js + webgpu_stacking.js)             │
│                                                                          │
│   1. Template Matching - Find local shifts at alignment points          │
│   2. De-warping        - Interpolate displacement map, warp frame       │
│   3. Accumulation      - Weighted sum with brightness normalization     │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                       POST-PROCESSING                                    │
│                      (PostProcessor.vue)                                 │
│                                                                          │
│   Wavelet sharpening, RGB alignment, color correction, crop             │
└─────────────────────────────────────────────────────────────────────────┘

File Format Support

FormatReaderNotes
SERuseSerParser → useDebayerReaderRecommended. Raw Bayer with GPU demosaic (VNG or bilinear)
AVI (Y800, DIB 8-bit)useAviParser → useDebayerReaderRaw Bayer AVI from capture software
AVI (MJPEG)useAviReaderAlready RGB, GPU analysis only
AVI (BGR 24-bit)useAviReaderAlready RGB, GPU analysis only
MP4, MOV, WebMuseFFmpegReaderFFmpeg decode → PNG → GPU analysis
PNG, JPG, TIFFuseImageReaderImage sequences
Camera RAW (CR2, CR3, NEF, ARW, ORF, RW2, DNG)useLibRawParser → useDebayerReaderLibRaw (WASM) reads the undebayered sensor mosaic; demosaic runs on the GPU like SER, so the full sensor bit depth is kept
Camera RAW without a Bayer grid (Fuji X-Trans RAF, linear DNG, Foveon)useLibRawRgb → useImageReaderNo 2x2 mosaic to read, so LibRaw demosaics these itself and they join the image pipeline

Analysis Phase

The analysis phase processes frames to determine which ones to keep for stacking.

For each batch of frames:

┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Read from   │     │   Demosaic   │     │  Detect      │     │  Compute     │
│    disk      │ ──▶ │  (if Bayer)  │ ──▶ │   bounds     │ ──▶ │  sharpness   │
│              │     │              │     │              │     │  (Tenengrad) │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
                            │
                     ┌──────┴──────┐
                     │             │
               ┌─────▼─────┐ ┌─────▼─────┐
               │  grayOnly │ │    VNG    │
               │  (fast)   │ │  (quality)│
               │  4 fetch  │ │  33 fetch │
               └───────────┘ └───────────┘
                     │
                     ▼
            Analysis uses grayOnly
            Stacking uses VNG

Demosaicing Methods

MethodTexture FetchesUsed For
grayOnly4 per pixelAnalysis phase (sharpness scoring)
Bilinear~8 per pixelFast preview, low quality stacking
VNG~33 per pixelStacking phase (full quality)

Sharpness Calculation

The GPU computes two complementary sharpness metrics and combines them:

Tenengrad (Sobel gradient magnitude):
┌─────────────────┐     ┌─────────────────┐
│ Gx = [-1  0  1] │     │ Gy = [-1 -2 -1] │
│      [-2  0  2] │     │      [ 0  0  0] │
│      [-1  0  1] │     │      [ 1  2  1] │
└─────────────────┘     └─────────────────┘
Tenengrad = mean(Gx² + Gy²)

Laplacian (second derivative):
┌─────────────────┐
│      [ 0  1  0] │
│      [ 1 -4  1] │
│      [ 0  1  0] │
└─────────────────┘
Laplacian = mean(lap²)

Combined sharpness = √(Tenengrad × Laplacian)

The geometric mean combines edge detection (Tenengrad) with fine detail detection (Laplacian). Higher values indicate sharper frames.

Note: The CPU fallback path uses Tenengrad only (no Laplacian) for simplicity.

Per-Frame Centering

Critical for planetary stacking: the planet drifts across frames due to atmospheric refraction and mount drift. Each frame must be cropped with its own detected center.

Frame 1:        Frame 50:       Frame 100:
┌─────────┐     ┌─────────┐     ┌─────────┐
│         │     │         │     │         │
│   ●     │     │    ●    │     │     ●   │    Planet drifts!
│         │     │         │     │         │
└─────────┘     └─────────┘     └─────────┘

After per-frame centering:
┌─────────┐     ┌─────────┐     ┌─────────┐
│         │     │         │     │         │
│    ●    │     │    ●    │     │    ●    │    Centered!
│         │     │         │     │         │
└─────────┘     └─────────┘     └─────────┘

Stacking Phase

Selected frames are aligned and accumulated using local alignment points.

Alignment Point Grid

┌───────────────────────────────────┐
│  ·     ·     ·     ·     ·     ·  │
│                                   │
│  ·     ·     ·     ·     ·     ·  │    · = Alignment Point (AP)
│           ████████████            │
│  ·     ·  ██ Planet ██  ·     ·   │    Each AP tracks local motion
│           ██        ██            │    using template matching
│  ·     ·  ████████████  ·     ·   │
│                                   │    Patch size: 20-50 pixels
│  ·     ·     ·     ·     ·     ·  │    Search radius: 8-34 pixels
│                                   │
│  ·     ·     ·     ·     ·     ·  │
└───────────────────────────────────┘

Template Matching (NCC)

Normalized Cross-Correlation finds the best match position for each AP:

           Σ[(ref - μref)(frame - μframe)]
NCC = ─────────────────────────────────────────
       sqrt(Σ(ref - μref)²) × sqrt(Σ(frame - μframe)²)

NCC ranges from -1 to +1 (1 = perfect match)

Sub-pixel precision achieved via parabolic interpolation of the 3x3 peak neighborhood.

De-warping

Displacement vectors from APs are interpolated across the frame:

Measured displacements:          Interpolated displacement map:

   ←·     ·→    ·                ←←←↖↖↑↑↗↗→→
                                 ←←←↖↖↑↑↗↗→→
   ←·     ·     ·→               ←←←↖↖↑↑↗↗→→
                                 ←←↖↖↖↑↗↗↗→→
   ·      ·→    ·                ←↖↖↖↖↑↗↗↗↗→
                                 ↖↖↖↖↖↑↗↗↗↗↗

Gaussian-weighted interpolation:
w(d) = exp(-d² / (2σ²))    where d = distance to AP

Accumulation

For each frame f with sharpness S:
    weight = S / max_sharpness
    brightness_scale = reference_brightness / frame_brightness

    accumulator += warped_frame × brightness_scale × weight
    weight_sum += weight

Final = accumulator / weight_sum

Key Files

FilePurpose
composables/useDebayerReader.jsUnified raw Bayer processing (SER, raw AVI)
composables/useFFmpegReader.jsVideo decode via FFmpeg.js
composables/useStacker.jsStacking orchestration, GPU/CPU path selection
public/webgpu_analyze_worker.jsGPU demosaic, sharpness, bounds detection
public/webgpu_template_match.jsGPU template matching for alignment
public/webgpu_stacking.jsGPU frame warping and accumulation
public/gpu/shaders.jsAll WGSL compute shaders

Processing Modes

The mode affects template matching search radius and cut-off frame rejection:

ModeAP Search RadiusCut-off RejectionUse Case
Planet8 pixelsYesJupiter, Saturn, Mars - small motion, reject frames where planet touches edge
Surface34 pixelsNoMoon, Sun closeups - larger drift between frames, no defined edge

AP Search Radius = how far (in pixels) to search around each alignment point when looking for the best template match. Larger radius handles more frame-to-frame motion but is slower.

GPU vs CPU Paths

┌─────────────────────────────────────────────────────────────────────────┐
│                        WebGPU Available?                                 │
└───────────────────────────────────┬─────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │   GPU Path    │               │   CPU Path    │
           │   (primary)   │               │  (fallback)   │
           │               │               │               │
           │ webgpu_*      │               │ unified_      │
           │ workers       │               │ analyze_      │
           │               │               │ worker.js     │
           │ Fast!         │               │ OpenCV-WASM   │
           └───────────────┘               └───────────────┘

Bayer Pattern Reference

OpenCV uses inverted naming from industry standard:

Industry (SER)OpenCVLayout
RGGBBayerBGR G
G B
BGGRBayerRGB G
G R
GRBGBayerGBG R
B G
GBRGBayerGRG B
R G

Want to Contribute?

Check out the GitHub repository. Key documentation files:

  • CLAUDE.md - Architecture overview for AI assistants
  • PROCESSING_PIPELINE.md - Detailed pipeline documentation
  • PIPELINE_OPTIMIZATION_ANALYSIS.md - Performance optimization opportunities