modewise_operations/
functional_connectivity.rs

1use modewise_connectivity::Measure;
2use modewise_fc::{Source, functional_connectivity};
3use modewise_pipeline::{
4    Artifact, ArtifactKind, Inputs, Operation, OperationDescriptor, Outputs, Port, resolve_params,
5};
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 = "lowercase")]
14enum Kind {
15    Pearson,
16    Plv,
17}
18
19#[derive(Serialize, Deserialize, JsonSchema)]
20#[serde(deny_unknown_fields)]
21struct Params {
22    /// `pearson` (correlation) or `plv` (phase locking value).
23    measure: Kind,
24    /// Return atanh(r) (pearson only); the diagonal becomes NaN.
25    #[serde(default)]
26    fisher_z: bool,
27}
28
29impl Params {
30    fn measure(&self) -> Result<Measure, String> {
31        match (self.measure, self.fisher_z) {
32            (Kind::Pearson, fisher_z) => Ok(Measure::Pearson { fisher_z }),
33            (Kind::Plv, false) => Ok(Measure::PhaseLockingValue),
34            (Kind::Plv, true) => Err("fisher_z applies to pearson only".into()),
35        }
36    }
37}
38
39/// Functional connectivity per mode and per epoch (docs/connectivity.md).
40pub struct FunctionalConnectivityOperation;
41
42impl Operation for FunctionalConnectivityOperation {
43    fn summary(&self) -> &'static str {
44        "Functional connectivity per component (and per epoch): Pearson correlation or phase locking value (docs/connectivity.md)."
45    }
46
47    fn params_schema(&self) -> serde_json::Value {
48        modewise_pipeline::schema_of::<Params>()
49    }
50
51    fn descriptor(&self) -> OperationDescriptor {
52        OperationDescriptor {
53            name: "functional_connectivity",
54            version: env!("CARGO_PKG_VERSION"),
55            inputs: &[Port {
56                name: "data",
57                kinds: &[
58                    ArtifactKind::Series,
59                    ArtifactKind::Decomposition,
60                    ArtifactKind::Epochs,
61                ],
62            }],
63            outputs: &[Port {
64                name: "matrices",
65                kinds: &[ArtifactKind::FunctionalConnectivity],
66            }],
67        }
68    }
69
70    fn resolve(&self, params: Value) -> Result<Value, String> {
71        let (typed, resolved) = resolve_params::<Params>(params)?;
72        typed.measure()?;
73        Ok(resolved)
74    }
75
76    fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String> {
77        let measure = serde_json::from_value::<Params>(params.clone())
78            .map_err(|e| e.to_string())?
79            .measure()?;
80        let source = match input(inputs, "data")? {
81            Artifact::Series(s) => Source::Series(s),
82            Artifact::Decomposition(d) => Source::Decomposition(d),
83            Artifact::Epochs(e) => Source::Epochs(e),
84            other => return Err(format!("input \"data\" cannot be a {}", other.kind())),
85        };
86        let matrices = functional_connectivity(source, measure).map_err(|e| e.to_string())?;
87        Ok(Outputs::from([(
88            "matrices".into(),
89            Artifact::FunctionalConnectivity(matrices),
90        )]))
91    }
92}