Skip to content

Commit 8ddb62f

Browse files
committed
wip: integration computing node in the graph
1 parent 8db2a20 commit 8ddb62f

File tree

3 files changed

+167
-2
lines changed

3 files changed

+167
-2
lines changed

rust/config.example.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,26 @@ processing:
286286
frequency_max: 2200.0 # Upper bound (Hz)
287287
smoothing_factor: 0.7 # Moving average smoothing
288288

289+
# Concentration calculation based on peak detection
290+
# This node calculates the concentration based on the detected peak frequency
291+
- id: "concentration_calculator"
292+
node_type: "computing_concentration"
293+
parameters:
294+
computing_peak_finder_id: "peak_detector" # Reference to the peak detector node
295+
polynomial_coefficients: # Polynomial coefficients for concentration calculation
296+
# Array of 5 coefficients [a₀, a₁, a₂, a₃, a₄]
297+
# Polynomial coefficients for concentration calculation a₀, a₁, a₂, a₃, a₄
298+
# Concentration(ppm) = a₀ + a₁A + a₂A² + a₃A³ + a₄A⁴ where A is the peak amplitude
299+
- 0.0 # a₀ constant term
300+
- 25.0 # a₁ linear term
301+
- -0.3 # a₂ quadratic term
302+
- 0.0 # a₃ cubic term
303+
- 0.0 # a₄ quartic term
304+
temperature_compensation: false # Controle temperature compensation
305+
spectral_line_id: CO₂_4.26μm # Optional line identifier for concentration calculation
306+
min_amplitude_threshold: 0.001 # Minimum amplitude threshold for valid concentration calculation
307+
max_concentration_ppm: 100.0 # Maximum concentration limit for safety/validation
308+
289309
# Apply gain adjustment (+3 dB amplification)
290310
- id: "gain_amplifier"
291311
node_type: "gain"
@@ -318,6 +338,8 @@ processing:
318338
- from: bandpass_filter
319339
to: peak_detector
320340
- from: peak_detector
341+
to: concentration_calculator
342+
- from: concentration_calculator
321343
to: streaming_bandpass_filter
322344
- from: streaming_bandpass_filter
323345
to: processed_recorder

