OpenHSI Architecture Overview

Core Architecture Patterns

OpenHSI uses a sophisticated object-oriented architecture built around several key design patterns:

  • Inheritance Hierarchies: Clear class hierarchies with well-defined responsibilities
  • Mixin Pattern: Composable functionality through mixins
  • Factory Pattern: Dynamic class creation for different configurations
  • Template Method: Configurable processing pipelines
  • Adapter Pattern: Unified interface across different hardware

Inheritance Hierarchies

The core inheritance structure follows a logical progression from basic camera properties to full hyperspectral imaging capabilities:

CameraProperties (data.py)
    ├── DataCube (data.py)
    │   └── OpenHSI (capture.py)
    │       └── [Camera Implementations]
    └── SharedDataCube (shared.py)
        └── SharedOpenHSI (shared.py)
            └── [Shared Camera Implementations]

Key Base Classes

CameraProperties (data.py)

  • Purpose: Core settings management, calibration handling, processing pipeline
  • Key Features:
    • Camera settings (exposure, gain, etc.)
    • Calibration parameters (flat field, dark current, spectral)
    • Processing pipeline configuration
    • Transform methods (cropping, binning, radiometric conversion)

DataCube (data.py)

  • Purpose: Hyperspectral data collection and storage
  • Inherits: CameraProperties
  • Adds:
    • Data buffering with CircArrayBuffer
    • Save/load functionality
    • Visualization methods
    • XArray integration

OpenHSI (capture.py)

  • Purpose: Main camera interface for data collection
  • Inherits: DataCube
  • Adds:
    • Camera control and acquisition
    • Real-time processing
    • Live preview capabilities
# Example: Basic inheritance structure
from openhsi.data import CameraProperties, DataCube
from openhsi.capture import OpenHSI

# Show the method resolution order (MRO)
print("CameraProperties MRO:", CameraProperties.__mro__)
print("DataCube MRO:", DataCube.__mro__)
print("OpenHSI MRO:", OpenHSI.__mro__)
CameraProperties MRO: (<class 'openhsi.data.CameraProperties'>, <class 'object'>)
DataCube MRO: (<class 'openhsi.data.DataCube'>, <class 'openhsi.data.CameraProperties'>, <class 'object'>)
OpenHSI MRO: (<class 'openhsi.capture.OpenHSI'>, <class 'openhsi.data.DataCube'>, <class 'openhsi.data.CameraProperties'>, <class 'object'>)
# Example: How mixins are composed dynamically
def create_settings_builder(clsname: str, cam_class: type) -> type:
    """Factory function to create a camera class with calibration capabilities"""
    return type(clsname, (cam_class, SettingsBuilderMixin), {})

# This creates classes like:
# FlirSettings = create_settings_builder('FlirSettings', FlirCamera)
# XimeaSettings = create_settings_builder('XimeaSettings', XimeaCamera)

Camera Architecture

The camera system uses a dual-inheritance pattern that combines hardware-specific implementations with processing capabilities:

Hardware Base Classes

Final Camera Classes

Multiple inheritance combines hardware interfaces with processing capabilities:

class FlirCamera(FlirCameraBase, OpenHSI): pass
class SharedFlirCamera(FlirCameraBase, SharedOpenHSI): pass

class LucidCamera(LucidCameraBase, OpenHSI): pass
class SharedLucidCamera(LucidCameraBase, SharedOpenHSI): pass

This pattern allows: - Same hardware interface to work with standard or shared memory architectures - Easy addition of new camera types - Consistent API across different hardware

# Example: Camera architecture in action
from openhsi.cameras import FlirCamera, SharedFlirCamera

# Both classes inherit from the same hardware base but different processing bases
print("FlirCamera MRO:", FlirCamera.__mro__)
print("SharedFlirCamera MRO:", SharedFlirCamera.__mro__)

