-
Udo Eisenbarth authoredUdo Eisenbarth authored
spectrometer.rs 9.14 KiB
#![warn(missing_docs)]
use serde_derive::{Deserialize, Serialize};
use serde_json::json;
use uom::si::length::nanometer;
use crate::dottable::Dottable;
use crate::error::OpmResult;
use crate::lightdata::LightData;
use crate::properties::{Properties, Property, Proptype};
use crate::{
error::OpossumError,
optic_ports::OpticPorts,
optical::{LightResult, Optical},
};
use std::collections::HashMap;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
#[non_exhaustive]
#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize)]
/// Type of the [`Spectrometer`]. This is currently not used.
pub enum SpectrometerType {
/// an ideal energy meter
#[default]
IdealSpectrometer,
/// Ocean Optics HR2000
HR2000,
}
/// (ideal) spectrometer
///
/// It normally measures / displays the spectrum of the incoming light.
///
/// ## Optical Ports
/// - Inputs
/// - `in1`
/// - Outputs
/// - `out1`
///
/// ## Properties
/// - `name`
/// - `spectrometer type
/// `
/// During analysis, the output port contains a replica of the input port similar to a [`Dummy`](crate::nodes::Dummy) node. This way,
/// different dectector nodes can be "stacked" or used somewhere within the optical setup.
pub struct Spectrometer {
light_data: Option<LightData>,
props: Properties,
}
fn create_default_props() -> Properties {
let mut props = Properties::default();
props.set("name", "spectrometer".into());
props.set(
"spectrometer type",
SpectrometerType::IdealSpectrometer.into(),
);
props.set("inverted", false.into());
props
}
impl Default for Spectrometer {
/// create an ideal spectrometer.
fn default() -> Self {
Self {
light_data: None,
props: create_default_props(),
}
}
}
impl Spectrometer {
/// Creates a new [`Spectrometer`] of the given [`SpectrometerType`].
pub fn new(name: &str, spectrometer_type: SpectrometerType) -> Self {
let mut props = create_default_props();
props.set("spectrometer type", spectrometer_type.into());
props.set("name", name.into());
Spectrometer {
props,
..Default::default()
}
}
/// Returns the meter type of this [`Spectrometer`].
pub fn spectrometer_type(&self) -> SpectrometerType {
let meter_type = self.props.get("spectrometer type").unwrap().prop.clone();
if let Proptype::SpectrometerType(meter_type) = meter_type {
meter_type
} else {
panic!("wrong data format")
}
}
/// Sets the meter type of this [`Spectrometer`].
pub fn set_spectrometer_type(&mut self, meter_type: SpectrometerType) {
self.props.set("spectrometer type", meter_type.into());
}
}
impl Optical for Spectrometer {
fn name(&self) -> &str {
if let Proptype::String(name) = &self.props.get("name").unwrap().prop {
name
} else {
self.node_type()
}
}
fn node_type(&self) -> &str {
"spectrometer"
}
fn inverted(&self) -> bool {
self.properties().get_bool("inverted").unwrap().unwrap()
}
fn ports(&self) -> OpticPorts {
let mut ports = OpticPorts::new();
ports.add_input("in1").unwrap();
ports.add_output("out1").unwrap();
ports
}
fn analyze(
&mut self,
incoming_data: LightResult,
_analyzer_type: &crate::analyzer::AnalyzerType,
) -> OpmResult<LightResult> {
let (src, target) = if self.inverted() {
("out1", "in1")
} else {
("in1", "out1")
};
let data = incoming_data.get(src).unwrap_or(&None);
self.light_data = data.clone();
Ok(HashMap::from([(target.into(), data.clone())]))
}
fn export_data(&self, report_dir: &Path) {
if let Some(data) = &self.light_data {
let mut file_path = PathBuf::from(report_dir);
file_path.push(format!("spectrum_{}.svg", self.name()));
data.export(&file_path)
}
}
fn is_detector(&self) -> bool {
true
}
fn properties(&self) -> &Properties {
&self.props
}
fn set_property(&mut self, name: &str, prop: Property) -> OpmResult<()> {
if self.props.set(name, prop).is_none() {
Err(OpossumError::Other("property not defined".into()))
} else {
Ok(())
}
}
fn report(&self) -> serde_json::Value {
let data = &self.light_data;
let mut energy_data = serde_json::Value::Null;
if let Some(LightData::Energy(e)) = data {
energy_data = e.spectrum.to_json();
}
json!({"type": self.node_type(),
"name": self.name(),
"energy": energy_data})
}
}
impl Debug for Spectrometer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.light_data {
Some(data) => match data {
LightData::Energy(data_energy) => {
let spectrum_range = data_energy.spectrum.range();
write!(
f,
"Spectrum {:.3} - {:.3} nm (Type: {:?})",
spectrum_range.start.get::<nanometer>(),
spectrum_range.end.get::<nanometer>(),
self.spectrometer_type()
)
}
_ => write!(f, "no spectrum data to display"),
},
None => write!(f, "no data"),
}
}
}
impl Dottable for Spectrometer {
fn node_color(&self) -> &str {
"lightseagreen"
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{analyzer::AnalyzerType, lightdata::DataEnergy, spectrum::create_he_ne_spectrum};
#[test]
fn default() {
let node = Spectrometer::default();
assert!(node.light_data.is_none());
assert_eq!(
node.spectrometer_type(),
SpectrometerType::IdealSpectrometer
);
assert_eq!(node.name(), "spectrometer");
assert_eq!(node.node_type(), "spectrometer");
assert_eq!(node.is_detector(), true);
assert_eq!(node.inverted(), false);
assert_eq!(node.node_color(), "lightseagreen");
assert!(node.as_group().is_err());
}
#[test]
fn new() {
let meter = Spectrometer::new("test", SpectrometerType::HR2000);
assert_eq!(meter.name(), "test");
assert!(meter.light_data.is_none());
assert_eq!(meter.spectrometer_type(), SpectrometerType::HR2000);
}
#[test]
fn set_meter_type() {
let mut meter = Spectrometer::new("test", SpectrometerType::IdealSpectrometer);
meter.set_spectrometer_type(SpectrometerType::HR2000);
assert_eq!(meter.spectrometer_type(), SpectrometerType::HR2000);
}
#[test]
fn ports() {
let meter = Spectrometer::default();
assert_eq!(meter.ports().inputs(), vec!["in1"]);
assert_eq!(meter.ports().outputs(), vec!["out1"]);
}
#[test]
fn inverted() {
let mut node = Spectrometer::default();
node.set_property("inverted", true.into()).unwrap();
assert_eq!(node.inverted(), true)
}
#[test]
fn analyze_ok() {
let mut node = Spectrometer::default();
let mut input = LightResult::default();
let input_light = LightData::Energy(DataEnergy {
spectrum: create_he_ne_spectrum(1.0),
});
input.insert("in1".into(), Some(input_light.clone()));
let output = node.analyze(input, &AnalyzerType::Energy);
assert!(output.is_ok());
let output = output.unwrap();
assert!(output.contains_key("out1".into()));
assert_eq!(output.len(), 1);
let output = output.get("out1".into()).unwrap();
assert!(output.is_some());
let output = output.clone().unwrap();
assert_eq!(output, input_light);
}
#[test]
fn analyze_wrong() {
let mut node =Spectrometer::default();
let mut input = LightResult::default();
let input_light = LightData::Energy(DataEnergy {
spectrum: create_he_ne_spectrum(1.0),
});
input.insert("wrong".into(), Some(input_light.clone()));
let output = node.analyze(input, &AnalyzerType::Energy);
assert!(output.is_ok());
let output = output.unwrap();
let output = output.get("out1".into()).unwrap();
assert!(output.is_none());
}
#[test]
fn analyze_inverse() {
let mut node = Spectrometer::default();
node.set_property("inverted", true.into()).unwrap();
let mut input = LightResult::default();
let input_light = LightData::Energy(DataEnergy {
spectrum: create_he_ne_spectrum(1.0),
});
input.insert("out1".into(), Some(input_light.clone()));
let output = node.analyze(input, &AnalyzerType::Energy);
assert!(output.is_ok());
let output = output.unwrap();
assert!(output.contains_key("in1".into()));
assert_eq!(output.len(), 1);
let output = output.get("in1".into()).unwrap();
assert!(output.is_some());
let output = output.clone().unwrap();
assert_eq!(output, input_light);
}
}