Source code for patato.unmixing.spectra

#  Copyright (c) Thomas Else 2023-25.
#  License: MIT

import os
from abc import ABC, abstractmethod
from os.path import join
from typing import Union

import numpy as np


# ALL UNITS IN INVERSE CENTIMETRES (some per mole where specified)


[docs] class Spectrum(ABC): @staticmethod @abstractmethod def get_name() -> Union[str, None]: pass @staticmethod @abstractmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: pass
[docs] class Haemoglobin(Spectrum): # In cm-1/M, divide by 187 for indicative mua (see prahl) @staticmethod def get_name() -> str: return "Hb" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: from pandas import read_table folder = os.path.dirname(os.path.realpath(__file__)) prahl_spec = read_table(join(folder, "spectra_files", "prahl.txt")) return np.interp( wavelengths, prahl_spec["lambda"], prahl_spec["Hb"], np.nan, np.nan )
[docs] class OxyHaemoglobin(Spectrum): # In cm-1/M, divide by 187 for indicative mua (see prahl) @staticmethod def get_name() -> str: return "HbO2" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: from pandas import read_table folder = os.path.dirname(os.path.realpath(__file__)) prahl_spec = read_table(join(folder, "spectra_files", "prahl.txt")) return np.interp( wavelengths, prahl_spec["lambda"], prahl_spec["Hb02"], np.nan, np.nan )
[docs] class IndocyanineGreen(Spectrum): @staticmethod def get_name() -> str: return "ICG" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: from pandas import read_csv folder = os.path.dirname(os.path.realpath(__file__)) df = read_csv( join(folder, "spectra_files", "ICG.csv"), header=None, sep=";", decimal="," ) df = df[df[0] < 890] return np.interp(wavelengths, df[0], df[1], np.nan, np.nan)
[docs] class Melanin(Spectrum): # NOTE: Multiply by a volume fraction (~0.01-0.4) to get a realistic epidermis absorption. @staticmethod def get_name() -> str: return "Melanin" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: if not isinstance(wavelengths, np.ndarray): wavelengths = np.array(wavelengths) return 1.7e12 * wavelengths ** (-3.48)
[docs] class Lipids(Spectrum): # From https://omlc.org/spectra/fat/fat.txt @staticmethod def get_name() -> str: return "Lipid" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: from pandas import read_table # print(os.path.realpath(__file__)) folder = os.path.dirname(os.path.realpath(__file__)) df = read_table(join(folder, "spectra_files", "lipids.txt"), skiprows=4) return np.interp(wavelengths, df["nm"], df["mu_a(1/m)"] / 300, np.nan, np.nan)
[docs] class Water(Spectrum): @staticmethod def get_name() -> str: return "H2O" @staticmethod def get_spectrum(wavelengths: np.ndarray) -> np.ndarray: import pandas as pd folder = os.path.dirname(os.path.realpath(__file__)) water_file = join(folder, "spectra_files", "water.txt") water_short = pd.read_table(water_file) water_file_long = join(folder, "spectra_files", "water_long.txt") water_long = pd.read_table(water_file_long) water_long = water_long.sort_values("lambda", ignore_index=True) # regrid min_wavelength = water_short["lambda"].min() max_wavelength = water_long["lambda"].max() grid_wavelengths = np.arange(min_wavelength, max_wavelength, 5) short = np.interp( grid_wavelengths, water_short["lambda"], water_short["absorption"], np.nan, np.nan, ) long = np.interp( grid_wavelengths, water_long["lambda"], water_long["absorption"], np.nan, np.nan, ) absorption_values = np.nanmean(np.array([short, long]), axis=0) return np.interp(wavelengths, grid_wavelengths, absorption_values)
SPECTRA = [Haemoglobin, OxyHaemoglobin, IndocyanineGreen, Melanin, Water, Lipids] SPECTRA_NAMES = {s.get_name(): s for s in SPECTRA}