# They provide the same interface but different memory management
# cam = FlirCamera()  # Standard memory
# shared_cam = SharedFlirCamera()  # Shared memory for multiprocessing
FlirCamera MRO: (<class 'openhsi.cameras.FlirCamera'>, <class 'openhsi.cameras.FlirCameraBase'>, <class 'openhsi.capture.OpenHSI'>, <class 'openhsi.data.DataCube'>, <class 'openhsi.data.CameraProperties'>, <class 'object'>)
SharedFlirCamera MRO: (<class 'openhsi.cameras.SharedFlirCamera'>, <class 'openhsi.cameras.FlirCameraBase'>, <class 'openhsi.shared.SharedOpenHSI'>, <class 'openhsi.shared.SharedDataCube'>, <class 'openhsi.data.CameraProperties'>, <class 'object'>)

Processing Pipeline

OpenHSI implements a Template Method pattern for the image processing pipeline:

Transform Pipeline

  • Processing steps are defined as methods that can be chained together
  • Pipeline is configurable through tfm_list parameter
  • Processing levels (0-8) provide preset combinations
  • Custom transform lists allow complete flexibility

Standard Processing Chain

Raw Image → crop → fast_smile → fast_bin → dn2rad → rad2ref_6SV → Processed Image

Processing Levels

  • Level 0: Raw data (no processing)
  • Level 1: Basic corrections (crop, smile)
  • Level 2: Radiometric conversion (dn2rad)
  • Level 3: Reflectance conversion (rad2ref)
  • Higher levels: Advanced atmospheric corrections
# Example: Processing pipeline configuration
from openhsi.data import CameraProperties

# Show available processing methods
processing_methods = [method for method in dir(CameraProperties) 
                     if not method.startswith('_') and 
                     method in ['crop', 'fast_smile', 'fast_bin', 'dn2rad', 'rad2ref_6SV']]
print("Available processing methods:", processing_methods)

# Example custom pipeline
custom_pipeline = ['crop', 'fast_bin', 'dn2rad']
print("Custom pipeline:", custom_pipeline)
Available processing methods: ['crop', 'dn2rad', 'fast_bin', 'fast_smile', 'rad2ref_6SV']
Custom pipeline: ['crop', 'fast_bin', 'dn2rad']

Module Organization

The codebase is organized into logical modules with clear responsibilities:

Core Modules

data.py - Data Layer

capture.py - Capture Layer

  • Classes: OpenHSI, SimulatedCamera, processing classes
  • Purpose: Camera control and data acquisition
  • Key Features: Live capture, preview, simulation

cameras.py - Hardware Abstraction

  • Classes: Camera-specific implementations
  • Purpose: Hardware interface abstraction
  • Key Features: Unified API across different camera brands

shared.py - Parallel Processing

  • Classes: SharedDataCube, SharedOpenHSI
  • Purpose: Multiprocessing-safe implementations
  • Key Features: Shared memory, background processes

calibrate.py - Calibration System

  • Classes: SettingsBuilderMixin
  • Purpose: Calibration workflows
  • Key Features: Spectral calibration, flat field, smile correction

Supporting Modules

  • sensors.py: Ancillary sensor integration
  • geometry.py: Geometric corrections
  • atmos.py: Atmospheric corrections
  • snr.py: Signal-to-noise analysis
  • metadata.py: Metadata handling
# Example: Module structure overview
import openhsi

# Show main modules
modules = [attr for attr in dir(openhsi) if not attr.startswith('_')]
print("Available modules:", modules)

# Show classes in data module
import openhsi.data as data
data_classes = [attr for attr in dir(data) if attr[0].isupper()]
print("Classes in data module:", data_classes)
Available modules: ['cameras', 'capture', 'data', 'shared']
Classes in data module: ['Array', 'Callable', 'CameraProperties', 'CircArrayBuffer', 'ContextManagers', 'DType', 'DataCube', 'DateTimeBuffer', 'EventTimer', 'Generic', 'Image', 'IterLen', 'Iterable', 'List', 'Optional', 'PartialFormatter', 'Path', 'ReindexCollection', 'Shape', 'Tuple', 'TypeVar', 'UNSET', 'Union', 'Unset']

Code Examples

Basic Usage Example

# Example: Basic camera setup and usage
from openhsi.capture import SimulatedCamera

# Create a simulated camera for demonstration
cam = SimulatedCamera(img_path="assets/great_hall_slide.png", 
                      n_lines=1024, 
                      processing_lvl = 2, 
                     json_path="assets/cam_settings.json",
                      cal_path="assets/cam_calibration.nc"
                     )

