Source code for ms_camera_model.model

'''
Multispectral Camera Model - Simulation Model
=============================================

* **Description:** Simulated model of a multispectral camera. Takes in hyperspectral data and colour filter specs.
  Outputs multispectral data
* **Author:** Tomas Vacek
'''

from __future__ import annotations

import logging
from dataclasses import dataclass, field

import numpy as np

from ms_camera_model.errors import NoImageData, NoProvidedFilterSensorUnits
from ms_camera_model.filter_sensor import FilterSensorUnit, InterpolatedFilterSensorUnit
from ms_camera_model.image_data import (
    HyperspectralImageData,
    ImageData,
    ModeledMultispectralImageData,
)

logger = logging.getLogger(__name__)


[docs] @dataclass class MultispectralCameraModel: """ Multispectral camera model :param hs_data: HyperspectralImageData class instance :param filter_sensor_units: list of InterpolatedFilterSensorUnit class instances :param band_names: list of names for spectral bands :param out_data: simulated multispectral image data as ImageData class instance """ hs_data: HyperspectralImageData filter_sensor_units: list[InterpolatedFilterSensorUnit] band_names: list[str] out_data: ModeledMultispectralImageData = field(init=False)
[docs] @classmethod def create_model(cls, hs_data: HyperspectralImageData, fs_units: list[FilterSensorUnit], band_names: list[str]) -> MultispectralCameraModel: """ Interpolate FilterSensorUnits to hyperspectral image data and create model with corrected units :param hs_data: HyperspectralImageData class instance :param fs_units: list of FilterSensorUnit class instances :param band_names: list of names for spectral bands """ if not isinstance(fs_units, list): raise TypeError(f"Expected list of FilterSensorUnit class instances, got {type(fs_units)}") if not isinstance(hs_data, HyperspectralImageData): raise TypeError(f"Expected HyperspectralImageData, got {type(hs_data)}") if not isinstance(band_names, list): raise TypeError(f"Expected list of band names, got {type(band_names)}") if not fs_units: raise NoProvidedFilterSensorUnits corrected_units = [] for filter_sensor_unit in fs_units: corrected_units.append( InterpolatedFilterSensorUnit.interpolate_to_hs_data(filter_sensor_unit, hs_data.band_centers)) return MultispectralCameraModel(hs_data, corrected_units, band_names)
[docs] def run_simulation(self) -> ImageData: """ Match filter_sensor_units specs with hs_data band_centers and calculate out_data :return: Returns resulting simulated ImageData """ logger.info("[MSModel] Units matched. Proceeding with data extraction...") self._extract_data() logger.info("[MSModel] Simulated multispectral data extraction completed") return self.out_data
def _extract_data(self) -> None: """ Extract simulated multispectral data from the hyperspectral img_data using filter_sensor_units """ if self.hs_data.img_data.size == 0: raise NoImageData shape = self.hs_data.img_data.shape modeled_ms_data = np.zeros((shape[0], shape[1], len(self.filter_sensor_units)), dtype=np.float32) modeled_ms_data_band_centers = [] for i_unit, unit in enumerate(self.filter_sensor_units): modeled_ms_data[:, :, i_unit] = self._calculate_band(unit) modeled_ms_data_band_centers.append(unit.filter_spec.band_center) logger.info(f"[MSModel] modeled_ms_data max val: {modeled_ms_data.max()}") self.out_data = ModeledMultispectralImageData(modeled_ms_data, modeled_ms_data_band_centers, len(self.filter_sensor_units), self.band_names) def _calculate_band(self, filter_sensor_unit: InterpolatedFilterSensorUnit) -> np.ndarray: """ Calculate the image created by a filter sensor unit :param filter_sensor_unit: Input FilterSensorUnit """ logger.info("[MSModel] Calculating single band image from hyperspectral data...") data_through_unit = self.hs_data.img_data * filter_sensor_unit.combined_response logger.info(f"[MSModel] Max data val: {data_through_unit.max()}") logger.info(f"[MSModel] Data type: {data_through_unit.dtype}") logger.info("[MSModel] Performing trapezoidal integration...") signal_integral = np.trapezoid(data_through_unit, axis=2) filter_integral = np.trapezoid(filter_sensor_unit.combined_response) out_img = signal_integral / filter_integral logger.info("[MSModel] Band calculation completed") return out_img