Skip to content

Latest commit

 

History

History
402 lines (356 loc) · 20.8 KB

File metadata and controls

402 lines (356 loc) · 20.8 KB

VENUS CCD/CMOS Data Reduction Workflow

Beamline: VENUS (SNS) Detector: CCD/CMOS camera Beam Type: Pulsed (no TOF for CCD/CMOS - integrating detector) Applications: nR (radiography), nCT (computed tomography)


Pipeline Flowchart

flowchart TD
    subgraph Input["1. Data Loading"]
        A1[TIFF/FITS] --> A[Load Sample]
        A2[TIFF/FITS] --> B[Load Open Beam]
        A3[TIFF/FITS] --> C[Load Dark Current]
        A4[DAQ] --> M[Load p_charge]
    end

    subgraph RunCombine["2. Run Combining"]
        RC1{Multiple Runs?}
        RC2[Aggregate Data + Average p_charge]
        RC3[Single Run]
    end

    subgraph ROI["3. ROI Clipping"]
        D{ROI Specified?}
        E[Apply ROI]
        F[Full Frame]
    end

    subgraph Prepare["4. Reference Preparation"]
        G[Average Dark → 2D]
        H[Average OB → 2D]
    end

    subgraph PixelDetect["5. Dead Pixel Detection"]
        PD[Identify Zero-Count Pixels]
        PM[Dead Pixel Mask]
    end

    subgraph Gamma["6. Gamma Filtering"]
        GF{Gamma Filter?}
        GY[Apply Filter]
        GN[Skip]
    end

    subgraph DarkCorr["7. Dark Correction"]
        DC1["Sample_corr = Sample - Dark"]
        DC2["OB_corr = OB - Dark"]
    end

    subgraph BeamCorr["8. p_charge Correction"]
        BC["f = p_charge_OB / p_charge_Sample"]
    end

    subgraph Norm["9. Normalization"]
        N["T = (Sample_corr / OB_corr) × f"]
    end

    subgraph AirCorr["10. Air Region Correction (Optional)"]
        AC1{Air ROI?}
        AC2["T_final = T / mean(T_air)"]
        AC3[Skip]
    end

    subgraph UQ["11. Experiment Error"]
        UQ1[Poisson + p_charge σ]
        UQ2[Error Propagation]
    end

    subgraph Output["12. Output"]
        O1[Transmission 3D]
        O2[Uncertainty 3D]
        O3[Dead Pixel Mask]
        O4[Metadata]
    end

    Input --> RC1
    RC1 -->|Yes| RC2
    RC1 -->|No| RC3
    RC2 --> D
    RC3 --> D
    D -->|Yes| E
    D -->|No| F
    E --> Prepare
    F --> Prepare
    G --> PD
    H --> PD
    PD --> PM
    PM --> GF
    GF -->|Yes| GY
    GF -->|No| GN
    GY --> DarkCorr
    GN --> DarkCorr
    DC1 --> BC
    DC2 --> BC
    BC --> N
    N --> AC1
    AC1 -->|Yes| AC2
    AC1 -->|No| AC3
    AC2 --> UQ1
    AC3 --> UQ1
    UQ1 --> UQ2
    UQ2 --> O1
    UQ2 --> O2
    PM --> O3
    O1 --> O4
    O2 --> O4
    O3 --> O4

    style Input fill:#e1f5ff
    style RunCombine fill:#f5e1ff
    style ROI fill:#fff4e1
    style Prepare fill:#e1ffe8
    style PixelDetect fill:#ffe1e1
    style Gamma fill:#fff4e1
    style DarkCorr fill:#e1ffe1
    style BeamCorr fill:#e1f5ff
    style Norm fill:#e1ffe1
    style AirCorr fill:#fff4e1
    style UQ fill:#ffe1cc
    style Output fill:#f5e1ff
Loading

1. Inputs

Input Format Required Description
Sample images TIFF/FITS stack Yes Raw neutron transmission images
Open Beam (OB) TIFF/FITS stack Yes Reference without sample
Dark Current TIFF/FITS stack No Electronic noise baseline (beam off). Optional — omit dark_paths (or pass []) to skip dark correction.
ROI (x0, y0, x1, y1) No Region of interest to crop
Reference ROI (x0, y0, x1, y1) No ROI for beam stability correction

Metadata (from files or DAQ):

  • Acquisition time per image
  • p_charge (proton charge - beam intensity proxy)
  • Detector gain settings

Key Differences from MARS CCD/CMOS:

  • Beam stability correction required (pulsed source fluctuates)
  • Run combining more critical (lower integrated flux)
  • p_charge metadata available for beam correction