# Show the inheritance chain
print("SimulatedCamera inherits from:")
for i, cls in enumerate(cam.__class__.__mro__):
    print(f"  {i}: {cls.__name__} ({cls.__module__})")

# Show available methods from different layers
methods_by_layer = {
    'CameraProperties': ['crop', 'fast_smile', 'dn2rad'],
    'DataCube': ['put', 'save', 'show'],
    'OpenHSI': ['capture', 'preview', 'connect']
}

for layer, methods in methods_by_layer.items():
    available = [m for m in methods if hasattr(cam, m)]
    print(f"{layer} methods available: {available}")
Allocated 480.78 MB of RAM. There was 28095.53 MB available.
SimulatedCamera inherits from:
  0: SimulatedCamera (openhsi.capture)
  1: OpenHSI (openhsi.capture)
  2: DataCube (openhsi.data)
  3: CameraProperties (openhsi.data)
  4: object (builtins)
CameraProperties methods available: ['crop', 'fast_smile', 'dn2rad']
DataCube methods available: ['put', 'save', 'show']
OpenHSI methods available: []

Processing Pipeline Example

# Example: Custom processing pipeline
import numpy as np

# Create sample data
sample_data = np.random.randint(0, 4096, (100, 200, 150), dtype=np.uint16)

# Set up camera with custom processing
cam = SimulatedCamera(img_path="assets/great_hall_slide.png", 
                      n_lines=1024, 
                      processing_lvl = 4, 
                     json_path="assets/cam_settings.json",
                      cal_path="assets/cam_calibration.nc"
                     )
print(f"Processing level: {cam.proc_lvl}")
print(f"Transform list: {cam.tfm_list}")

# Show how transforms would be applied
if hasattr(cam, 'tfm_list'):
    print("Transform pipeline:")
    for i, transform in enumerate(cam.tfm_list or []):
        print(f"  {i+1}. {transform}")
Allocated 480.78 MB of RAM. There was 27964.42 MB available.
Processing level: 4
Transform list: [<bound method CameraProperties.crop of DataCube: shape = (905, 136), Processing level = 4
>, <bound method CameraProperties.fast_smile of DataCube: shape = (905, 136), Processing level = 4
>, <bound method CameraProperties.fast_bin of DataCube: shape = (905, 136), Processing level = 4
>, <bound method CameraProperties.dn2rad of DataCube: shape = (905, 136), Processing level = 4
>]
Transform pipeline:
  1. <bound method CameraProperties.crop of DataCube: shape = (905, 136), Processing level = 4
>
  2. <bound method CameraProperties.fast_smile of DataCube: shape = (905, 136), Processing level = 4
>
  3. <bound method CameraProperties.fast_bin of DataCube: shape = (905, 136), Processing level = 4
>
  4. <bound method CameraProperties.dn2rad of DataCube: shape = (905, 136), Processing level = 4
>

Mixin Composition Example

# Example: How mixins add functionality
from openhsi.calibrate import SettingsBuilderMixin

# Show mixin methods
mixin_methods = [method for method in dir(SettingsBuilderMixin) 
                if not method.startswith('_') and callable(getattr(SettingsBuilderMixin, method))]
print("SettingsBuilderMixin methods:", mixin_methods)

# Example of dynamic composition (simplified)
def demonstrate_mixin_composition():
    """Show how mixins are composed with base classes"""
    
    # This is conceptually how camera classes with calibration are created:
    # CalibrationCamera = type('CalibrationCamera', 
    #                         (SimulatedCamera, SettingsBuilderMixin), {})
    
    print("Mixin composition adds calibration methods to any camera class")
    print("Result: Camera + Calibration = Full featured calibration camera")

demonstrate_mixin_composition()
SettingsBuilderMixin methods: ['fit_HgAr_lines', 'fit_emission_lines', 'retake_HgAr', 'retake_emission_lines', 'retake_flat_field', 'update_intsphere_cube', 'update_intsphere_fit', 'update_resolution', 'update_row_minmax', 'update_smile_shifts', 'update_window_across_track', 'update_window_along_track']
Mixin composition adds calibration methods to any camera class
Result: Camera + Calibration = Full featured calibration camera