rust/resources/config.schema.json

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,8 @@
553553
"photoacoustic_output",
554554
"record",
555555
"streaming",
556-
"computing_peak_finder"
556+
"computing_peak_finder",
557+
"computing_concentration"
557558
],
558559
"description": "Type of processing node"
559560
},
@@ -847,6 +848,64 @@
847848
}
848849
}
849850
},
851+
{
852+
"if": {
853+
"properties": {
854+
"node_type": {
855+
"const": "computing_concentration"
856+
}
857+
}
858+
},
859+
"then": {
860+
"properties": {
861+
"parameters": {
862+
"type": "object",
863+
"properties": {
864+
"computing_peak_finder_id": {
865+
"type": "string",
866+
"description": "ID of the PeakFinderNode to use as data source. If not specified, uses the most recent peak data available."
867+
},
868+
"polynomial_coefficients": {
869+
"type": "array",
870+
"items": {
871+
"type": "number"
872+
},
873+
"minItems": 5,
874+
"maxItems": 5,
875+
"description": "Array of 5 coefficients [a₀, a₁, a₂, a₃, a₄] for polynomial concentration calculation: Concentration(ppm) = a₀ + a₁*A + a₂*A² + a₃*A³ + a₄*A⁴ where A is the peak amplitude"
876+
},
877+
"temperature_compensation": {
878+
"type": "boolean",
879+
"default": false,
880+
"description": "Enable temperature compensation for improved accuracy"
881+
},
882+
"spectral_line_id": {
883+
"type": "string",
884+
"description": "Optional identifier for the spectral line being analyzed (e.g., 'CO₂_4.26μm', 'CH₄_3.39μm')"
885+
},
886+
"min_amplitude_threshold": {
887+
"type": "number",
888+
"minimum": 0.0,
889+
"maximum": 10.0,
890+
"default": 0.001,
891+
"description": "Minimum amplitude threshold for valid concentration calculation"
892+
},
893+
"max_concentration_ppm": {
894+
"type": "number",
895+
"minimum": 0.1,
896+
"maximum": 1000000.0,
897+
"default": 10000.0,
898+
"description": "Maximum concentration limit for safety/validation (ppm)"
899+
}
900+
},
901+
"required": [
902+
"polynomial_coefficients"
903+
],
904+
"additionalProperties": false
905+
}
906+
}
907+
}
908+
},
850909
{
851910
"if": {
852911
"properties": {

rust/src/processing/graph.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use crate::config::processing::{NodeConfig, ProcessingGraphConfig};
1111
use crate::preprocessing::differential::SimpleDifferential;
1212
use crate::preprocessing::filters::{BandpassFilter, HighpassFilter, LowpassFilter};
13-
use crate::processing::computing_nodes::{PeakFinderNode, SharedComputingState};
13+
use crate::processing::computing_nodes::{ConcentrationNode, PeakFinderNode, SharedComputingState};
1414
use crate::processing::nodes::{
1515
ChannelMixerNode, ChannelSelectorNode, ChannelTarget, DifferentialNode, FilterNode, GainNode,
1616
InputNode, MixStrategy, NodeId, PhotoacousticOutputNode, ProcessingData, ProcessingNode,
@@ -1220,6 +1220,90 @@ impl ProcessingGraph {
12201220

12211221
Ok(Box::new(peak_finder))
12221222
}
1223+
"computing_concentration" => {
1224+
// Extract concentration calculator parameters
1225+
let params = config
1226+
.parameters
1227+
.as_object()
1228+
.ok_or_else(|| anyhow::anyhow!("Concentration node requires parameters"))?;
1229+
1230+
// Create concentration node with shared state
1231+
let mut concentration_node = ConcentrationNode::new_with_shared_state(
1232+
config.id.clone(),
1233+
computing_state.clone(),
1234+
);
1235+
1236+
// Extract computing_peak_finder_id (required)
1237+
let peak_finder_id = params
1238+
.get("computing_peak_finder_id")
1239+
.and_then(|v| v.as_str())
1240+
.ok_or_else(|| {
1241+
anyhow::anyhow!(
1242+
"Concentration node requires 'computing_peak_finder_id' parameter"
1243+
)
1244+
})?;
1245+
concentration_node =
1246+
concentration_node.with_peak_finder_source(peak_finder_id.to_string());
1247+
1248+
// Extract polynomial coefficients (required array of 5 values)
1249+
if let Some(coeffs_value) = params.get("polynomial_coefficients") {
1250+
if let Some(coeffs_array) = coeffs_value.as_array() {
1251+
if coeffs_array.len() == 5 {
1252+
let mut coefficients = [0.0; 5];
1253+
for (i, coeff) in coeffs_array.iter().enumerate() {
1254+
if let Some(val) = coeff.as_f64() {
1255+
coefficients[i] = val;
1256+
} else {
1257+
return Err(anyhow::anyhow!(
1258+
"Polynomial coefficient {} must be a number",
1259+
i
1260+
));
1261+
}
1262+
}
1263+
concentration_node =
1264+
concentration_node.with_polynomial_coefficients(coefficients);
1265+
} else {
1266+
return Err(anyhow::anyhow!(
1267+
"Polynomial coefficients must be an array of exactly 5 values, got {}",
1268+
coeffs_array.len()
1269+
));
1270+
}
1271+
} else {
1272+
return Err(anyhow::anyhow!("Polynomial coefficients must be an array"));
1273+
}
1274+
}
1275+
1276+
// Extract optional parameters
1277+
if let Some(temp_comp) = params.get("temperature_compensation") {
1278+
if let Some(enable_temp_comp) = temp_comp.as_bool() {
1279+
concentration_node =
1280+
concentration_node.with_temperature_compensation(enable_temp_comp);
1281+
}
1282+
}
1283+
1284+
if let Some(spectral_line) = params.get("spectral_line_id") {
1285+
if let Some(line_id) = spectral_line.as_str() {
1286+
concentration_node =
1287+
concentration_node.with_spectral_line_id(line_id.to_string());
1288+
}
1289+
}
1290+
1291+
if let Some(min_threshold) = params.get("min_amplitude_threshold") {
1292+
if let Some(threshold) = min_threshold.as_f64() {
1293+
concentration_node =
1294+
concentration_node.with_min_amplitude_threshold(threshold as f32);
1295+
}
1296+
}
1297+
1298+
if let Some(max_conc) = params.get("max_concentration_ppm") {
1299+
if let Some(max_ppm) = max_conc.as_f64() {
1300+
concentration_node =
1301+
concentration_node.with_max_concentration(max_ppm as f32);
1302+
}
1303+
}
1304+
1305+
Ok(Box::new(concentration_node))
1306+
}
12231307
"gain" => {
12241308
// Extract gain parameters
12251309
let params = config

0 commit comments

Comments
 (0)