2. Processing Pipeline

┌─────────────────────────────────────────────────────────────────┐
│  STEP 1: Load Data                                              │
│  ────────────────                                               │
│  • Load Sample stack → 3D array (N_images, y, x)                │
│  • Load OB stack → 3D array (N_ob, y, x)                        │
│  • Load Dark Current stack → 3D array (N_dark, y, x)            │
│  • Load metadata: p_charge per image                            │
│  • Validate dimensions match (y, x must be same)                │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 2: Run Combining (Often Required for VENUS)               │
│  ────────────────────────────────────────────────               │
│  IF multiple runs provided:                                     │
│    • Aggregate sample images across runs                        │
│    • Aggregate OB images across runs                            │
│    • Aggregate dark images across runs                          │
│    • Average p_charge across runs (normalize_by_runs=True)      │
│    • Dead pixels detected once on the combined sample           │
│                                                                 │
│  Note: More important at VENUS due to lower integrated flux     │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 3: ROI Clipping (Optional)                                │
│  ───────────────────────────────                                │
│  IF ROI specified:                                              │
│    • Crop all arrays to ROI: arr[:, y0:y1, x0:x1]               │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 4: Prepare Reference Images                               │
│  ────────────────────────────────                               │
│  • Average dark images: Dark_avg = mean(Dark, axis=0) → 2D      │
│  • Average OB images: OB_avg = mean(OB, axis=0) → 2D            │
│  • Track p_charge_OB = mean(p_charge across OB runs)            │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 5: Dead Pixel Detection                                   │
│  ────────────────────────────                                   │
│  • Detect on the SAMPLE: pixels whose total counts, summed over │
│    the image-stack dimension (N_image), are exactly zero        │
│  • dead_mask = (Sample.sum(N_image) == 0)                       │
│  • Output: 2D boolean mask                                      │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 6: Gamma Filtering (Optional)                             │
│  ──────────────────────────────────                             │
│  Less critical at VENUS than MARS (no adjacent SANS beamline)   │
│                                                                 │
│  IF gamma filtering requested:                                  │
│    FOR each image:                                              │
│      • Detect gamma spikes (outliers > threshold)               │
│      • Replace with local median (3x3 neighborhood)             │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 7: Dark Current Correction                                │
│  ───────────────────────────────                                │
│  FOR each image i in Sample stack:                              │
│    Sample_corr[i] = Sample[i] - Dark_avg                        │
│                                                                 │
│  OB_corr = OB_avg - Dark_avg                                    │
│                                                                 │
│  Handle negative values: clip to zero or flag as invalid        │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 8: p_charge Beam Correction                               │
│  ────────────────────────────────                               │
│  PRIMARY correction for VENUS (pulsed source fluctuates)        │
│                                                                 │
│  FOR each image i:                                              │
│    f_beam[i] = p_charge_OB / p_charge_sample[i]                 │
│                                                                 │
│  Note: p_charge is proton charge from accelerator diagnostics   │
│  It correlates with neutron pulse intensity                     │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 9: Normalization                                          │
│  ─────────────────────                                          │
│  FOR each image i:                                              │
│                                                                 │
│    T[i] = (Sample_corr[i] / OB_corr) × f_beam[i]                │
│                                                                 │
│  Handle division:                                               │
│    • dead_mask is carried as a scipp mask (values not rewritten)│
│    • Where OB_corr == 0: T is inf/nan (division artifact)       │
│                                                                 │
│  Formula:                                                       │
│    T = [(I_sample - I_dark) / (I_OB - I_dark)] × f_p_charge     │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 10: Air Region Correction (Optional)                      │
│  ─────────────────────────────────────────                      │
│  Post-normalization refinement if p_charge wasn't sufficient    │
│                                                                 │
│  IF Air ROI specified:                                          │
│    1. Calculate mean transmission in air region:                │
│       <T_air> = mean(T[air_ROI])                                │
│                                                                 │
│    2. Scale to ensure air = 1.0:                                │
│       T_final = T / <T_air>                                     │
│                                                                 │
│  Goal: Correct residual beam fluctuations not captured by       │
│        p_charge. Air region should have T = 1.0 (no absorption) │
│                                                                 │
│  Note: This is "cherry on top" - only if p_charge insufficient  │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 11: Experiment Error Propagation                          │
│  ─────────────────────────────────────                          │
│  Sources of uncertainty:                                        │
│    • Poisson: σ_counts = √(N)                                   │
│    • p_charge: σ_p (Gaussian, from DAQ)                         │
│    • Air region: σ_air (if air correction applied)              │
│                                                                 │
│  Combined error propagation:                                    │
│                                                                 │
│  normalize_with_dark counts the shared dark ONCE (issue #142):  │
│  it propagates Var(D) through S_corr and OB_corr, then          │
│  subtracts the over-count 2·k²·S_corr·σ_D²/OB_corr³             │
│  (k = p_OB/p_sample). The σ_S, σ_OB and p_charge terms are:     │
│                                                                 │
│    Var(T) = T² × [ (σ_S/S_corr)² + (σ_OB/OB_corr)²              │
│                  + (σ_p_sample/p_sample)² + (σ_p_OB/p_OB)² ]    │
│             + Var(D) dark term (shared-dark corrected, see #142) │
│                                                                 │
│  If air correction applied: add (σ_air/<T_air>)² term           │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│  STEP 12: Output                                                │
│  ────────────                                                   │
│  • Transmission: 3D array (N_images, y, x) or (θ, y, x) for CT  │
│  • Experiment Error: 3D array (same shape as Transmission)      │
│  • Dead Pixel Mask: 2D boolean array (y, x)                     │
│  • Metadata: processing parameters, provenance                  │
└─────────────────────────────────────────────────────────────────┘

3. Output Specification

Output Dimensions dtype Description
Transmission (θ, y, x) float32 Normalized transmission values
Experiment Error (θ, y, x) float32 Propagated uncertainty (1σ)
Dead Pixel Mask (y, x) bool True = dead pixel
Metadata dict - Processing provenance

The pipeline computes in float32 end-to-end — TIFF/FITS images are loaded as float32 and all processing (combine, dark correction, normalization, uncertainty propagation) stays float32. float32 is sufficient for neutron imaging (16-bit detectors) and halves the in-memory footprint of large stacks.

Metadata contents:

  • Sample and OB file paths
  • Gamma filter applied (yes/no)
  • Dark correction applied (yes/no)
  • Processing timestamp
  • Software version
  • Dark file paths (if dark correction applied)
  • ROI applied (if any)

4. Decision Points

Step Decision Options
2 Multiple runs? Combine or single run
3 ROI needed? Apply crop or full frame
4 OB averaging Mean vs Median
6 Gamma filtering? Apply / Skip
8 p_charge available? Apply (default) / Skip (not recommended)
10 Air region correction? Apply if p_charge insufficient / Skip

5. Development Components

Required Modules

Component Purpose Priority
loaders.tiff_loader Load TIFF stacks P0
loaders.fits_loader Load FITS stacks P0
loaders.metadata_loader Extract p_charge from DAQ P0
processing.run_combiner Aggregate multiple runs P0 (critical for VENUS)
processing.roi_clipper Apply ROI to arrays P1
tof.pixel_detector Identify dead pixels P0
filters.gamma_filter Remove gamma contamination P1
processing.dark_corrector Subtract dark current P0
processing.normalizer Apply p_charge correction P0
processing.normalizer Compute transmission P0
processing.air_region_corrector Optional post-normalization correction P1
processing.uncertainty_calculator Error propagation P0
exporters.hdf5_writer / exporters.tiff_writer Write results (HDF5 primary; TIFF optional) P0

Data Models

InputData:
  - sample: NDArray[float32]       # (N, y, x)
  - open_beam: NDArray[float32]    # (N_ob, y, x)
  - dark_current: NDArray[float32] # (N_dark, y, x)
  - p_charge_sample: NDArray[float32]  # per image
  - p_charge_OB: float32           # total for OB
  - roi: Optional[Tuple[int, int, int, int]]
  - reference_roi: Optional[Tuple[int, int, int, int]]
  - metadata: Dict

ProcessedData:
  - transmission: NDArray[float32]  # (N, y, x)
  - uncertainty: NDArray[float32]   # (N, y, x)
  - dead_pixel_mask: NDArray[bool]  # (y, x)
  - metadata: Dict

6. Key Differences from MARS CCD/CMOS

Aspect MARS CCD/CMOS VENUS CCD/CMOS
Beam type Continuous Pulsed
Beam correction Not needed Required (p_charge or ROI)
Run combining Optional Often required
Gamma filtering Critical Optional
p_charge metadata Not available Available from DAQ
Error propagation Poisson only Poisson + p_charge

7. Validation Criteria

  • Transmission values in expected range (typically 0-1)
  • inf/nan only at zero-OB pixels (dead pixels carried as a mask, not NaN-filled)
  • Uncertainty > 0 for all valid pixels
  • Dead pixel mask correctly identifies zero-count pixels
  • Beam correction factor close to 1.0 (significant deviation indicates issues)
  • p_charge correlation check between sample and OB