modewise_operations/
wavelet.rs1use modewise_decomposition::ModeDecomposer;
2use modewise_pipeline::{
3 Artifact, ArtifactKind, Inputs, Operation, OperationDescriptor, Outputs, Port, resolve_params,
4};
5use modewise_wavelet::{Boundary, ModwtMra};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::input;
11
12#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)]
13#[serde(rename_all = "snake_case")]
14enum BoundaryName {
15 Periodic,
16 Reflection,
17}
18
19#[derive(Serialize, Deserialize, JsonSchema)]
20#[serde(deny_unknown_fields)]
21struct Params {
22 #[serde(default = "sym4")]
24 wavelet: String,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
27 levels: Option<usize>,
28 #[serde(default = "periodic")]
30 boundary: BoundaryName,
31}
32
33fn sym4() -> String {
34 "sym4".into()
35}
36
37fn periodic() -> BoundaryName {
38 BoundaryName::Periodic
39}
40
41impl Params {
42 fn method(&self) -> Result<ModwtMra, String> {
43 let boundary = match self.boundary {
44 BoundaryName::Periodic => Boundary::Periodic,
45 BoundaryName::Reflection => Boundary::Reflection,
46 };
47 ModwtMra::new(&self.wavelet, self.levels, boundary).map_err(|e| e.to_string())
48 }
49}
50
51pub struct ModwtMraOperation;
53
54impl Operation for ModwtMraOperation {
55 fn summary(&self) -> &'static str {
56 "MODWT multiresolution analysis: octave-band details and a smooth that sum to each channel (docs/wavelets.md)."
57 }
58
59 fn params_schema(&self) -> serde_json::Value {
60 modewise_pipeline::schema_of::<Params>()
61 }
62
63 fn descriptor(&self) -> OperationDescriptor {
64 OperationDescriptor {
65 name: "modwt_mra",
66 version: env!("CARGO_PKG_VERSION"),
67 inputs: &[Port {
68 name: "signal",
69 kinds: &[ArtifactKind::Series],
70 }],
71 outputs: &[Port {
72 name: "modes",
73 kinds: &[ArtifactKind::Decomposition],
74 }],
75 }
76 }
77
78 fn resolve(&self, params: Value) -> Result<Value, String> {
79 let (typed, resolved) = resolve_params::<Params>(params)?;
80 typed.method()?;
81 Ok(resolved)
82 }
83
84 fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String> {
85 let method = serde_json::from_value::<Params>(params.clone())
86 .map_err(|e| e.to_string())?
87 .method()?;
88 let Artifact::Series(signal) = input(inputs, "signal")? else {
89 return Err("input \"signal\" must be a series".into());
90 };
91 let modes = method.decompose(signal).map_err(|e| e.to_string())?;
92 Ok(Outputs::from([(
93 "modes".into(),
94 Artifact::Decomposition(modes),
95 )]))
96 }
97}