Add Subcarrier Manifold and Vitals Oracle modules for 3D visualizations
- Implemented Subcarrier Manifold to visualize amplitude data as a 3D surface with height and age attributes. - Created Vitals Oracle to represent vital signs using toroidal rings and particle trails, incorporating breathing and heart rate dynamics. - Both modules utilize Three.js for rendering and include custom shaders for visual effects.
This commit is contained in:
parent
decd97b6ab
commit
d23007120e
|
|
@ -1 +1 @@
|
|||
166
|
||||
54612
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"timestamp": "2026-03-06T13:17:27.368Z",
|
||||
"mode": "local",
|
||||
"checks": {
|
||||
"envFilesProtected": true,
|
||||
"gitIgnoreExists": true,
|
||||
"noHardcodedSecrets": true
|
||||
},
|
||||
"riskLevel": "low",
|
||||
"recommendations": [],
|
||||
"note": "Install Claude Code CLI for AI-powered security analysis"
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"enabledMcpjsonServers": [
|
||||
"claude-flow"
|
||||
],
|
||||
"enableAllProjectMcpServers": true
|
||||
}
|
||||
10
README.md
10
README.md
|
|
@ -638,6 +638,8 @@ cargo add wifi-densepose-ruvector # RuVector v2.0.4 integration layer (ADR-017
|
|||
|
||||
All crates integrate with [RuVector v2.0.4](https://github.com/ruvnet/ruvector) — see [AI Backbone](#ai-backbone-ruvector) below.
|
||||
|
||||
**[rUv Neural](rust-port/wifi-densepose-rs/crates/ruv-neural/)** — A separate 12-crate workspace for brain network topology analysis, neural decoding, and medical sensing. See [rUv Neural](#ruv-neural) in Models & Training.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
|
@ -736,6 +738,7 @@ The neural pipeline uses a graph transformer with cross-attention to map CSI fea
|
|||
| [RVF Model Container](#rvf-model-container) | Binary packaging with Ed25519 signing, progressive 3-layer loading, SIMD quantization | [ADR-023](docs/adr/ADR-023-trained-densepose-model-ruvector-pipeline.md) |
|
||||
| [Training & Fine-Tuning](#training--fine-tuning) | 8-phase pure Rust pipeline (7,832 lines), MM-Fi/Wi-Pose pre-training, 6-term composite loss, SONA LoRA | [ADR-023](docs/adr/ADR-023-trained-densepose-model-ruvector-pipeline.md) |
|
||||
| [RuVector Crates](#ruvector-crates) | 11 vendored Rust crates from [ruvector](https://github.com/ruvnet/ruvector): attention, min-cut, solver, GNN, HNSW, temporal compression, sparse inference | [GitHub](https://github.com/ruvnet/ruvector) · [Source](vendor/ruvector/) |
|
||||
| [rUv Neural](#ruv-neural) | 12-crate brain topology analysis ecosystem: neural decoding, quantum sensor integration, cognitive state classification, BCI output | [README](rust-port/wifi-densepose-rs/crates/ruv-neural/README.md) |
|
||||
| [AI Backbone (RuVector)](#ai-backbone-ruvector) | 5 AI capabilities replacing hand-tuned thresholds: attention, graph min-cut, sparse solvers, tiered compression | [crates.io](https://crates.io/crates/wifi-densepose-ruvector) |
|
||||
| [Self-Learning WiFi AI (ADR-024)](#self-learning-wifi-ai-adr-024) | Contrastive self-supervised learning, room fingerprinting, anomaly detection, 55 KB model | [ADR-024](docs/adr/ADR-024-contrastive-csi-embedding-model.md) |
|
||||
| [Cross-Environment Generalization (ADR-027)](docs/adr/ADR-027-cross-environment-domain-generalization.md) | Domain-adversarial training, geometry-conditioned inference, hardware normalization, zero-shot deployment | [ADR-027](docs/adr/ADR-027-cross-environment-domain-generalization.md) |
|
||||
|
|
@ -1421,6 +1424,13 @@ The full RuVector ecosystem includes 90+ crates. See [github.com/ruvnet/ruvector
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><a id="ruv-neural"></a><strong>🧠 rUv Neural</strong> — Brain topology analysis ecosystem for neural decoding and medical sensing</summary>
|
||||
|
||||
[**rUv Neural**](rust-port/wifi-densepose-rs/crates/ruv-neural/README.md) is a 12-crate Rust ecosystem that extends RuView's signal processing into brain network topology analysis. It transforms neural magnetic field measurements from quantum sensors (NV diamond magnetometers, optically pumped magnetometers) into dynamic connectivity graphs, using minimum cut algorithms to detect cognitive state transitions in real time. The ecosystem includes crates for signal processing (`ruv-neural-signal`), graph construction (`ruv-neural-graph`), HNSW-indexed pattern memory (`ruv-neural-memory`), graph embeddings (`ruv-neural-embed`), cognitive state decoding (`ruv-neural-decoder`), and ESP32/WASM edge targets. Medical and research applications include early neurological disease detection via topology signatures, brain-computer interfaces, clinical neurofeedback, and non-invasive biomedical sensing -- bridging RuView's RF sensing architecture with the emerging field of quantum biomedical diagnostics.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
## Introduction
|
||||
|
||||
RuView is a WiFi-based human pose estimation system built on ESP32 CSI (Channel State Information). Today, managing a RuView deployment requires juggling **6+ disconnected CLI tools**: `esptool.py` for flashing, `provision.py` for NVS configuration, `curl` for OTA and WASM management, `cargo run` for the sensing server, a browser for visualization, and manual IP tracking for node discovery. There is no single tool that provides a unified view of the entire deployment — from ESP32 hardware through the sensing pipeline to pose visualization.
|
||||
|
||||
This issue tracks the implementation of **RuView Desktop** — a Tauri v2 cross-platform desktop application that replaces all of these tools with a single, cohesive interface. The application is designed as the **control plane** for the RuView platform, managing the full lifecycle: discover, flash, provision, OTA, load WASM, observe sensing.
|
||||
|
||||
### Why Tauri (Not Electron/Flutter/Web)
|
||||
|
||||
| Requirement | Why Desktop is Required |
|
||||
|-------------|------------------------|
|
||||
| Serial port access | Browser/PWA cannot touch COM/tty ports for firmware flashing |
|
||||
| Raw UDP sockets | Node discovery via broadcast probes requires raw socket access |
|
||||
| Filesystem access | Firmware binaries, WASM modules, model files live on local disk |
|
||||
| Process management | Sensing server runs as a managed child process (sidecar) |
|
||||
| Small binary | Tauri ~20 MB vs Electron ~150 MB |
|
||||
| Rust integration | Shares crates with existing workspace |
|
||||
|
||||
### UI Design Language
|
||||
|
||||
The frontend uses a **Foundation Book** design scheme with **Unity Editor-inspired** UI panels. Think: clean typographic hierarchy, structured panels with dockable regions, monospaced data displays, and a professional dark theme with accent colors for status indicators. Powered by rUv.
|
||||
|
||||
---
|
||||
|
||||
## ADR-052 Deep Overview
|
||||
|
||||
The full architecture is documented in [ADR-052](https://github.com/ruvnet/RuView/blob/feat/tauri-desktop-frontend/docs/adr/ADR-052-tauri-desktop-frontend.md) with a companion [DDD bounded contexts appendix](https://github.com/ruvnet/RuView/blob/feat/tauri-desktop-frontend/docs/adr/ADR-052-ddd-bounded-contexts.md).
|
||||
|
||||
### Workspace Integration
|
||||
|
||||
The desktop app is a new Rust crate (`wifi-densepose-desktop`) in the existing workspace, sharing types with the sensing server and hardware crate. The frontend uses React + Vite + TypeScript with a Foundation Book / Unity-inspired design system.
|
||||
|
||||
### 6 Rust Command Groups
|
||||
|
||||
| Group | Commands | Bounded Context |
|
||||
|-------|----------|-----------------|
|
||||
| **Discovery** | `discover_nodes`, `get_node_status`, `watch_nodes` | Device Discovery |
|
||||
| **Flash** | `list_serial_ports`, `flash_firmware`, `read_chip_info` | Firmware Management |
|
||||
| **OTA** | `ota_update`, `ota_status`, `ota_batch_update` | Firmware Management |
|
||||
| **WASM** | `wasm_list`, `wasm_upload`, `wasm_control` | Edge Module |
|
||||
| **Server** | `start_server`, `stop_server`, `server_status` | Sensing Pipeline |
|
||||
| **Provision** | `provision_node`, `read_nvs` | Configuration |
|
||||
|
||||
### 7 Frontend Pages
|
||||
|
||||
| Page | Purpose |
|
||||
|------|---------|
|
||||
| **Dashboard** | Node count (online/offline), server status, quick actions, activity feed |
|
||||
| **Node Detail** | Single node deep-dive: firmware, health, TDM config, WASM modules |
|
||||
| **Flash Firmware** | 3-step wizard: select port, select firmware, flash with progress bar |
|
||||
| **WASM Modules** | Drag-and-drop upload, module list with start/stop/unload |
|
||||
| **Sensing View** | Live CSI heatmap, pose skeleton overlay, vital signs |
|
||||
| **Mesh Topology** | Force-directed graph: TDM slots, sync drift, node health |
|
||||
| **Settings** | Server ports, bind address, OTA PSK, UI theme |
|
||||
|
||||
### DDD Bounded Contexts
|
||||
|
||||
6 bounded contexts with 9 aggregates, 25+ domain events, and 3 anti-corruption layers. See the [DDD appendix](https://github.com/ruvnet/RuView/blob/feat/tauri-desktop-frontend/docs/adr/ADR-052-ddd-bounded-contexts.md) for full details.
|
||||
|
||||
| Context | Aggregate Root(s) | Key Events |
|
||||
|---------|--------------------|------------|
|
||||
| Device Discovery | `NodeRegistry` | `NodeDiscovered`, `NodeWentOffline`, `ScanCompleted` |
|
||||
| Firmware Management | `FlashSession`, `OtaSession`, `BatchOtaSession` | `FlashProgress`, `OtaCompleted`, `BatchOtaCompleted` |
|
||||
| Configuration | `ProvisioningSession` | `NodeProvisioned`, `ConfigReadBack` |
|
||||
| Sensing Pipeline | `SensingServer`, `WebSocketSession` | `ServerStarted`, `FrameReceived` |
|
||||
| Edge Module (WASM) | `ModuleRegistry` | `ModuleUploaded`, `ModuleStarted` |
|
||||
| Visualization | Query model (no aggregate) | Consumes all upstream events |
|
||||
|
||||
### Persistent Node Registry
|
||||
|
||||
Stored in `~/.ruview/nodes.db` (SQLite). On startup, previously known nodes load as Offline and reconcile against fresh discovery. The app remembers the mesh across restarts.
|
||||
|
||||
### OTA Safety Gate
|
||||
|
||||
The `TdmSafe` rolling update strategy updates even-slot nodes first, then odd-slot nodes, ensuring adjacent nodes are never offline simultaneously during mesh-wide firmware updates.
|
||||
|
||||
### Platform-Specific Considerations
|
||||
|
||||
| Platform | Concern | Solution |
|
||||
|----------|---------|----------|
|
||||
| macOS | USB serial drivers need signing on Sequoia+ | Document driver requirements |
|
||||
| Windows | COM port naming, UAC | Auto-detect via registry |
|
||||
| Linux | Serial port permissions | Bundle udev rules installer |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
| Phase | Scope | Priority |
|
||||
|-------|-------|----------|
|
||||
| 1. Skeleton | Tauri scaffolding, workspace integration, React window | P0 |
|
||||
| 2. Discovery | Serial ports, node discovery, dashboard cards | P0 |
|
||||
| 3. Flash | espflash integration, flashing wizard | P0 |
|
||||
| 4. Server | Sidecar sensing server, log viewer | P1 |
|
||||
| 5. OTA | HTTP OTA with PSK auth, batch TdmSafe | P1 |
|
||||
| 6. Provisioning | NVS GUI form, read-back, mesh presets | P1 |
|
||||
| 7. WASM | Module upload/list/control | P2 |
|
||||
| 8. Sensing | WebSocket, live charts, pose overlay | P2 |
|
||||
| 9. Mesh View | Topology graph, TDM visualization | P2 |
|
||||
| 10. Polish | App signing, auto-update, onboarding wizard | P3 |
|
||||
|
||||
Total estimated effort: ~11 weeks for a single developer.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Tauri app builds on Windows, macOS, Linux
|
||||
- [ ] Can discover ESP32 nodes on local network
|
||||
- [ ] Node registry persists across restarts
|
||||
- [ ] Can flash firmware via serial port (no Python dependency)
|
||||
- [ ] Can push OTA updates with PSK authentication
|
||||
- [ ] Rolling OTA with TdmSafe strategy for mesh deployments
|
||||
- [ ] Can upload/manage WASM modules on nodes
|
||||
- [ ] Can start/stop sensing server and view live logs
|
||||
- [ ] Can view real-time sensing data via WebSocket
|
||||
- [ ] Can provision NVS config via GUI form
|
||||
- [ ] Mesh topology visualization shows TDM slots and health
|
||||
- [ ] Binary size less than 30 MB
|
||||
- [ ] Foundation Book / Unity-inspired UI design system
|
||||
- [ ] Each new Rust module has unit tests
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ADR-012: ESP32 CSI Sensor Mesh
|
||||
- ADR-039: ESP32 Edge Intelligence
|
||||
- ADR-040: WASM Programmable Sensing
|
||||
- ADR-044: Provisioning Tool Enhancements
|
||||
- ADR-050: Quality Engineering Security Hardening
|
||||
- ADR-051: Sensing Server Decomposition
|
||||
- ADR-053: UI Design System (Foundation Book + Unity-inspired)
|
||||
|
||||
## Branch
|
||||
|
||||
[`feat/tauri-desktop-frontend`](https://github.com/ruvnet/RuView/tree/feat/tauri-desktop-frontend)
|
||||
|
||||
## References
|
||||
|
||||
- [ADR-052: Tauri Desktop Frontend](https://github.com/ruvnet/RuView/blob/feat/tauri-desktop-frontend/docs/adr/ADR-052-tauri-desktop-frontend.md)
|
||||
- [ADR-052 DDD Appendix](https://github.com/ruvnet/RuView/blob/feat/tauri-desktop-frontend/docs/adr/ADR-052-ddd-bounded-contexts.md)
|
||||
- [Tauri v2 Documentation](https://v2.tauri.app/)
|
||||
- [espflash crate](https://crates.io/crates/espflash)
|
||||
|
||||
Powered by **rUv**
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{"type":"edit","file":"unknown","timestamp":1772820418129,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772820462588,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772820472219,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772832571444,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772832585997,"sessionId":null}
|
||||
|
|
@ -51,13 +51,6 @@ petgraph = "0.6"
|
|||
# Async runtime
|
||||
tokio = { version = "1.35", features = ["full"] }
|
||||
|
||||
# RuVector integration (published on crates.io)
|
||||
ruvector-mincut = "2.0.4"
|
||||
ruvector-attn-mincut = "2.0.4"
|
||||
ruvector-temporal-tensor = "2.0.4"
|
||||
ruvector-solver = "2.0.4"
|
||||
ruvector-attention = "2.0.4"
|
||||
|
||||
# WASM support
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,181 +1,112 @@
|
|||
# rUv Neural CLI
|
||||
# ruv-neural-cli
|
||||
|
||||
CLI tool for the rUv Neural brain topology analysis system. Provides commands for
|
||||
simulating neural sensor data, analyzing brain connectivity graphs, computing
|
||||
minimum cuts, running full analysis pipelines, and exporting results to multiple
|
||||
visualization formats.
|
||||
CLI tool for brain topology analysis, simulation, and visualization.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-cli` is the command-line binary (`ruv-neural`) that ties together
|
||||
the entire rUv Neural crate ecosystem. It provides subcommands for simulating
|
||||
neural sensor data, analyzing brain connectivity graphs, computing minimum cuts,
|
||||
running the full processing pipeline with an optional ASCII dashboard, and
|
||||
exporting to multiple visualization formats.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Build from source
|
||||
cargo install --path .
|
||||
|
||||
# Or run directly
|
||||
cargo run -p ruv-neural-cli -- <command>
|
||||
```
|
||||
|
||||
Or build from the workspace root:
|
||||
## Commands
|
||||
|
||||
### `simulate` -- Generate synthetic neural data
|
||||
|
||||
```bash
|
||||
cargo build -p ruv-neural-cli --release
|
||||
ruv-neural simulate --channels 64 --duration 10 --sample-rate 1000 --output data.json
|
||||
```
|
||||
|
||||
The binary is named `ruv-neural`.
|
||||
| Flag | Default | Description |
|
||||
|------------------|---------|------------------------------|
|
||||
| `-c, --channels` | 64 | Number of sensor channels |
|
||||
| `-d, --duration` | 10.0 | Duration in seconds |
|
||||
| `-s, --sample-rate` | 1000.0 | Sample rate in Hz |
|
||||
| `-o, --output` | (none) | Output file path (JSON) |
|
||||
|
||||
## Command Reference
|
||||
|
||||
| Command | Description |
|
||||
|------------|-------------------------------------------------------|
|
||||
| `simulate` | Generate simulated multi-channel neural sensor data |
|
||||
| `analyze` | Load and analyze a brain connectivity graph (JSON) |
|
||||
| `mincut` | Compute minimum cut (Stoer-Wagner or multi-way) |
|
||||
| `pipeline` | Full end-to-end: simulate -> filter -> graph -> decode|
|
||||
| `export` | Export brain graph to D3, DOT, GEXF, CSV, or RVF |
|
||||
| `info` | Show system info, crate versions, and capabilities |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Simulate Neural Data
|
||||
|
||||
Generate 64-channel simulated neural data at 1 kHz for 10 seconds:
|
||||
### `analyze` -- Analyze a brain connectivity graph
|
||||
|
||||
```bash
|
||||
ruv-neural simulate -c 64 -d 10.0 -s 1000.0 -o output.json
|
||||
ruv-neural analyze --input graph.json --ascii --csv metrics.csv
|
||||
```
|
||||
|
||||
Default parameters (no arguments required):
|
||||
| Flag | Default | Description |
|
||||
|----------------|---------|--------------------------------|
|
||||
| `-i, --input` | (required) | Input graph file (JSON) |
|
||||
| `--ascii` | false | Show ASCII visualization |
|
||||
| `--csv` | (none) | Export metrics to CSV file |
|
||||
|
||||
### `mincut` -- Compute minimum cut
|
||||
|
||||
```bash
|
||||
ruv-neural simulate
|
||||
ruv-neural mincut --input graph.json --k 4
|
||||
```
|
||||
|
||||
### Analyze a Brain Graph
|
||||
| Flag | Default | Description |
|
||||
|----------------|---------|--------------------------------|
|
||||
| `-i, --input` | (required) | Input graph file (JSON) |
|
||||
| `-k` | (none) | Multi-way cut with k partitions|
|
||||
|
||||
Load a graph from JSON and display topology metrics:
|
||||
### `pipeline` -- Full end-to-end pipeline
|
||||
|
||||
```bash
|
||||
ruv-neural analyze -i brain_graph.json
|
||||
ruv-neural pipeline --channels 32 --duration 5 --dashboard
|
||||
```
|
||||
|
||||
With ASCII adjacency matrix visualization:
|
||||
Runs: simulate -> preprocess -> build graph -> mincut -> embed -> decode.
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------------------|---------|--------------------------------|
|
||||
| `-c, --channels` | 32 | Number of sensor channels |
|
||||
| `-d, --duration` | 5.0 | Duration in seconds |
|
||||
| `--dashboard` | false | Show real-time ASCII dashboard |
|
||||
|
||||
### `export` -- Export to visualization format
|
||||
|
||||
```bash
|
||||
ruv-neural analyze -i brain_graph.json --ascii
|
||||
ruv-neural export --input graph.json --format dot --output graph.dot
|
||||
```
|
||||
|
||||
Export per-node metrics to CSV:
|
||||
| Flag | Default | Description |
|
||||
|------------------|---------|---------------------------------------|
|
||||
| `-i, --input` | (required) | Input graph file (JSON) |
|
||||
| `-f, --format` | d3 | Output format: d3, dot, gexf, csv, rvf |
|
||||
| `-o, --output` | (required) | Output file path |
|
||||
|
||||
```bash
|
||||
ruv-neural analyze -i brain_graph.json --csv metrics.csv
|
||||
```
|
||||
|
||||
### Compute Minimum Cut
|
||||
|
||||
Standard two-way Stoer-Wagner minimum cut:
|
||||
|
||||
```bash
|
||||
ruv-neural mincut -i brain_graph.json
|
||||
```
|
||||
|
||||
Multi-way cut with 4 partitions:
|
||||
|
||||
```bash
|
||||
ruv-neural mincut -i brain_graph.json -k 4
|
||||
```
|
||||
|
||||
### Run Full Pipeline
|
||||
|
||||
The pipeline command runs all stages end-to-end:
|
||||
|
||||
1. Generate simulated sensor data
|
||||
2. Preprocess (bandpass filter 1-100 Hz)
|
||||
3. Construct brain connectivity graph (PLV)
|
||||
4. Compute minimum cut and topology metrics
|
||||
5. Generate topology and spectral embeddings
|
||||
6. Decode cognitive state
|
||||
7. Display results summary
|
||||
|
||||
```bash
|
||||
ruv-neural pipeline -c 32 -d 5.0
|
||||
```
|
||||
|
||||
With ASCII dashboard visualization:
|
||||
|
||||
```bash
|
||||
ruv-neural pipeline -c 16 -d 3.0 --dashboard
|
||||
```
|
||||
|
||||
### Export Graph
|
||||
|
||||
Export to D3.js-compatible JSON:
|
||||
|
||||
```bash
|
||||
ruv-neural export -i brain_graph.json -f d3 -o graph.d3.json
|
||||
```
|
||||
|
||||
Export to Graphviz DOT:
|
||||
|
||||
```bash
|
||||
ruv-neural export -i brain_graph.json -f dot -o graph.dot
|
||||
```
|
||||
|
||||
All supported formats:
|
||||
|
||||
```bash
|
||||
ruv-neural export -i graph.json -f d3 -o out.json # D3.js JSON
|
||||
ruv-neural export -i graph.json -f dot -o out.dot # Graphviz DOT
|
||||
ruv-neural export -i graph.json -f gexf -o out.gexf # GEXF XML
|
||||
ruv-neural export -i graph.json -f csv -o out.csv # CSV edge list
|
||||
ruv-neural export -i graph.json -f rvf -o out.rvf # RuVector File
|
||||
```
|
||||
|
||||
### System Info
|
||||
### `info` -- Show system information
|
||||
|
||||
```bash
|
||||
ruv-neural info
|
||||
```
|
||||
|
||||
### Verbosity
|
||||
Displays crate versions, available features, and system capabilities.
|
||||
|
||||
Use `-v` flags for increased logging detail:
|
||||
## Global Options
|
||||
|
||||
```bash
|
||||
ruv-neural -v pipeline -c 8 -d 2.0 # INFO level
|
||||
ruv-neural -vv pipeline -c 8 -d 2.0 # DEBUG level
|
||||
ruv-neural -vvv pipeline -c 8 -d 2.0 # TRACE level
|
||||
```
|
||||
| Flag | Description |
|
||||
|------------------|------------------------------------|
|
||||
| `-v` | Increase verbosity (up to `-vvv`) |
|
||||
| `--version` | Print version |
|
||||
| `--help` | Print help |
|
||||
|
||||
## Output Formats
|
||||
## Integration
|
||||
|
||||
| Format | Extension | Description |
|
||||
|--------|-----------|------------------------------------------------|
|
||||
| D3 | `.json` | D3.js force-directed graph with nodes and links|
|
||||
| DOT | `.dot` | Graphviz DOT for rendering with `dot` or `neato`|
|
||||
| GEXF | `.gexf` | Graph Exchange XML Format for Gephi |
|
||||
| CSV | `.csv` | Edge list with source, target, weight, metric |
|
||||
| RVF | `.json` | RuVector File format with adjacency matrix |
|
||||
Depends on all workspace crates: `ruv-neural-core`, `ruv-neural-sensor`,
|
||||
`ruv-neural-signal`, `ruv-neural-graph`, `ruv-neural-mincut`, `ruv-neural-embed`,
|
||||
`ruv-neural-memory`, `ruv-neural-decoder`, and `ruv-neural-viz`. Uses `clap`
|
||||
for argument parsing and `tokio` for async runtime.
|
||||
|
||||
## Pipeline Walkthrough
|
||||
## License
|
||||
|
||||
The `pipeline` command demonstrates the full rUv Neural analysis flow:
|
||||
|
||||
```
|
||||
simulate -> filter -> PLV graph -> mincut -> embed -> decode
|
||||
```
|
||||
|
||||
**Step 1 - Simulate**: Generates multi-channel neural data with alpha (10 Hz),
|
||||
beta (20 Hz), and gamma (40 Hz) oscillations plus white noise.
|
||||
|
||||
**Step 2 - Filter**: Applies a 4th-order Butterworth bandpass filter (1-100 Hz)
|
||||
using zero-phase SOS filtering.
|
||||
|
||||
**Step 3 - Graph**: Computes Phase Locking Value (PLV) between all channel pairs
|
||||
and constructs a brain connectivity graph with edges above a PLV threshold.
|
||||
|
||||
**Step 4 - Mincut**: Runs the Stoer-Wagner algorithm for the global minimum cut,
|
||||
revealing the natural partition boundary in the brain network.
|
||||
|
||||
**Step 5 - Embed**: Generates both topology-based and spectral (Laplacian
|
||||
eigenvector) embeddings of the brain graph state.
|
||||
|
||||
**Step 6 - Decode**: Classifies the cognitive state (Rest, Focused, MotorPlanning)
|
||||
using a threshold decoder on topology metrics.
|
||||
|
||||
**Step 7 - Results**: Displays a formatted summary table with all computed metrics.
|
||||
MIT OR Apache-2.0
|
||||
|
|
|
|||
|
|
@ -1,73 +1,102 @@
|
|||
# rUv Neural Core
|
||||
# ruv-neural-core
|
||||
|
||||
**Core types, traits, and error types for the ruv-neural brain topology analysis system.**
|
||||
Core types, traits, and error types for the rUv Neural brain topology analysis system.
|
||||
|
||||
`ruv-neural-core` is the foundational crate of the ruv-neural workspace. It defines all shared types, error variants, and trait interfaces that downstream crates implement. It has **zero internal dependencies** -- every other ruv-neural crate depends on this one.
|
||||
## Overview
|
||||
|
||||
## Feature Flags
|
||||
`ruv-neural-core` is the foundation crate of the rUv Neural workspace. It defines all
|
||||
shared data types, trait interfaces, and the RVF binary file format used across the
|
||||
other eleven crates. This crate has **zero** internal dependencies -- every other
|
||||
ruv-neural crate depends on it.
|
||||
|
||||
| Feature | Default | Description |
|
||||
|----------|---------|------------------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `no_std` | No | Embedded/ESP32 target compatibility |
|
||||
| `wasm` | No | WebAssembly target support |
|
||||
| `rvf` | No | RuVector RVF file format extensions |
|
||||
## Features
|
||||
|
||||
## Type Overview
|
||||
|
||||
| Module | Key Types |
|
||||
|-------------|------------------------------------------------------------------|
|
||||
| `error` | `RuvNeuralError`, `Result<T>` |
|
||||
| `sensor` | `SensorType`, `SensorChannel`, `SensorArray` |
|
||||
| `signal` | `MultiChannelTimeSeries`, `FrequencyBand`, `SpectralFeatures` |
|
||||
| `brain` | `Atlas`, `BrainRegion`, `Hemisphere`, `Lobe`, `Parcellation` |
|
||||
| `graph` | `BrainGraph`, `BrainEdge`, `ConnectivityMetric` |
|
||||
| `topology` | `MincutResult`, `MultiPartition`, `CognitiveState`, `TopologyMetrics` |
|
||||
| `embedding` | `NeuralEmbedding`, `EmbeddingMetadata`, `EmbeddingTrajectory` |
|
||||
| `rvf` | `RvfFile`, `RvfHeader`, `RvfDataType` |
|
||||
|
||||
## Trait Overview
|
||||
|
||||
| Trait | Purpose |
|
||||
|----------------------|------------------------------------------------|
|
||||
| `SensorSource` | Read chunks from hardware or simulated sensors |
|
||||
| `SignalProcessor` | Transform time series (filter, artifact removal)|
|
||||
| `GraphConstructor` | Build connectivity graphs from signals |
|
||||
| `TopologyAnalyzer` | Compute mincut, modularity, efficiency |
|
||||
| `EmbeddingGenerator` | Map brain graphs to vector space |
|
||||
| `StateDecoder` | Classify cognitive state from embeddings |
|
||||
| `NeuralMemory` | Store and query embedding history |
|
||||
| `RvfSerializable` | Serialize/deserialize to RVF file format |
|
||||
- **Sensor types**: `SensorType`, `SensorChannel`, `SensorArray` with sensitivity specs
|
||||
for NV diamond, OPM, SQUID MEG, and EEG sensors
|
||||
- **Signal types**: `MultiChannelTimeSeries`, `FrequencyBand` (delta through gamma + custom),
|
||||
`SpectralFeatures`, `TimeFrequencyMap`
|
||||
- **Brain atlas**: `Atlas` (Desikan-Killiany 68, Destrieux 148, Schaefer 100/200/400, custom),
|
||||
`BrainRegion`, `Parcellation` with hemisphere and lobe queries
|
||||
- **Graph types**: `BrainGraph` with adjacency matrix, density, and degree methods;
|
||||
`BrainEdge`, `ConnectivityMetric`, `BrainGraphSequence`
|
||||
- **Topology types**: `MincutResult`, `MultiPartition`, `TopologyMetrics`, `CognitiveState`,
|
||||
`SleepStage`
|
||||
- **Embedding types**: `NeuralEmbedding` with cosine similarity and Euclidean distance,
|
||||
`EmbeddingTrajectory`, `EmbeddingMetadata`
|
||||
- **RVF format**: Binary RuVector File format with magic bytes, versioned headers,
|
||||
typed payloads, and read/write round-trip support
|
||||
- **Trait definitions**: `SensorSource`, `SignalProcessor`, `GraphConstructor`,
|
||||
`TopologyAnalyzer`, `EmbeddingGenerator`, `NeuralMemory`, `StateDecoder`,
|
||||
`RvfSerializable`
|
||||
- **Error handling**: `RuvNeuralError` enum with `DimensionMismatch`, `ChannelOutOfRange`,
|
||||
`InsufficientData`, and domain-specific variants
|
||||
- **Feature flags**: `std` (default), `no_std` (ESP32/embedded), `wasm`, `rvf`
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_core::{
|
||||
Atlas, BrainGraph, BrainEdge, ConnectivityMetric, FrequencyBand,
|
||||
BrainGraph, BrainEdge, ConnectivityMetric, FrequencyBand, Atlas,
|
||||
NeuralEmbedding, EmbeddingMetadata, CognitiveState,
|
||||
RvfFile, RvfDataType,
|
||||
Result,
|
||||
MultiChannelTimeSeries, RvfFile, RvfDataType,
|
||||
};
|
||||
|
||||
// Build a connectivity graph
|
||||
// Create a brain graph
|
||||
let graph = BrainGraph {
|
||||
num_nodes: 68,
|
||||
num_nodes: 3,
|
||||
edges: vec![BrainEdge {
|
||||
source: 0,
|
||||
target: 1,
|
||||
weight: 0.85,
|
||||
source: 0, target: 1, weight: 0.8,
|
||||
metric: ConnectivityMetric::PhaseLockingValue,
|
||||
frequency_band: FrequencyBand::Alpha,
|
||||
}],
|
||||
timestamp: 1000.0,
|
||||
window_duration_s: 2.0,
|
||||
timestamp: 0.0,
|
||||
window_duration_s: 1.0,
|
||||
atlas: Atlas::DesikanKilliany68,
|
||||
};
|
||||
|
||||
let adj = graph.adjacency_matrix();
|
||||
let matrix = graph.adjacency_matrix();
|
||||
let density = graph.density();
|
||||
|
||||
// Create a neural embedding
|
||||
let meta = EmbeddingMetadata {
|
||||
subject_id: Some("sub-01".into()),
|
||||
session_id: None,
|
||||
cognitive_state: Some(CognitiveState::Focused),
|
||||
source_atlas: Atlas::Schaefer100,
|
||||
embedding_method: "spectral".into(),
|
||||
};
|
||||
let emb = NeuralEmbedding::new(vec![3.0, 4.0], 1000.0, meta).unwrap();
|
||||
assert_eq!(emb.dimension, 2);
|
||||
assert!((emb.norm() - 5.0).abs() < 1e-10);
|
||||
|
||||
// Write/read RVF files
|
||||
let mut rvf = RvfFile::new(RvfDataType::BrainGraph);
|
||||
rvf.data = serde_json::to_vec(&graph).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
rvf.write_to(&mut buf).unwrap();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `sensor` | `SensorType`, `SensorChannel`, `SensorArray` |
|
||||
| `signal` | `MultiChannelTimeSeries`, `FrequencyBand`, `SpectralFeatures` |
|
||||
| `brain` | `Atlas`, `BrainRegion`, `Parcellation`, `Hemisphere`, `Lobe` |
|
||||
| `graph` | `BrainGraph`, `BrainEdge`, `ConnectivityMetric` |
|
||||
| `topology` | `MincutResult`, `TopologyMetrics`, `CognitiveState` |
|
||||
| `embedding` | `NeuralEmbedding`, `EmbeddingTrajectory`, `EmbeddingMetadata` |
|
||||
| `rvf` | `RvfFile`, `RvfHeader`, `RvfDataType` |
|
||||
| `traits` | `SensorSource`, `SignalProcessor`, `EmbeddingGenerator`, etc. |
|
||||
| `error` | `RuvNeuralError`, `Result<T>` |
|
||||
|
||||
## Integration
|
||||
|
||||
This crate is a dependency of every other crate in the ruv-neural workspace.
|
||||
It provides the shared type vocabulary that allows crates to interoperate --
|
||||
for example, `ruv-neural-signal` produces `MultiChannelTimeSeries` values,
|
||||
`ruv-neural-graph` consumes them, and `ruv-neural-embed` outputs
|
||||
`NeuralEmbedding` values that `ruv-neural-memory` stores.
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::brain::Atlas;
|
||||
use crate::error::{Result, RuvNeuralError};
|
||||
use crate::signal::FrequencyBand;
|
||||
|
||||
/// Connectivity metric used to compute edge weights.
|
||||
|
|
@ -55,6 +56,37 @@ pub struct BrainGraph {
|
|||
}
|
||||
|
||||
impl BrainGraph {
|
||||
/// Validate graph integrity: edge bounds, weight finiteness, no self-loops.
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
for (i, edge) in self.edges.iter().enumerate() {
|
||||
if edge.source >= self.num_nodes {
|
||||
return Err(RuvNeuralError::Graph(format!(
|
||||
"Edge {i}: source {} out of bounds (num_nodes={})",
|
||||
edge.source, self.num_nodes
|
||||
)));
|
||||
}
|
||||
if edge.target >= self.num_nodes {
|
||||
return Err(RuvNeuralError::Graph(format!(
|
||||
"Edge {i}: target {} out of bounds (num_nodes={})",
|
||||
edge.target, self.num_nodes
|
||||
)));
|
||||
}
|
||||
if edge.source == edge.target {
|
||||
return Err(RuvNeuralError::Graph(format!(
|
||||
"Edge {i}: self-loop on node {}",
|
||||
edge.source
|
||||
)));
|
||||
}
|
||||
if !edge.weight.is_finite() {
|
||||
return Err(RuvNeuralError::Graph(format!(
|
||||
"Edge {i}: non-finite weight {}",
|
||||
edge.weight
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build a dense adjacency matrix (num_nodes x num_nodes).
|
||||
/// For duplicate edges, the last one wins.
|
||||
pub fn adjacency_matrix(&self) -> Vec<Vec<f64>> {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ pub const RVF_MAGIC: [u8; 4] = [b'R', b'V', b'F', 0x01];
|
|||
/// Current RVF format version.
|
||||
pub const RVF_VERSION: u8 = 1;
|
||||
|
||||
/// Maximum allowed metadata JSON length (16 MiB).
|
||||
pub const MAX_METADATA_LEN: u32 = 16 * 1024 * 1024;
|
||||
|
||||
/// Maximum allowed payload length when reading (256 MiB).
|
||||
pub const MAX_PAYLOAD_LEN: usize = 256 * 1024 * 1024;
|
||||
|
||||
/// Data type stored in an RVF file.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum RvfDataType {
|
||||
|
|
@ -190,6 +196,13 @@ impl RvfFile {
|
|||
let header = RvfHeader::from_bytes(&header_bytes)?;
|
||||
header.validate()?;
|
||||
|
||||
if header.metadata_json_len > MAX_METADATA_LEN {
|
||||
return Err(RuvNeuralError::Serialization(format!(
|
||||
"RVF metadata length {} exceeds maximum {}",
|
||||
header.metadata_json_len, MAX_METADATA_LEN
|
||||
)));
|
||||
}
|
||||
|
||||
let mut meta_bytes = vec![0u8; header.metadata_json_len as usize];
|
||||
reader
|
||||
.read_exact(&mut meta_bytes)
|
||||
|
|
@ -203,6 +216,13 @@ impl RvfFile {
|
|||
.read_to_end(&mut data)
|
||||
.map_err(|e| RuvNeuralError::Serialization(e.to_string()))?;
|
||||
|
||||
if data.len() > MAX_PAYLOAD_LEN {
|
||||
return Err(RuvNeuralError::Serialization(format!(
|
||||
"RVF payload length {} exceeds maximum {}",
|
||||
data.len(), MAX_PAYLOAD_LEN
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
metadata,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ pub struct MultiChannelTimeSeries {
|
|||
impl MultiChannelTimeSeries {
|
||||
/// Create a new time series, validating dimensions.
|
||||
pub fn new(data: Vec<Vec<f64>>, sample_rate_hz: f64, timestamp_start: f64) -> Result<Self> {
|
||||
if !sample_rate_hz.is_finite() || sample_rate_hz <= 0.0 {
|
||||
return Err(RuvNeuralError::Signal(
|
||||
"sample_rate_hz must be finite and positive".into(),
|
||||
));
|
||||
}
|
||||
let num_channels = data.len();
|
||||
if num_channels == 0 {
|
||||
return Err(RuvNeuralError::Signal(
|
||||
|
|
|
|||
|
|
@ -1,85 +1,92 @@
|
|||
# rUv Neural Decoder
|
||||
# ruv-neural-decoder
|
||||
|
||||
Cognitive state classification and BCI decoding from neural topology embeddings.
|
||||
|
||||
Part of the **rUv Neural** brain-computer interface platform.
|
||||
## Overview
|
||||
|
||||
## Decoders
|
||||
`ruv-neural-decoder` classifies cognitive states from brain graph embeddings and
|
||||
topology metrics. It provides multiple decoding strategies -- KNN classification
|
||||
from labeled exemplars, threshold-based rule systems, temporal transition detection,
|
||||
and clinical biomarker scoring -- plus an ensemble pipeline that combines all
|
||||
strategies for robust real-time brain-computer interface (BCI) output.
|
||||
|
||||
| Decoder | Description |
|
||||
|---------|-------------|
|
||||
| **KnnDecoder** | K-nearest neighbor classification using stored labeled embeddings with inverse-distance weighting |
|
||||
| **ThresholdDecoder** | Rule-based classification from topology metric ranges (mincut, modularity, efficiency, entropy) |
|
||||
| **TransitionDecoder** | Detects cognitive state transitions by matching topology delta patterns against a sliding window |
|
||||
| **ClinicalScorer** | Biomarker detection via z-score deviation from a learned healthy baseline population |
|
||||
| **DecoderPipeline** | End-to-end ensemble combining all decoders with configurable weights and clinical scoring |
|
||||
## Features
|
||||
|
||||
## Pipeline Architecture
|
||||
|
||||
```
|
||||
NeuralEmbedding ──> KnnDecoder ─────────┐
|
||||
│
|
||||
TopologyMetrics ──> ThresholdDecoder ────┤── Weighted Vote ──> DecoderOutput
|
||||
│ │ state, confidence
|
||||
├─> TransitionDecoder ──┘ transition
|
||||
│ brain_health_index
|
||||
└─> ClinicalScorer ─────────> clinical_flags
|
||||
```
|
||||
- **KNN decoder** (`knn_decoder`): K-nearest neighbor classification using stored
|
||||
labeled embeddings from `ruv-neural-memory`; supports configurable k and distance
|
||||
metrics
|
||||
- **Threshold decoder** (`threshold_decoder`): Rule-based classification from
|
||||
topology metric ranges (mincut value, modularity, efficiency, Fiedler value)
|
||||
with configurable `TopologyThreshold` bounds per cognitive state
|
||||
- **Transition decoder** (`transition_decoder`): Detects cognitive state transitions
|
||||
from temporal topology dynamics; outputs `StateTransition` events matching
|
||||
known `TransitionPattern` templates
|
||||
- **Clinical scorer** (`clinical`): `ClinicalScorer` for biomarker detection via
|
||||
deviation from healthy baseline distributions; flags abnormal topology patterns
|
||||
- **Ensemble pipeline** (`pipeline`): `DecoderPipeline` combining all decoder
|
||||
strategies with confidence-weighted voting; produces `DecoderOutput` with
|
||||
classified state, confidence score, and contributing decoder votes
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_decoder::{DecoderPipeline, TopologyThreshold};
|
||||
use ruv_neural_decoder::{
|
||||
KnnDecoder, ThresholdDecoder, TopologyThreshold,
|
||||
TransitionDecoder, ClinicalScorer, DecoderPipeline, DecoderOutput,
|
||||
};
|
||||
use ruv_neural_core::topology::{CognitiveState, TopologyMetrics};
|
||||
|
||||
// Build a pipeline with all decoders
|
||||
let mut pipeline = DecoderPipeline::new()
|
||||
.with_knn(5)
|
||||
.with_thresholds()
|
||||
.with_transitions(10)
|
||||
.with_clinical(baseline_metrics, baseline_std);
|
||||
// Threshold-based decoding from topology metrics
|
||||
let mut decoder = ThresholdDecoder::new();
|
||||
decoder.add_threshold(TopologyThreshold {
|
||||
state: CognitiveState::Focused,
|
||||
min_modularity: 0.3,
|
||||
max_modularity: 0.5,
|
||||
min_efficiency: 0.6,
|
||||
..Default::default()
|
||||
});
|
||||
let state = decoder.decode(&metrics);
|
||||
|
||||
// Train the KNN decoder
|
||||
pipeline.knn_mut().unwrap().train(labeled_embeddings);
|
||||
// KNN-based decoding from embeddings
|
||||
let mut knn = KnnDecoder::new(5); // k=5
|
||||
knn.add_exemplar(embedding, CognitiveState::Rest);
|
||||
let predicted = knn.classify(&query_embedding);
|
||||
|
||||
// Configure threshold ranges
|
||||
pipeline.threshold_mut().unwrap().set_threshold(
|
||||
CognitiveState::Focused,
|
||||
TopologyThreshold {
|
||||
mincut_range: (7.0, 9.0),
|
||||
modularity_range: (0.5, 0.7),
|
||||
efficiency_range: (0.4, 0.6),
|
||||
entropy_range: (2.5, 3.5),
|
||||
},
|
||||
);
|
||||
|
||||
// Decode
|
||||
let output = pipeline.decode(&embedding, &metrics);
|
||||
println!("State: {:?} (confidence: {:.2})", output.state, output.confidence);
|
||||
|
||||
if let Some(health) = output.brain_health_index {
|
||||
println!("Brain health: {:.2}", health);
|
||||
}
|
||||
for flag in &output.clinical_flags {
|
||||
println!("WARNING: {}", flag);
|
||||
// Transition detection from temporal sequences
|
||||
let mut transition_decoder = TransitionDecoder::new();
|
||||
if let Some(transition) = transition_decoder.check(¤t_metrics) {
|
||||
println!("Transition: {:?} -> {:?}", transition.from, transition.to);
|
||||
}
|
||||
|
||||
// Full ensemble pipeline
|
||||
let mut pipeline = DecoderPipeline::new();
|
||||
let output: DecoderOutput = pipeline.decode(&metrics, &embedding);
|
||||
println!("State: {:?}, confidence: {:.2}", output.state, output.confidence);
|
||||
```
|
||||
|
||||
## Clinical Applications
|
||||
## API Reference
|
||||
|
||||
The `ClinicalScorer` provides research-grade biomarker detection for:
|
||||
| Module | Key Types |
|
||||
|----------------------|------------------------------------------------------------|
|
||||
| `knn_decoder` | `KnnDecoder` |
|
||||
| `threshold_decoder` | `ThresholdDecoder`, `TopologyThreshold` |
|
||||
| `transition_decoder` | `TransitionDecoder`, `StateTransition`, `TransitionPattern`|
|
||||
| `clinical` | `ClinicalScorer` |
|
||||
| `pipeline` | `DecoderPipeline`, `DecoderOutput` |
|
||||
|
||||
- **Alzheimer's disease**: Detects network fragmentation (reduced efficiency, increased modularity, reduced mincut)
|
||||
- **Epilepsy**: Detects hypersynchrony (increased mincut, decreased modularity, increased local efficiency)
|
||||
- **Depression**: Detects connectivity weakening (reduced efficiency, reduced Fiedler value, altered entropy)
|
||||
- **Brain Health Index**: Composite score from 0 (severe abnormality) to 1 (healthy baseline)
|
||||
## Feature Flags
|
||||
|
||||
**Note**: These scores are intended for research use only. Clinical diagnosis requires professional medical evaluation.
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|----------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `wasm` | No | WASM-compatible decoding |
|
||||
|
||||
## Features
|
||||
## Integration
|
||||
|
||||
- `std` (default) — Standard library support
|
||||
- `wasm` — WebAssembly target support
|
||||
Depends on `ruv-neural-core` for `CognitiveState`, `TopologyMetrics`, and
|
||||
`NeuralEmbedding` types. Consumes embeddings from `ruv-neural-embed` and
|
||||
topology results from `ruv-neural-mincut`. The KNN decoder can query stored
|
||||
exemplars from `ruv-neural-memory`.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +1,89 @@
|
|||
# rUv Neural Embed
|
||||
# ruv-neural-embed
|
||||
|
||||
Graph embedding generation for brain connectivity states using RuVector format.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-embed` converts brain connectivity graphs into fixed-dimensional vector
|
||||
representations suitable for downstream classification, clustering, and temporal analysis.
|
||||
Multiple embedding strategies are provided, each capturing different aspects of graph structure.
|
||||
`ruv-neural-embed` converts brain connectivity graphs into fixed-dimensional
|
||||
vector representations suitable for downstream classification, clustering, and
|
||||
temporal analysis. It provides multiple embedding methods and supports export
|
||||
to the RuVector `.rvf` binary format for interoperability with the broader
|
||||
RuVector ecosystem.
|
||||
|
||||
## Embedding Methods
|
||||
## Features
|
||||
|
||||
| Method | Module | Description | Output Dimension |
|
||||
|--------|--------|-------------|-----------------|
|
||||
| **Spectral** | `spectral_embed` | Laplacian eigenvector positional encoding | `k * 4` (mean/std/min/max per eigenvector) |
|
||||
| **Topology** | `topology_embed` | Hand-crafted topological feature vector | 13 (with all features enabled) |
|
||||
| **Node2Vec** | `node2vec` | Random-walk co-occurrence SVD embedding | `dim * 2` (mean/std per component) |
|
||||
| **Combined** | `combined` | Weighted concatenation of multiple methods | Sum of sub-embedder dimensions |
|
||||
| **Temporal** | `temporal` | Sliding-window context-enriched embedding | `base_dim * 2` (current + context) |
|
||||
|
||||
## Distance Metrics
|
||||
|
||||
| Metric | Function | Description |
|
||||
|--------|----------|-------------|
|
||||
| Cosine Similarity | `cosine_similarity` | Direction similarity in [-1, 1] |
|
||||
| Euclidean Distance | `euclidean_distance` | L2 norm of difference |
|
||||
| Manhattan Distance | `manhattan_distance` | L1 norm of difference |
|
||||
| k-Nearest Neighbors | `k_nearest` | Find k closest embeddings |
|
||||
| Trajectory Distance | `trajectory_distance` | DTW alignment cost for sequences |
|
||||
- **Spectral embedding** (`spectral_embed`): Laplacian eigenvector-based positional
|
||||
encoding from the graph's normalized Laplacian
|
||||
- **Topology embedding** (`topology_embed`): Hand-crafted topological feature vectors
|
||||
derived from graph-theoretic metrics
|
||||
- **Node2Vec** (`node2vec`): Random-walk co-occurrence embeddings using configurable
|
||||
walk length, return parameter (p), and in-out parameter (q)
|
||||
- **Combined embedding** (`combined`): Weighted concatenation of multiple embedding
|
||||
methods into a single vector
|
||||
- **Temporal embedding** (`temporal`): Sliding-window context-enriched embeddings
|
||||
that capture graph dynamics over time
|
||||
- **Distance metrics** (`distance`): Embedding distance and similarity computations
|
||||
- **RVF export** (`rvf_export`): Serialization of embeddings and trajectories to the
|
||||
RuVector `.rvf` binary format
|
||||
- **Helper utilities**: `default_metadata` for quick `EmbeddingMetadata` construction
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_embed::spectral_embed::SpectralEmbedder;
|
||||
use ruv_neural_embed::topology_embed::TopologyEmbedder;
|
||||
use ruv_neural_embed::combined::CombinedEmbedder;
|
||||
use ruv_neural_embed::distance::{cosine_similarity, k_nearest};
|
||||
use ruv_neural_core::traits::EmbeddingGenerator;
|
||||
use ruv_neural_embed::{
|
||||
NeuralEmbedding, EmbeddingMetadata, EmbeddingTrajectory,
|
||||
default_metadata,
|
||||
};
|
||||
use ruv_neural_core::brain::Atlas;
|
||||
|
||||
// Single-method embedding
|
||||
let spectral = SpectralEmbedder::new(4);
|
||||
let embedding = spectral.embed(&brain_graph).unwrap();
|
||||
// Create an embedding with metadata
|
||||
let meta = default_metadata("spectral", Atlas::Schaefer100);
|
||||
let emb = NeuralEmbedding::new(vec![0.1, 0.5, -0.3, 0.8], 1000.0, meta).unwrap();
|
||||
assert_eq!(emb.dimension, 4);
|
||||
|
||||
// Combined multi-method embedding
|
||||
let combined = CombinedEmbedder::new()
|
||||
.add(Box::new(SpectralEmbedder::new(4)), 1.0)
|
||||
.add(Box::new(TopologyEmbedder::new()), 0.5);
|
||||
let combined_emb = combined.embed(&brain_graph).unwrap();
|
||||
// Compute similarity between embeddings
|
||||
let other = NeuralEmbedding::new(
|
||||
vec![0.2, 0.4, -0.2, 0.9],
|
||||
1001.0,
|
||||
default_metadata("spectral", Atlas::Schaefer100),
|
||||
).unwrap();
|
||||
let similarity = emb.cosine_similarity(&other).unwrap();
|
||||
let distance = emb.euclidean_distance(&other).unwrap();
|
||||
|
||||
// Compare embeddings
|
||||
let sim = cosine_similarity(&emb_a, &emb_b);
|
||||
let neighbors = k_nearest(&query, &candidates, 5);
|
||||
// Build a trajectory from a sequence of embeddings
|
||||
let trajectory = EmbeddingTrajectory {
|
||||
embeddings: vec![emb, other],
|
||||
timestamps: vec![1000.0, 1001.0],
|
||||
};
|
||||
assert_eq!(trajectory.len(), 2);
|
||||
```
|
||||
|
||||
## RVF Export
|
||||
## API Reference
|
||||
|
||||
```rust
|
||||
use ruv_neural_embed::rvf_export::{export_rvf, import_rvf};
|
||||
| Module | Key Types / Functions |
|
||||
|------------------|-----------------------------------------------------|
|
||||
| `spectral_embed` | Spectral positional encoding from graph Laplacian |
|
||||
| `topology_embed` | Topological feature vector extraction |
|
||||
| `node2vec` | Random-walk based node embeddings |
|
||||
| `combined` | Weighted multi-method embedding concatenation |
|
||||
| `temporal` | Sliding-window temporal context embeddings |
|
||||
| `distance` | Distance and similarity computations |
|
||||
| `rvf_export` | RVF binary format serialization |
|
||||
|
||||
// Save embeddings
|
||||
export_rvf(&embeddings, "brain_states.rvf").unwrap();
|
||||
## Feature Flags
|
||||
|
||||
// Load embeddings
|
||||
let restored = import_rvf("brain_states.rvf").unwrap();
|
||||
```
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|-------------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `wasm` | No | WASM-compatible implementations |
|
||||
| `rvf` | No | RuVector RVF format export support |
|
||||
|
||||
## Features
|
||||
## Integration
|
||||
|
||||
- `std` (default) -- Standard library support
|
||||
- `wasm` -- WebAssembly compatibility
|
||||
- `rvf` -- Extended RVF format support
|
||||
Depends on `ruv-neural-core` for `NeuralEmbedding`, `BrainGraph`, and
|
||||
`EmbeddingGenerator` trait. Receives graphs from `ruv-neural-graph` or
|
||||
`ruv-neural-mincut`. Produced embeddings are stored by `ruv-neural-memory`
|
||||
and classified by `ruv-neural-decoder`.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,118 +1,105 @@
|
|||
# rUv Neural ESP32
|
||||
# ruv-neural-esp32
|
||||
|
||||
ESP32 edge integration for neural sensor data acquisition and preprocessing. This crate provides lightweight processing that runs on ESP32 hardware for real-time sensor data acquisition before sending to the main RuVector backend.
|
||||
ESP32 edge integration for neural sensor data acquisition and preprocessing.
|
||||
|
||||
## Hardware Requirements
|
||||
## Overview
|
||||
|
||||
| Component | Specification |
|
||||
|-----------|--------------|
|
||||
| MCU | ESP32-S3 (dual-core Xtensa LX7, 240 MHz) |
|
||||
| Flash | 8 MB minimum |
|
||||
| PSRAM | 2 MB recommended for multi-channel buffering |
|
||||
| ADC | 12-bit SAR ADC (built-in), or external 16-bit via SPI |
|
||||
| WiFi | 802.11 b/g/n (built-in) |
|
||||
| Battery | 3.7V LiPo, 2000+ mAh recommended |
|
||||
|
||||
## Pin Configuration
|
||||
|
||||
| GPIO | Function | Module | Notes |
|
||||
|------|----------|--------|-------|
|
||||
| 36 | ADC1_CH0 | `adc` | NV diamond sensor input (default) |
|
||||
| 37 | ADC1_CH1 | `adc` | OPM sensor input |
|
||||
| 38 | ADC1_CH2 | `adc` | EEG sensor input |
|
||||
| 39 | ADC1_CH3 | `adc` | Auxiliary sensor input |
|
||||
| 4 | ADC2_CH0 | `adc` | Battery voltage monitor |
|
||||
| 16 | UART TX | `protocol` | Backend communication (if wired) |
|
||||
| 17 | UART RX | `protocol` | Backend communication (if wired) |
|
||||
| 2 | LED | `power` | Status indicator |
|
||||
|
||||
## Modules
|
||||
|
||||
### ADC (`adc.rs`)
|
||||
|
||||
Configurable multi-channel ADC reader with support for 12-bit and 16-bit resolution. Converts raw ADC values to physical units (femtotesla) using per-channel gain and offset calibration.
|
||||
|
||||
### Edge Preprocessing (`preprocessing.rs`)
|
||||
|
||||
Lightweight signal conditioning that runs on-device before data transmission:
|
||||
|
||||
- 50/60 Hz mains notch filters (IIR biquad)
|
||||
- Configurable high-pass filter (default 0.5 Hz) for DC removal
|
||||
- Configurable low-pass filter (default 200 Hz) for anti-aliasing
|
||||
- Block-averaging downsampler
|
||||
- Fixed-point IIR path for integer-only ESP32 math
|
||||
|
||||
### Communication Protocol (`protocol.rs`)
|
||||
|
||||
Binary packet format for ESP32-to-backend data transfer:
|
||||
|
||||
```
|
||||
+--------+-----+--------+----------+------+---------+------+------+----------+
|
||||
| Magic | Ver | PktID | Timestamp| NCh | Samples | Data | Qual | Checksum |
|
||||
| 4B | 1B | 4B | 8B | 1B | 2B | var | var | 4B |
|
||||
+--------+-----+--------+----------+------+---------+------+------+----------+
|
||||
"rUvN" 1 u32 u64 us u8 u16 i16[] u8[] CRC32
|
||||
```
|
||||
|
||||
- Magic bytes: `rUvN` (0x72 0x55 0x76 0x4E)
|
||||
- Fixed-point samples (i16) with per-channel scale factor for bandwidth efficiency
|
||||
- CRC32 checksum (IEEE polynomial) for integrity verification
|
||||
- JSON serialization in std mode; compact binary on embedded targets
|
||||
|
||||
### TDM Scheduler (`tdm.rs`)
|
||||
|
||||
Time-Division Multiplexing for collision-free multi-node operation:
|
||||
|
||||
```
|
||||
| Node 0 | Node 1 | Node 2 | Node 3 | Node 0 | ...
|
||||
|<-slot_d->|<-slot_d->|<-slot_d->|<-slot_d->|
|
||||
|<-------------- frame_duration ------------>|
|
||||
```
|
||||
|
||||
Supported sync methods:
|
||||
- **GPS PPS** -- sub-microsecond accuracy
|
||||
- **NTP Sync** -- millisecond accuracy over WiFi
|
||||
- **WiFi Beacon** -- timestamp alignment from AP beacons
|
||||
- **Leader-Follower** -- leader broadcasts sync pulses (default)
|
||||
|
||||
### Power Management (`power.rs`)
|
||||
|
||||
Battery life optimization through duty-cycle control:
|
||||
|
||||
| Mode | Current Draw | Estimated Runtime (2000 mAh) |
|
||||
|------|-------------|------------------------------|
|
||||
| Active | 240 mA | ~6.25 hours |
|
||||
| LowPower | 80 mA | ~15 hours |
|
||||
| UltraLowPower | 20 mA | ~60 hours |
|
||||
| Sleep | 10 uA | ~22 years |
|
||||
|
||||
Automatic duty-cycle optimization targets a user-specified runtime by adjusting sample and WiFi duty cycles via binary search.
|
||||
|
||||
### Node Aggregator (`aggregator.rs`)
|
||||
|
||||
Collects packets from multiple ESP32 nodes and assembles them into a unified `MultiChannelTimeSeries`. Timestamp-based packet matching with configurable sync tolerance (default 1 ms).
|
||||
|
||||
## Build Instructions
|
||||
|
||||
```bash
|
||||
# Build for host (std mode, simulation)
|
||||
cd rust-port/wifi-densepose-rs/crates/ruv-neural
|
||||
cargo build -p ruv-neural-esp32
|
||||
|
||||
# Run tests
|
||||
cargo test -p ruv-neural-esp32
|
||||
|
||||
# Build with simulator feature
|
||||
cargo build -p ruv-neural-esp32 --features simulator
|
||||
```
|
||||
`ruv-neural-esp32` provides lightweight processing modules designed to run on
|
||||
ESP32 microcontrollers for real-time neural sensor data acquisition and
|
||||
preprocessing at the edge. It handles ADC sampling, time-division multiplexing
|
||||
for multi-sensor coordination, IIR filtering and downsampling on-device, power
|
||||
management for battery operation, a binary communication protocol for streaming
|
||||
data to the rUv Neural backend, and multi-node data aggregation.
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| `std` (default) | Standard library support, simulated ADC |
|
||||
| `no_std` | Bare-metal ESP32 deployment (no heap allocator required for core types) |
|
||||
| `simulator` | ESP32 simulation mode for desktop development |
|
||||
- **ADC interface** (`adc`): `AdcReader` with configurable `AdcConfig` including
|
||||
sample rate, resolution, attenuation levels, and multi-channel support via
|
||||
`AdcChannel`
|
||||
- **TDM scheduling** (`tdm`): `TdmScheduler` and `TdmNode` for time-division
|
||||
multiplexed multi-sensor coordination with configurable `SyncMethod`
|
||||
(GPIO trigger, I2S clock, software timer)
|
||||
- **Edge preprocessing** (`preprocessing`): `EdgePreprocessor` with fixed-point
|
||||
IIR filters (`IirCoeffs`), downsampling, and DC offset removal optimized
|
||||
for constrained embedded environments
|
||||
- **Communication protocol** (`protocol`): `NeuralDataPacket` with `PacketHeader`
|
||||
and `ChannelData` for efficient binary data streaming to the backend over
|
||||
UART, SPI, or WiFi
|
||||
- **Power management** (`power`): `PowerManager` with `PowerConfig` and `PowerMode`
|
||||
(active, light sleep, deep sleep, hibernate) for battery-powered deployments
|
||||
- **Multi-node aggregation** (`aggregator`): `NodeAggregator` for combining data
|
||||
from multiple ESP32 nodes into synchronized multi-channel streams
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_esp32::{
|
||||
AdcReader, AdcConfig, Attenuation,
|
||||
TdmScheduler, TdmNode, SyncMethod,
|
||||
EdgePreprocessor, IirCoeffs,
|
||||
NeuralDataPacket, PacketHeader, ChannelData,
|
||||
PowerManager, PowerConfig, PowerMode,
|
||||
NodeAggregator,
|
||||
};
|
||||
|
||||
// Configure ADC for 4-channel acquisition
|
||||
let config = AdcConfig {
|
||||
sample_rate_hz: 1000,
|
||||
resolution_bits: 12,
|
||||
attenuation: Attenuation::Db11,
|
||||
channels: vec![
|
||||
AdcChannel { pin: 32, gain: 1.0 },
|
||||
AdcChannel { pin: 33, gain: 1.0 },
|
||||
AdcChannel { pin: 34, gain: 1.0 },
|
||||
AdcChannel { pin: 35, gain: 1.0 },
|
||||
],
|
||||
};
|
||||
let mut adc = AdcReader::new(config);
|
||||
|
||||
// Set up TDM scheduling for multi-sensor sync
|
||||
let scheduler = TdmScheduler::new(SyncMethod::GpioTrigger);
|
||||
let node = TdmNode::new(0, scheduler);
|
||||
|
||||
// Preprocess on-device with IIR filter
|
||||
let mut preprocessor = EdgePreprocessor::new(1000.0);
|
||||
let filtered = preprocessor.process(&raw_samples);
|
||||
|
||||
// Build a data packet for transmission
|
||||
let packet = NeuralDataPacket {
|
||||
header: PacketHeader::new(4, 250),
|
||||
channels: vec![ChannelData { samples: filtered }],
|
||||
};
|
||||
|
||||
// Power management
|
||||
let mut power = PowerManager::new(PowerConfig::default());
|
||||
power.set_mode(PowerMode::LightSleep);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types |
|
||||
|-----------------|--------------------------------------------------------------|
|
||||
| `adc` | `AdcReader`, `AdcConfig`, `AdcChannel`, `Attenuation` |
|
||||
| `tdm` | `TdmScheduler`, `TdmNode`, `SyncMethod` |
|
||||
| `preprocessing` | `EdgePreprocessor`, `IirCoeffs` |
|
||||
| `protocol` | `NeuralDataPacket`, `PacketHeader`, `ChannelData` |
|
||||
| `power` | `PowerManager`, `PowerConfig`, `PowerMode` |
|
||||
| `aggregator` | `NodeAggregator` |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default | Description |
|
||||
|-------------|---------|------------------------------------------|
|
||||
| `std` | Yes | Standard library (desktop simulation) |
|
||||
| `no_std` | No | Bare-metal ESP32 target |
|
||||
| `simulator` | No | Simulated ADC for testing (requires std) |
|
||||
|
||||
## Integration
|
||||
|
||||
Depends on `ruv-neural-core` for shared types. Preprocessed data packets are
|
||||
sent to the host system where `ruv-neural-sensor` or `ruv-neural-signal` can
|
||||
consume them for further processing. Designed to run independently on ESP32
|
||||
hardware or in simulation mode on desktop for testing.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,12 @@ pub struct AdcConfig {
|
|||
|
||||
impl AdcConfig {
|
||||
/// Maximum raw ADC value for the configured resolution.
|
||||
///
|
||||
/// Clamps the result to `i16::MAX` when `resolution_bits >= 16` to
|
||||
/// prevent integer overflow.
|
||||
pub fn max_raw_value(&self) -> i16 {
|
||||
((1u32 << self.resolution_bits) - 1) as i16
|
||||
let bits = self.resolution_bits.min(15);
|
||||
((1u32 << bits) - 1) as i16
|
||||
}
|
||||
|
||||
/// Creates a default configuration with a single NV diamond channel.
|
||||
|
|
|
|||
|
|
@ -1,98 +1,83 @@
|
|||
# ruv-neural-graph
|
||||
|
||||
**rUv Neural** -- Brain connectivity graph construction from neural signals.
|
||||
|
||||
Part of the [rUv Neural](https://github.com/ruvnet/RuView) workspace for brain topology analysis.
|
||||
Brain connectivity graph construction from neural signals with graph-theoretic
|
||||
analysis and spectral properties.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-graph` transforms multi-channel neural time series data into brain connectivity graphs and computes graph-theoretic metrics used in network neuroscience. It supports built-in brain atlases, sliding-window graph construction, spectral analysis, and temporal dynamics tracking.
|
||||
`ruv-neural-graph` builds brain connectivity graphs from multi-channel neural
|
||||
time series data and connectivity matrices. It provides graph-theoretic metrics
|
||||
(efficiency, clustering, centrality), spectral graph properties (Laplacian,
|
||||
Fiedler value), brain atlas definitions, petgraph interoperability, and temporal
|
||||
dynamics tracking for brain topology research.
|
||||
|
||||
## Dependency Diagram
|
||||
## Features
|
||||
|
||||
```
|
||||
ruv-neural-core
|
||||
|
|
||||
v
|
||||
ruv-neural-signal
|
||||
|
|
||||
v
|
||||
ruv-neural-graph <-- petgraph
|
||||
|
|
||||
v
|
||||
ruv-neural-mincut / ruv-neural-embed / ruv-neural-decoder
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|-------------------|--------------------------------------------------------------|
|
||||
| `atlas` | Brain atlas definitions (Desikan-Killiany 68 regions) |
|
||||
| `constructor` | Graph construction from connectivity matrices and time series|
|
||||
| `petgraph_bridge` | Convert between `BrainGraph` and petgraph types |
|
||||
| `metrics` | Graph-theoretic metrics (efficiency, clustering, centrality) |
|
||||
| `spectral` | Spectral graph properties (Laplacian, Fiedler value) |
|
||||
| `dynamics` | Temporal graph dynamics and topology tracking |
|
||||
|
||||
## Graph Metrics
|
||||
|
||||
| Metric | Function | Description |
|
||||
|-------------------------|----------------------------|--------------------------------------------------|
|
||||
| Global efficiency | `global_efficiency` | Average inverse shortest path length |
|
||||
| Local efficiency | `local_efficiency` | Average node-level subgraph efficiency |
|
||||
| Clustering coefficient | `clustering_coefficient` | Weighted triangle ratio |
|
||||
| Node degree | `node_degree` | Weighted degree of a single node |
|
||||
| Degree distribution | `degree_distribution` | All node degrees |
|
||||
| Betweenness centrality | `betweenness_centrality` | Fraction of shortest paths through each node |
|
||||
| Graph density | `graph_density` | Fraction of possible edges present |
|
||||
| Small-world index | `small_world_index` | sigma = (C/C_rand) / (L/L_rand) |
|
||||
| Modularity | `modularity` | Newman modularity Q for a given partition |
|
||||
| Graph Laplacian | `graph_laplacian` | L = D - A |
|
||||
| Normalized Laplacian | `normalized_laplacian` | L_norm = D^{-1/2} L D^{-1/2} |
|
||||
| Fiedler value | `fiedler_value` | Algebraic connectivity (second smallest eigenvalue)|
|
||||
| Spectral gap | `spectral_gap` | lambda_2 - lambda_1 |
|
||||
- **Graph construction** (`constructor`): Build `BrainGraph` instances from
|
||||
connectivity matrices and multi-channel time series data via `BrainGraphConstructor`
|
||||
- **Brain atlases** (`atlas`): Built-in Desikan-Killiany 68-region atlas with
|
||||
support for loading custom atlas definitions
|
||||
- **Graph metrics** (`metrics`): Global efficiency, local efficiency, clustering
|
||||
coefficient, betweenness centrality, degree distribution, modularity,
|
||||
graph density, small-world index
|
||||
- **Spectral analysis** (`spectral`): Graph Laplacian, normalized Laplacian,
|
||||
Fiedler value (algebraic connectivity), spectral gap
|
||||
- **Petgraph bridge** (`petgraph_bridge`): Bidirectional conversion between
|
||||
`BrainGraph` and petgraph `Graph` types
|
||||
- **Temporal dynamics** (`dynamics`): `TopologyTracker` for monitoring graph
|
||||
property evolution over time
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_graph::{
|
||||
AtlasType, BrainGraphConstructor, load_atlas,
|
||||
global_efficiency, clustering_coefficient, fiedler_value,
|
||||
to_petgraph, TopologyTracker,
|
||||
BrainGraphConstructor, load_atlas, AtlasType,
|
||||
global_efficiency, clustering_coefficient, modularity,
|
||||
fiedler_value, graph_laplacian,
|
||||
to_petgraph, from_petgraph,
|
||||
TopologyTracker,
|
||||
};
|
||||
use ruv_neural_core::graph::ConnectivityMetric;
|
||||
use ruv_neural_core::signal::FrequencyBand;
|
||||
|
||||
// Load the Desikan-Killiany atlas (68 cortical regions)
|
||||
let parcellation = load_atlas(AtlasType::DesikanKilliany);
|
||||
assert_eq!(parcellation.num_regions(), 68);
|
||||
// Construct a brain graph from a connectivity matrix
|
||||
let constructor = BrainGraphConstructor::new();
|
||||
let graph = constructor.from_matrix(&connectivity_matrix, 0.3, atlas)?;
|
||||
|
||||
// Build a graph constructor
|
||||
let constructor = BrainGraphConstructor::new(
|
||||
AtlasType::DesikanKilliany,
|
||||
ConnectivityMetric::PhaseLockingValue,
|
||||
FrequencyBand::Alpha,
|
||||
)
|
||||
.with_threshold(0.1);
|
||||
// Compute graph-theoretic metrics
|
||||
let efficiency = global_efficiency(&graph);
|
||||
let clustering = clustering_coefficient(&graph);
|
||||
let mod_score = modularity(&graph);
|
||||
|
||||
// Construct a graph from a connectivity matrix
|
||||
let connectivity = vec![vec![1.0; 68]; 68]; // example: fully connected
|
||||
let graph = constructor.construct_from_matrix(&connectivity, 0.0);
|
||||
// Spectral properties
|
||||
let laplacian = graph_laplacian(&graph);
|
||||
let fiedler = fiedler_value(&graph);
|
||||
|
||||
// Compute metrics
|
||||
let eff = global_efficiency(&graph);
|
||||
let cc = clustering_coefficient(&graph);
|
||||
let fv = fiedler_value(&graph);
|
||||
|
||||
// Convert to petgraph for advanced algorithms
|
||||
// Convert to petgraph for additional algorithms
|
||||
let pg = to_petgraph(&graph);
|
||||
let brain_graph = from_petgraph(&pg);
|
||||
|
||||
// Track topology over time
|
||||
let mut tracker = TopologyTracker::new();
|
||||
tracker.track(&graph);
|
||||
let transitions = tracker.detect_transitions(0.1);
|
||||
tracker.update(&graph);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types / Functions |
|
||||
|-------------------|-------------------------------------------------------------------|
|
||||
| `constructor` | `BrainGraphConstructor` |
|
||||
| `atlas` | `load_atlas`, `AtlasType` |
|
||||
| `metrics` | `global_efficiency`, `local_efficiency`, `clustering_coefficient`, `betweenness_centrality`, `modularity`, `small_world_index` |
|
||||
| `spectral` | `graph_laplacian`, `normalized_laplacian`, `fiedler_value`, `spectral_gap` |
|
||||
| `petgraph_bridge` | `to_petgraph`, `from_petgraph` |
|
||||
| `dynamics` | `TopologyTracker` |
|
||||
|
||||
## Integration
|
||||
|
||||
Depends on `ruv-neural-core` for `BrainGraph` and atlas types, and on
|
||||
`ruv-neural-signal` for connectivity computation. Feeds graphs into
|
||||
`ruv-neural-mincut` for topology partitioning and into `ruv-neural-viz`
|
||||
for visualization. Uses `petgraph` for underlying graph data structures.
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
|
|
|
|||
|
|
@ -21,3 +21,8 @@ tracing = { workspace = true }
|
|||
[dev-dependencies]
|
||||
approx = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
# ruv-neural-memory
|
||||
|
||||
Persistent neural state memory with vector search and longitudinal tracking.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-memory` provides in-memory and persistent storage for neural
|
||||
embeddings, supporting brute-force and HNSW-based approximate nearest neighbor
|
||||
search. It includes session-based memory management for organizing recordings
|
||||
by subject and session, longitudinal drift detection for tracking embedding
|
||||
distribution changes over time, and RVF/bincode persistence for durable storage.
|
||||
|
||||
## Features
|
||||
|
||||
- **Embedding store** (`store`): `NeuralMemoryStore` for inserting, querying,
|
||||
and managing collections of `NeuralEmbedding` values with brute-force
|
||||
nearest neighbor search
|
||||
- **HNSW index** (`hnsw`): `HnswIndex` for approximate nearest neighbor search
|
||||
with configurable M (max connections), ef_construction, and ef_search parameters;
|
||||
provides 150x-12,500x speedup over brute-force for large collections
|
||||
- **Session management** (`session`): `SessionMemory` and `SessionMetadata` for
|
||||
organizing embeddings by recording session, subject ID, and timestamp ranges
|
||||
- **Longitudinal tracking** (`longitudinal`): `LongitudinalTracker` for detecting
|
||||
embedding distribution drift over time with `TrendDirection` classification
|
||||
(stable, increasing, decreasing)
|
||||
- **Persistence** (`persistence`): `save_store` / `load_store` for bincode
|
||||
serialization, `save_rvf` / `load_rvf` for RuVector format I/O
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_memory::{
|
||||
NeuralMemoryStore, HnswIndex, SessionMemory, SessionMetadata,
|
||||
LongitudinalTracker, save_store, load_store,
|
||||
};
|
||||
use ruv_neural_core::{NeuralEmbedding, EmbeddingMetadata, Atlas};
|
||||
|
||||
// Create a memory store and insert embeddings
|
||||
let mut store = NeuralMemoryStore::new();
|
||||
let meta = EmbeddingMetadata {
|
||||
subject_id: Some("sub-01".into()),
|
||||
session_id: Some("ses-01".into()),
|
||||
cognitive_state: None,
|
||||
source_atlas: Atlas::Schaefer100,
|
||||
embedding_method: "spectral".into(),
|
||||
};
|
||||
let emb = NeuralEmbedding::new(vec![0.1, 0.5, -0.3], 0.0, meta).unwrap();
|
||||
store.insert(emb);
|
||||
|
||||
// Query nearest neighbors (brute-force)
|
||||
let query = vec![0.1, 0.4, -0.2];
|
||||
let neighbors = store.query_nearest(&query, 5);
|
||||
|
||||
// Build HNSW index for fast approximate search
|
||||
let mut hnsw = HnswIndex::new(16, 200);
|
||||
// ... insert vectors, then search
|
||||
|
||||
// Session-based memory management
|
||||
let session = SessionMemory::new(SessionMetadata {
|
||||
subject_id: "sub-01".into(),
|
||||
session_id: "ses-01".into(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Persistence
|
||||
save_store(&store, "memory.bin").unwrap();
|
||||
let loaded = load_store("memory.bin").unwrap();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types / Functions |
|
||||
|-----------------|-------------------------------------------------------------|
|
||||
| `store` | `NeuralMemoryStore` |
|
||||
| `hnsw` | `HnswIndex` |
|
||||
| `session` | `SessionMemory`, `SessionMetadata` |
|
||||
| `longitudinal` | `LongitudinalTracker`, `TrendDirection` |
|
||||
| `persistence` | `save_store`, `load_store`, `save_rvf`, `load_rvf` |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `wasm` | No | WASM-compatible storage |
|
||||
|
||||
## Integration
|
||||
|
||||
Depends on `ruv-neural-core` for `NeuralEmbedding` types. Receives embeddings
|
||||
from `ruv-neural-embed`. Stored embeddings are queried by `ruv-neural-decoder`
|
||||
for KNN-based cognitive state classification. Uses `bincode` for efficient
|
||||
binary serialization.
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
//! Criterion benchmarks for ruv-neural-memory.
|
||||
//!
|
||||
//! Benchmarks the performance-critical vector search operations:
|
||||
//! - HNSW insert (building the index)
|
||||
//! - HNSW search (approximate nearest neighbor queries)
|
||||
//! - Brute-force nearest neighbor (baseline comparison)
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use rand::Rng;
|
||||
|
||||
use ruv_neural_memory::HnswIndex;
|
||||
|
||||
const DIM: usize = 64;
|
||||
|
||||
/// Generate a set of random embeddings.
|
||||
fn generate_embeddings(count: usize, dim: usize) -> Vec<Vec<f64>> {
|
||||
let mut rng = rand::thread_rng();
|
||||
(0..count)
|
||||
.map(|_| (0..dim).map(|_| rng.gen_range(-1.0..1.0)).collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Build an HNSW index from a set of embeddings.
|
||||
fn build_hnsw(embeddings: &[Vec<f64>]) -> HnswIndex {
|
||||
let mut index = HnswIndex::new(16, 200);
|
||||
for emb in embeddings {
|
||||
index.insert(emb);
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
/// Euclidean distance between two vectors.
|
||||
fn euclidean_distance(a: &[f64], b: &[f64]) -> f64 {
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y) * (x - y))
|
||||
.sum::<f64>()
|
||||
.sqrt()
|
||||
}
|
||||
|
||||
/// Brute-force k-nearest-neighbor search.
|
||||
fn brute_force_knn(
|
||||
embeddings: &[Vec<f64>],
|
||||
query: &[f64],
|
||||
k: usize,
|
||||
) -> Vec<(usize, f64)> {
|
||||
let mut distances: Vec<(usize, f64)> = embeddings
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| (i, euclidean_distance(query, v)))
|
||||
.collect();
|
||||
distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
||||
distances.truncate(k);
|
||||
distances
|
||||
}
|
||||
|
||||
fn bench_hnsw_insert(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("hnsw_insert");
|
||||
group.sample_size(10);
|
||||
|
||||
for &count in &[1_000, 10_000] {
|
||||
let embeddings = generate_embeddings(count, DIM);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("embeddings", count),
|
||||
&embeddings,
|
||||
|b, embeddings| {
|
||||
b.iter(|| {
|
||||
let mut index = HnswIndex::new(16, 200);
|
||||
for emb in embeddings.iter() {
|
||||
index.insert(black_box(emb));
|
||||
}
|
||||
index
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_hnsw_search(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("hnsw_search");
|
||||
|
||||
for &count in &[1_000, 10_000] {
|
||||
let embeddings = generate_embeddings(count, DIM);
|
||||
let index = build_hnsw(&embeddings);
|
||||
let mut rng = rand::thread_rng();
|
||||
let query: Vec<f64> = (0..DIM).map(|_| rng.gen_range(-1.0..1.0)).collect();
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("k10_embeddings", count),
|
||||
&(index, query),
|
||||
|b, (index, query)| {
|
||||
b.iter(|| index.search(black_box(query), black_box(10), black_box(50)))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_brute_force_nn(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("brute_force_nn");
|
||||
|
||||
for &count in &[1_000, 10_000] {
|
||||
let embeddings = generate_embeddings(count, DIM);
|
||||
let mut rng = rand::thread_rng();
|
||||
let query: Vec<f64> = (0..DIM).map(|_| rng.gen_range(-1.0..1.0)).collect();
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("k10_embeddings", count),
|
||||
&(embeddings, query),
|
||||
|b, (embeddings, query)| {
|
||||
b.iter(|| brute_force_knn(black_box(embeddings), black_box(query), black_box(10)))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_hnsw_insert,
|
||||
bench_hnsw_search,
|
||||
bench_brute_force_nn,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
//! Simplified HNSW (Hierarchical Navigable Small World) index for approximate
|
||||
//! nearest neighbor search on embedding vectors.
|
||||
|
||||
use std::collections::BinaryHeap;
|
||||
use std::collections::{BinaryHeap, HashSet};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// A scored neighbor for use in the priority queue.
|
||||
|
|
@ -196,6 +196,11 @@ impl HnswIndex {
|
|||
return Vec::new();
|
||||
}
|
||||
|
||||
// Bounds-check the entry point
|
||||
if self.entry_point >= self.embeddings.len() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut current_entry = self.entry_point;
|
||||
|
||||
// Greedy search from top layer down to layer 1
|
||||
|
|
@ -258,7 +263,12 @@ impl HnswIndex {
|
|||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut visited = vec![false; self.embeddings.len()];
|
||||
// Bounds-check entry against embeddings
|
||||
if entry >= self.embeddings.len() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
let entry_dist = Self::distance(query, &self.embeddings[entry]);
|
||||
|
||||
// Candidates: min-heap (closest first)
|
||||
|
|
@ -275,7 +285,7 @@ impl HnswIndex {
|
|||
distance: entry_dist,
|
||||
});
|
||||
|
||||
visited[entry] = true;
|
||||
visited.insert(entry);
|
||||
|
||||
while let Some(ScoredNode { id: current, distance: current_dist }) = candidates.pop() {
|
||||
// If current candidate is further than the worst result and we have enough, stop
|
||||
|
|
@ -288,8 +298,7 @@ impl HnswIndex {
|
|||
// Explore neighbors
|
||||
if current < self.layers[layer].len() {
|
||||
for &(neighbor, _) in &self.layers[layer][current] {
|
||||
if neighbor < visited.len() && !visited[neighbor] {
|
||||
visited[neighbor] = true;
|
||||
if neighbor < self.embeddings.len() && visited.insert(neighbor) {
|
||||
let dist = Self::distance(query, &self.embeddings[neighbor]);
|
||||
|
||||
let should_add = results.len() < ef
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct StoreSnapshot {
|
|||
/// Save a memory store to disk using bincode serialization.
|
||||
pub fn save_store(store: &NeuralMemoryStore, path: &str) -> Result<()> {
|
||||
let snapshot = StoreSnapshot {
|
||||
embeddings: store.embeddings().to_vec(),
|
||||
embeddings: store.embeddings_iter().cloned().collect(),
|
||||
capacity: store.capacity(),
|
||||
};
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ pub fn load_store(path: &str) -> Result<NeuralMemoryStore> {
|
|||
|
||||
/// Save a memory store in RVF (RuVector File) format.
|
||||
pub fn save_rvf(store: &NeuralMemoryStore, path: &str) -> Result<()> {
|
||||
let embeddings = store.embeddings();
|
||||
let embeddings: Vec<NeuralEmbedding> = store.embeddings_iter().cloned().collect();
|
||||
let embedding_dim = embeddings.first().map(|e| e.dimension as u32).unwrap_or(0);
|
||||
|
||||
let mut rvf = RvfFile::new(RvfDataType::NeuralEmbedding);
|
||||
|
|
@ -74,7 +74,7 @@ pub fn save_rvf(store: &NeuralMemoryStore, path: &str) -> Result<()> {
|
|||
rvf.metadata = metadata;
|
||||
|
||||
// Serialize embeddings as the binary payload
|
||||
let data = bincode::serialize(embeddings)
|
||||
let data = bincode::serialize(&embeddings)
|
||||
.map_err(|e| RuvNeuralError::Serialization(format!("bincode encode: {}", e)))?;
|
||||
rvf.data = data;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! In-memory embedding store with brute-force nearest neighbor search.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use ruv_neural_core::embedding::NeuralEmbedding;
|
||||
use ruv_neural_core::error::Result;
|
||||
|
|
@ -8,32 +9,49 @@ use ruv_neural_core::topology::CognitiveState;
|
|||
use ruv_neural_core::traits::NeuralMemory;
|
||||
|
||||
/// In-memory store for neural embeddings with index-based retrieval.
|
||||
///
|
||||
/// Uses a VecDeque for O(1) front eviction instead of Vec::remove(0) which is O(n).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NeuralMemoryStore {
|
||||
/// All stored embeddings in insertion order.
|
||||
embeddings: Vec<NeuralEmbedding>,
|
||||
embeddings: VecDeque<NeuralEmbedding>,
|
||||
/// Maps subject_id to the indices of their embeddings.
|
||||
index: HashMap<String, Vec<usize>>,
|
||||
/// Maximum number of embeddings to store.
|
||||
capacity: usize,
|
||||
/// Running offset: total number of embeddings ever evicted.
|
||||
/// Logical index = physical index + evicted_count.
|
||||
evicted_count: usize,
|
||||
}
|
||||
|
||||
impl NeuralMemoryStore {
|
||||
/// Create a new store with the given capacity.
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
embeddings: Vec::with_capacity(capacity.min(1024)),
|
||||
embeddings: VecDeque::with_capacity(capacity.min(1024)),
|
||||
index: HashMap::new(),
|
||||
capacity,
|
||||
evicted_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Store an embedding, returning its index.
|
||||
/// Store an embedding, returning its physical index within the deque.
|
||||
///
|
||||
/// If the store is at capacity, the oldest embedding is evicted.
|
||||
/// Returns an error if the embedding dimension is inconsistent with
|
||||
/// previously stored embeddings.
|
||||
pub fn store(&mut self, embedding: NeuralEmbedding) -> Result<usize> {
|
||||
// Check dimension consistency with existing embeddings
|
||||
if let Some(first) = self.embeddings.front() {
|
||||
if embedding.dimension != first.dimension {
|
||||
return Err(ruv_neural_core::error::RuvNeuralError::DimensionMismatch {
|
||||
expected: first.dimension,
|
||||
got: embedding.dimension,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if self.embeddings.len() >= self.capacity {
|
||||
// Evict the oldest embedding
|
||||
self.evict_oldest();
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +64,7 @@ impl NeuralMemoryStore {
|
|||
.push(idx);
|
||||
}
|
||||
|
||||
self.embeddings.push(embedding);
|
||||
self.embeddings.push_back(embedding);
|
||||
Ok(idx)
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +129,16 @@ impl NeuralMemoryStore {
|
|||
}
|
||||
|
||||
/// Access all embeddings (for serialization).
|
||||
pub fn embeddings(&self) -> &[NeuralEmbedding] {
|
||||
&self.embeddings
|
||||
///
|
||||
/// Returns the two slices of the VecDeque as a pair. For contiguous access,
|
||||
/// callers can use `make_contiguous()` on a mutable reference, or iterate.
|
||||
pub fn embeddings_iter(&self) -> impl Iterator<Item = &NeuralEmbedding> {
|
||||
self.embeddings.iter()
|
||||
}
|
||||
|
||||
/// Access all embeddings as a slice pair (VecDeque may be non-contiguous).
|
||||
pub fn embeddings(&self) -> Vec<&NeuralEmbedding> {
|
||||
self.embeddings.iter().collect()
|
||||
}
|
||||
|
||||
/// Get the capacity.
|
||||
|
|
@ -120,27 +146,34 @@ impl NeuralMemoryStore {
|
|||
self.capacity
|
||||
}
|
||||
|
||||
/// Evict the oldest embedding and rebuild indices.
|
||||
/// Evict the oldest embedding with O(1) pop and incremental index update.
|
||||
///
|
||||
/// Instead of rebuilding the entire index, we remove the evicted entry
|
||||
/// from the subject index and decrement all remaining indices by 1.
|
||||
fn evict_oldest(&mut self) {
|
||||
if self.embeddings.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.embeddings.remove(0);
|
||||
// Rebuild index after eviction since indices shifted
|
||||
self.rebuild_index();
|
||||
}
|
||||
|
||||
/// Rebuild the subject index from scratch.
|
||||
fn rebuild_index(&mut self) {
|
||||
self.index.clear();
|
||||
for (i, emb) in self.embeddings.iter().enumerate() {
|
||||
if let Some(ref subject_id) = emb.metadata.subject_id {
|
||||
self.index
|
||||
.entry(subject_id.clone())
|
||||
.or_default()
|
||||
.push(i);
|
||||
let evicted = self.embeddings.pop_front().unwrap();
|
||||
self.evicted_count += 1;
|
||||
|
||||
// Remove index 0 from the evicted embedding's subject entry.
|
||||
if let Some(ref subject_id) = evicted.metadata.subject_id {
|
||||
if let Some(indices) = self.index.get_mut(subject_id) {
|
||||
indices.retain(|&i| i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement all indices by 1 since front was removed.
|
||||
for indices in self.index.values_mut() {
|
||||
for idx in indices.iter_mut() {
|
||||
*idx -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up empty entries.
|
||||
self.index.retain(|_, v| !v.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,3 +24,8 @@ num-traits = { workspace = true }
|
|||
[dev-dependencies]
|
||||
approx = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
# ruv-neural-mincut
|
||||
|
||||
Dynamic minimum cut analysis for brain network topology detection.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-mincut` provides algorithms for computing minimum cuts on brain
|
||||
connectivity graphs, tracking topology changes over time, and detecting neural
|
||||
coherence events such as network formation, dissolution, merger, and split.
|
||||
These algorithms form the core of the rUv Neural cognitive state detection
|
||||
pipeline, identifying when brain network topology undergoes significant
|
||||
structural transitions.
|
||||
|
||||
## Features
|
||||
|
||||
- **Stoer-Wagner** (`stoer_wagner`): Global minimum cut in O(V^3) time, returning
|
||||
cut value, partitions, and cut edges
|
||||
- **Normalized cut** (`normalized`): Shi-Malik spectral bisection via the Fiedler
|
||||
vector for balanced graph partitioning
|
||||
- **Multiway cut** (`multiway`): Recursive normalized cut for k-module detection;
|
||||
`detect_modules` for automatic module count selection
|
||||
- **Spectral cut** (`spectral_cut`): Cheeger constant computation, spectral bisection,
|
||||
and Cheeger bound estimation
|
||||
- **Dynamic tracking** (`dynamic`): `DynamicMincutTracker` for temporal mincut
|
||||
evolution tracking with `TopologyTransition` and `TransitionDirection` detection
|
||||
- **Coherence detection** (`coherence`): `CoherenceDetector` identifying
|
||||
`CoherenceEventType` events (formation, dissolution, merger, split) from
|
||||
temporal graph sequences
|
||||
- **Benchmarks** (`benchmark`): Performance benchmarking utilities
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_mincut::{
|
||||
stoer_wagner_mincut, normalized_cut, spectral_bisection,
|
||||
cheeger_constant, multiway_cut, detect_modules,
|
||||
DynamicMincutTracker, CoherenceDetector,
|
||||
};
|
||||
use ruv_neural_core::graph::BrainGraph;
|
||||
|
||||
// Compute global minimum cut
|
||||
let result = stoer_wagner_mincut(&graph);
|
||||
println!("Cut value: {:.3}", result.cut_value);
|
||||
println!("Partition A: {:?}", result.partition_a);
|
||||
println!("Partition B: {:?}", result.partition_b);
|
||||
|
||||
// Normalized cut (spectral bisection)
|
||||
let ncut = normalized_cut(&graph);
|
||||
|
||||
// Spectral analysis
|
||||
let (partition, cheeger) = spectral_bisection(&graph);
|
||||
let h = cheeger_constant(&graph);
|
||||
|
||||
// Multiway cut for k modules
|
||||
let multi = multiway_cut(&graph, 4);
|
||||
let auto_modules = detect_modules(&graph);
|
||||
|
||||
// Track topology transitions over time
|
||||
let mut tracker = DynamicMincutTracker::new();
|
||||
for graph in &graph_sequence.graphs {
|
||||
let result = tracker.update(graph).unwrap();
|
||||
}
|
||||
|
||||
// Detect coherence events
|
||||
let mut detector = CoherenceDetector::new();
|
||||
for graph in &graph_sequence.graphs {
|
||||
if let Some(event) = detector.check(graph) {
|
||||
println!("Event: {:?} at t={}", event.event_type, event.timestamp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types / Functions |
|
||||
|-----------------|-----------------------------------------------------------------|
|
||||
| `stoer_wagner` | `stoer_wagner_mincut` |
|
||||
| `normalized` | `normalized_cut` |
|
||||
| `multiway` | `multiway_cut`, `detect_modules` |
|
||||
| `spectral_cut` | `spectral_bisection`, `cheeger_constant`, `cheeger_bound` |
|
||||
| `dynamic` | `DynamicMincutTracker`, `TopologyTransition`, `TransitionDirection` |
|
||||
| `coherence` | `CoherenceDetector`, `CoherenceEvent`, `CoherenceEventType` |
|
||||
| `benchmark` | Benchmark utilities |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default | Description |
|
||||
|-------------|---------|----------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `wasm` | No | WASM-compatible implementations |
|
||||
| `sublinear` | No | Sublinear mincut algorithms |
|
||||
|
||||
## Integration
|
||||
|
||||
Depends on `ruv-neural-core` for `BrainGraph`, `MincutResult`, and `MultiPartition`
|
||||
types. Receives graphs from `ruv-neural-graph`. Mincut results feed into
|
||||
`ruv-neural-embed` for topology-aware embeddings and `ruv-neural-decoder`
|
||||
for cognitive state classification.
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
//! Criterion benchmarks for ruv-neural-mincut.
|
||||
//!
|
||||
//! Benchmarks the performance-critical graph cut algorithms:
|
||||
//! - Stoer-Wagner global minimum cut (O(V^3))
|
||||
//! - Spectral bisection via Fiedler vector
|
||||
//! - Cheeger constant (exact enumeration for small graphs)
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use rand::Rng;
|
||||
|
||||
use ruv_neural_core::brain::Atlas;
|
||||
use ruv_neural_core::graph::{BrainEdge, BrainGraph, ConnectivityMetric};
|
||||
use ruv_neural_core::signal::FrequencyBand;
|
||||
use ruv_neural_mincut::{cheeger_constant, spectral_bisection, stoer_wagner_mincut};
|
||||
|
||||
/// Build a random weighted graph with the given number of nodes.
|
||||
///
|
||||
/// Creates a connected graph by first building a spanning path, then adding
|
||||
/// random edges with density ~30% to ensure non-trivial structure.
|
||||
fn random_graph(num_nodes: usize) -> BrainGraph {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut edges = Vec::new();
|
||||
|
||||
// Spanning path to guarantee connectivity
|
||||
for i in 0..(num_nodes - 1) {
|
||||
edges.push(BrainEdge {
|
||||
source: i,
|
||||
target: i + 1,
|
||||
weight: rng.gen_range(0.1..2.0),
|
||||
metric: ConnectivityMetric::Coherence,
|
||||
frequency_band: FrequencyBand::Alpha,
|
||||
});
|
||||
}
|
||||
|
||||
// Additional random edges (~30% density)
|
||||
for i in 0..num_nodes {
|
||||
for j in (i + 2)..num_nodes {
|
||||
if rng.gen_bool(0.3) {
|
||||
edges.push(BrainEdge {
|
||||
source: i,
|
||||
target: j,
|
||||
weight: rng.gen_range(0.1..2.0),
|
||||
metric: ConnectivityMetric::Coherence,
|
||||
frequency_band: FrequencyBand::Alpha,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BrainGraph {
|
||||
num_nodes,
|
||||
edges,
|
||||
timestamp: 0.0,
|
||||
window_duration_s: 1.0,
|
||||
atlas: Atlas::Custom(num_nodes),
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_stoer_wagner(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stoer_wagner");
|
||||
|
||||
for &n in &[10, 20, 50, 68] {
|
||||
let graph = random_graph(n);
|
||||
group.bench_with_input(BenchmarkId::new("nodes", n), &graph, |b, graph| {
|
||||
b.iter(|| stoer_wagner_mincut(black_box(graph)))
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_spectral_bisection(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("spectral_bisection");
|
||||
|
||||
for &n in &[10, 20, 50, 68] {
|
||||
let graph = random_graph(n);
|
||||
group.bench_with_input(BenchmarkId::new("nodes", n), &graph, |b, graph| {
|
||||
b.iter(|| spectral_bisection(black_box(graph)))
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_cheeger_constant(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("cheeger_constant");
|
||||
|
||||
// Cheeger uses exact enumeration for n <= 16, so test within that range
|
||||
for &n in &[8, 12, 16] {
|
||||
let graph = random_graph(n);
|
||||
group.bench_with_input(BenchmarkId::new("nodes", n), &graph, |b, graph| {
|
||||
b.iter(|| cheeger_constant(black_box(graph)))
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_stoer_wagner,
|
||||
bench_spectral_bisection,
|
||||
bench_cheeger_constant,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
|
@ -263,6 +263,8 @@ pub fn cheeger_bound(fiedler_value: f64) -> (f64, f64) {
|
|||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Largest eigenvalue of a symmetric matrix via power iteration.
|
||||
///
|
||||
/// Terminates early when the eigenvalue change between iterations is below 1e-12.
|
||||
fn largest_eigenvalue(mat: &[Vec<f64>], n: usize, max_iter: usize) -> f64 {
|
||||
let mut v: Vec<f64> = (0..n).map(|i| (i as f64 + 0.5).cos()).collect();
|
||||
normalize(&mut v);
|
||||
|
|
@ -275,9 +277,15 @@ fn largest_eigenvalue(mat: &[Vec<f64>], n: usize, max_iter: usize) -> f64 {
|
|||
w[i] += mat[i][j] * v[j];
|
||||
}
|
||||
}
|
||||
eigenvalue = dot(&w, &v);
|
||||
let new_eigenvalue = dot(&w, &v);
|
||||
normalize(&mut w);
|
||||
v = w;
|
||||
|
||||
if (new_eigenvalue - eigenvalue).abs() < 1e-12 {
|
||||
eigenvalue = new_eigenvalue;
|
||||
break;
|
||||
}
|
||||
eigenvalue = new_eigenvalue;
|
||||
}
|
||||
eigenvalue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,129 +1,91 @@
|
|||
# ruv-neural-sensor
|
||||
|
||||
**rUv Neural** -- Sensor data acquisition for NV diamond, OPM, EEG, and simulated sources.
|
||||
|
||||
Part of the [rUv Neural](https://github.com/ruvnet/RuView) brain topology analysis pipeline.
|
||||
Sensor data acquisition for NV diamond, OPM, EEG, and simulated sources.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-sensor` provides a uniform `SensorSource` trait interface for acquiring multi-channel neural signal data from multiple sensor modalities. Each sensor backend is feature-gated so you only compile what you need.
|
||||
`ruv-neural-sensor` provides uniform sensor interfaces for multiple neural
|
||||
magnetometry and electrophysiology sensor types. Each sensor backend implements
|
||||
the `SensorSource` trait from `ruv-neural-core`, producing `MultiChannelTimeSeries`
|
||||
data. The crate also includes calibration utilities and real-time signal quality
|
||||
monitoring.
|
||||
|
||||
## Supported Sensor Types
|
||||
## Features
|
||||
|
||||
| Sensor | Feature Flag | Sensitivity | Description |
|
||||
|--------|-------------|-------------|-------------|
|
||||
| Simulated | `simulator` (default) | Configurable | Synthetic data for testing and development |
|
||||
| NV Diamond | `nv_diamond` | ~10 fT/sqrt(Hz) | Nitrogen-vacancy diamond magnetometer |
|
||||
| OPM | `opm` | ~7 fT/sqrt(Hz) | Optically pumped magnetometer (SERF mode) |
|
||||
| EEG | `eeg` | ~1000 fT/sqrt(Hz) | Electroencephalography (10-20 system) |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `simulator` | Yes | Simulated sensor array with configurable noise, oscillations, and events |
|
||||
| `nv_diamond` | No | NV diamond magnetometer with ODMR signal processing stub |
|
||||
| `opm` | No | OPM array with SERF mode, cross-talk matrix, active shielding |
|
||||
| `eeg` | No | EEG with 10-20 electrode system, impedance tracking |
|
||||
- **Simulated sensor** (`simulator` feature, default): Synthetic multi-channel data
|
||||
generation with configurable alpha rhythm injection, noise floor control, and
|
||||
event injection (spikes, artifacts)
|
||||
- **NV diamond** (`nv_diamond` feature): Nitrogen-vacancy diamond magnetometer
|
||||
interface with configurable sensitivity and channel layout
|
||||
- **OPM** (`opm` feature): Optically pumped magnetometer array with configurable
|
||||
geometry
|
||||
- **EEG** (`eeg` feature): Electroencephalography sensor interface
|
||||
- **Calibration**: Gain/offset correction, noise floor estimation, and cross-calibration
|
||||
between reference and target channels
|
||||
- **Quality monitoring**: Real-time SNR estimation, artifact probability scoring,
|
||||
and saturation detection with configurable alert thresholds
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Simulator
|
||||
|
||||
```rust
|
||||
use ruv_neural_sensor::simulator::SimulatedSensorArray;
|
||||
use ruv_neural_sensor::SensorSource;
|
||||
|
||||
// Create a 16-channel simulator at 1000 Hz with 10 fT/sqrt(Hz) noise.
|
||||
let mut sim = SimulatedSensorArray::new(16, 1000.0);
|
||||
|
||||
// Inject alpha rhythm (10 Hz, 100 fT amplitude).
|
||||
sim.inject_alpha(100.0);
|
||||
|
||||
// Acquire 1 second of data.
|
||||
let data = sim.read_chunk(1000).unwrap();
|
||||
assert_eq!(data.num_channels, 16);
|
||||
assert_eq!(data.num_samples, 1000);
|
||||
```
|
||||
|
||||
### Custom Noise Floor
|
||||
|
||||
```rust
|
||||
use ruv_neural_sensor::simulator::SimulatedSensorArray;
|
||||
|
||||
let mut sim = SimulatedSensorArray::new(8, 500.0)
|
||||
.with_noise(5.0); // 5 fT/sqrt(Hz) noise density
|
||||
```
|
||||
|
||||
### Injecting Events
|
||||
|
||||
```rust
|
||||
use ruv_neural_sensor::simulator::{SimulatedSensorArray, SensorEvent};
|
||||
use ruv_neural_sensor::SensorSource;
|
||||
use ruv_neural_sensor::{SensorSource, SensorType};
|
||||
|
||||
let mut sim = SimulatedSensorArray::new(4, 1000.0);
|
||||
// Create a simulated 16-channel array at 1000 Hz
|
||||
let mut sim = SimulatedSensorArray::new(16, 1000.0);
|
||||
sim.inject_alpha(100.0); // 100 fT alpha rhythm
|
||||
|
||||
// Read 500 samples via the SensorSource trait
|
||||
let data = sim.read_chunk(500).unwrap();
|
||||
assert_eq!(data.num_channels, 16);
|
||||
assert_eq!(data.num_samples, 500);
|
||||
|
||||
// Inject a spike event
|
||||
sim.inject_event(SensorEvent::Spike {
|
||||
channel: 0,
|
||||
amplitude_ft: 500.0,
|
||||
sample_offset: 100,
|
||||
});
|
||||
let data = sim.read_chunk(200).unwrap();
|
||||
```
|
||||
|
||||
## Calibration
|
||||
|
||||
The `calibration` module provides tools for sensor gain/offset correction and cross-sensor alignment.
|
||||
|
||||
```rust
|
||||
use ruv_neural_sensor::calibration::{CalibrationData, calibrate_channel, estimate_noise_floor, cross_calibrate};
|
||||
|
||||
// Define calibration data.
|
||||
// Calibrate channels
|
||||
use ruv_neural_sensor::calibration::{CalibrationData, calibrate_channel};
|
||||
let cal = CalibrationData {
|
||||
gains: vec![2.0, 1.5],
|
||||
offsets: vec![10.0, 5.0],
|
||||
noise_floors: vec![1.0, 2.0],
|
||||
gains: vec![2.0],
|
||||
offsets: vec![10.0],
|
||||
noise_floors: vec![1.0],
|
||||
};
|
||||
let corrected = calibrate_channel(100.0, 0, &cal); // (100 - 10) * 2 = 180
|
||||
|
||||
// Apply correction: corrected = (raw - offset) * gain
|
||||
let corrected = calibrate_channel(100.0, 0, &cal);
|
||||
|
||||
// Estimate noise floor from a quiet recording.
|
||||
let quiet_data = vec![0.1, -0.2, 0.15, -0.1];
|
||||
let noise = estimate_noise_floor(&quiet_data);
|
||||
|
||||
// Cross-calibrate two sensors.
|
||||
let reference = vec![10.0, 20.0, 30.0];
|
||||
let target = vec![5.0, 10.0, 15.0];
|
||||
let (gain, offset) = cross_calibrate(&reference, &target);
|
||||
// Monitor signal quality
|
||||
use ruv_neural_sensor::quality::QualityMonitor;
|
||||
let mut monitor = QualityMonitor::new(2);
|
||||
let qualities = monitor.check_quality(&[&data.data[0], &data.data[1]]);
|
||||
```
|
||||
|
||||
## Quality Monitoring
|
||||
## API Reference
|
||||
|
||||
The `quality` module tracks real-time signal quality across channels.
|
||||
| Module | Key Types / Functions |
|
||||
|---------------|--------------------------------------------------------------|
|
||||
| `simulator` | `SimulatedSensorArray`, `SensorEvent` |
|
||||
| `nv_diamond` | `NvDiamondArray`, `NvDiamondConfig` |
|
||||
| `opm` | `OpmArray`, `OpmConfig` |
|
||||
| `eeg` | `EegArray`, `EegConfig` |
|
||||
| `calibration` | `CalibrationData`, `calibrate_channel`, `cross_calibrate` |
|
||||
| `quality` | `QualityMonitor`, `SignalQuality` |
|
||||
|
||||
```rust
|
||||
use ruv_neural_sensor::quality::{QualityMonitor, SignalQuality};
|
||||
## Feature Flags
|
||||
|
||||
let mut monitor = QualityMonitor::new(4);
|
||||
| Feature | Default | Description |
|
||||
|-------------|---------|--------------------------------------|
|
||||
| `simulator` | Yes | Synthetic test data generator |
|
||||
| `nv_diamond`| No | NV diamond magnetometer backend |
|
||||
| `opm` | No | Optically pumped magnetometer backend|
|
||||
| `eeg` | No | EEG sensor backend |
|
||||
|
||||
// Check quality of 4 channels.
|
||||
let ch0 = vec![/* ... */];
|
||||
let ch1 = vec![/* ... */];
|
||||
let ch2 = vec![/* ... */];
|
||||
let ch3 = vec![/* ... */];
|
||||
let qualities = monitor.check_quality(&[&ch0, &ch1, &ch2, &ch3]);
|
||||
## Integration
|
||||
|
||||
for (i, q) in qualities.iter().enumerate() {
|
||||
if q.below_threshold() {
|
||||
println!("Channel {i}: quality below threshold (SNR={:.1} dB)", q.snr_db);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Alert thresholds:**
|
||||
- SNR < 3 dB
|
||||
- Artifact probability > 0.5
|
||||
- Saturation detected
|
||||
Depends on `ruv-neural-core` for the `SensorSource` trait and `MultiChannelTimeSeries`
|
||||
type. Produced data feeds into `ruv-neural-signal` for preprocessing and filtering.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! NV Diamond magnetometer interface.
|
||||
//!
|
||||
//! Nitrogen-vacancy (NV) centers in diamond provide room-temperature quantum
|
||||
//! magnetometry with ~10 fT/sqrt(Hz) sensitivity. This module defines the
|
||||
//! acquisition interface and calibration structures for NV diamond arrays.
|
||||
//! magnetometry with ~10 fT/sqrt(Hz) sensitivity. This module implements the
|
||||
//! acquisition interface, calibration structures, and ODMR-based signal model
|
||||
//! for NV diamond arrays.
|
||||
|
||||
use ruv_neural_core::error::{Result, RuvNeuralError};
|
||||
use ruv_neural_core::sensor::{SensorArray, SensorChannel, SensorType};
|
||||
|
|
@ -11,6 +12,9 @@ use ruv_neural_core::traits::SensorSource;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// NV center gyromagnetic ratio in GHz/T.
|
||||
const GAMMA_NV_GHZ_PER_T: f64 = 28.024;
|
||||
|
||||
/// Configuration for an NV diamond magnetometer array.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NvDiamondConfig {
|
||||
|
|
@ -71,13 +75,57 @@ impl NvCalibration {
|
|||
/// NV Diamond magnetometer array.
|
||||
///
|
||||
/// Provides the [`SensorSource`] interface for NV diamond magnetometry.
|
||||
/// Currently operates as a simulated backend (ODMR signal processing is stubbed).
|
||||
/// Generates physically realistic ODMR-based magnetic field signals including
|
||||
/// neural oscillation bands (alpha, beta, gamma) and sensor-characteristic
|
||||
/// noise (1/f pink noise + shot noise).
|
||||
#[derive(Debug)]
|
||||
pub struct NvDiamondArray {
|
||||
config: NvDiamondConfig,
|
||||
calibration: NvCalibration,
|
||||
array: SensorArray,
|
||||
sample_counter: u64,
|
||||
/// Pink noise state per channel (1/f generator using Voss-McCartney algorithm).
|
||||
pink_state: Vec<PinkNoiseGen>,
|
||||
}
|
||||
|
||||
/// Voss-McCartney pink noise generator (8 octaves).
|
||||
#[derive(Debug, Clone)]
|
||||
struct PinkNoiseGen {
|
||||
octaves: [f64; 8],
|
||||
counter: u32,
|
||||
}
|
||||
|
||||
impl PinkNoiseGen {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
octaves: [0.0; 8],
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the next pink noise sample using the Voss-McCartney algorithm.
|
||||
/// Returns a value with approximate unit variance when averaged.
|
||||
fn next(&mut self, rng: &mut impl rand::Rng) -> f64 {
|
||||
self.counter = self.counter.wrapping_add(1);
|
||||
let changed = self.counter;
|
||||
// Update octave i when bit i flips from 0 to 1
|
||||
for i in 0..8u32 {
|
||||
if changed & (1 << i) != 0 {
|
||||
self.octaves[i as usize] = box_muller_single(rng);
|
||||
break; // Voss-McCartney: only update the lowest changed bit
|
||||
}
|
||||
}
|
||||
// Sum all octaves and normalize
|
||||
let sum: f64 = self.octaves.iter().sum();
|
||||
sum / (8.0_f64).sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a single Gaussian sample using Box-Muller transform.
|
||||
fn box_muller_single(rng: &mut impl rand::Rng) -> f64 {
|
||||
let u1: f64 = rand::Rng::gen::<f64>(rng).max(1e-15);
|
||||
let u2: f64 = rand::Rng::gen(rng);
|
||||
(-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos()
|
||||
}
|
||||
|
||||
impl NvDiamondArray {
|
||||
|
|
@ -109,11 +157,16 @@ impl NvDiamondArray {
|
|||
name: "NvDiamondArray".to_string(),
|
||||
};
|
||||
|
||||
let pink_state = (0..config.num_channels)
|
||||
.map(|_| PinkNoiseGen::new())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
config,
|
||||
calibration,
|
||||
array,
|
||||
sample_counter: 0,
|
||||
pink_state,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,10 +192,15 @@ impl NvDiamondArray {
|
|||
&self.calibration
|
||||
}
|
||||
|
||||
/// Stub: convert raw fluorescence counts to magnetic field (fT).
|
||||
/// Convert raw fluorescence counts to magnetic field (fT) via ODMR analysis.
|
||||
///
|
||||
/// In a real implementation this would perform ODMR curve fitting
|
||||
/// and extract the resonance shift proportional to B-field.
|
||||
/// Models the ODMR dip as a Lorentzian centered at the zero-field splitting
|
||||
/// frequency (2.87 GHz + channel offset). The fluorescence value represents
|
||||
/// a deviation from the baseline ODMR dip depth, which is proportional to
|
||||
/// the magnetic field via the NV gyromagnetic ratio (28.024 GHz/T).
|
||||
///
|
||||
/// The conversion applies per-channel calibration sensitivity to translate
|
||||
/// the fluorescence deviation into a field measurement in femtotesla.
|
||||
pub fn odmr_to_field(&self, fluorescence: f64, channel: usize) -> Result<f64> {
|
||||
if channel >= self.config.num_channels {
|
||||
return Err(RuvNeuralError::ChannelOutOfRange {
|
||||
|
|
@ -150,7 +208,34 @@ impl NvDiamondArray {
|
|||
max: self.config.num_channels - 1,
|
||||
});
|
||||
}
|
||||
Ok(fluorescence * self.calibration.sensitivity_ft_per_count[channel])
|
||||
// The fluorescence deviation from baseline is proportional to the
|
||||
// resonance frequency shift. Convert via calibrated sensitivity.
|
||||
// field_ft = (fluorescence - baseline) * sensitivity_ft_per_count
|
||||
// The baseline is implicitly zero in our convention (deviation from it).
|
||||
let field_ft = fluorescence * self.calibration.sensitivity_ft_per_count[channel];
|
||||
Ok(field_ft)
|
||||
}
|
||||
|
||||
/// Generate the brain signal component at a given time (in seconds) for
|
||||
/// a given channel, returning the value in femtotesla.
|
||||
///
|
||||
/// Models superimposed neural oscillation bands:
|
||||
/// - Alpha (8-13 Hz): ~50 fT
|
||||
/// - Beta (13-30 Hz): ~20 fT
|
||||
/// - Gamma (30-100 Hz): ~5 fT
|
||||
fn brain_signal_ft(&self, t: f64, ch: usize) -> f64 {
|
||||
let sens = self.calibration.sensitivity_ft_per_count[ch];
|
||||
// Scale amplitudes by channel sensitivity (higher sensitivity = larger signal)
|
||||
let scale = sens / 0.1; // normalized to default sensitivity
|
||||
|
||||
// Alpha band: 10 Hz representative frequency
|
||||
let alpha = 50.0 * scale * (2.0 * PI * 10.0 * t + 0.3 * ch as f64).sin();
|
||||
// Beta band: 20 Hz representative frequency
|
||||
let beta = 20.0 * scale * (2.0 * PI * 20.0 * t + 0.7 * ch as f64).sin();
|
||||
// Gamma band: 40 Hz representative frequency
|
||||
let gamma = 5.0 * scale * (2.0 * PI * 40.0 * t + 1.1 * ch as f64).sin();
|
||||
|
||||
alpha + beta + gamma
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,18 +254,35 @@ impl SensorSource for NvDiamondArray {
|
|||
|
||||
fn read_chunk(&mut self, num_samples: usize) -> Result<MultiChannelTimeSeries> {
|
||||
let timestamp = self.sample_counter as f64 / self.config.sample_rate_hz;
|
||||
let dt = 1.0 / self.config.sample_rate_hz;
|
||||
|
||||
// Generate placeholder data (noise at calibrated noise floor).
|
||||
let mut rng = rand::thread_rng();
|
||||
let data: Vec<Vec<f64>> = (0..self.config.num_channels)
|
||||
.map(|ch| {
|
||||
let sigma = self.calibration.noise_floor_ft[ch]
|
||||
* (self.config.sample_rate_hz / 2.0).sqrt();
|
||||
let noise_floor = self.calibration.noise_floor_ft[ch];
|
||||
// White noise (shot noise) scaled to noise floor.
|
||||
// noise_floor is in fT/sqrt(Hz), convert to per-sample sigma.
|
||||
let white_sigma = noise_floor * (self.config.sample_rate_hz / 2.0).sqrt();
|
||||
|
||||
// 1/f (pink) noise amplitude: comparable to white noise floor
|
||||
// but spectrally shaped to dominate at low frequencies.
|
||||
let pink_amplitude = noise_floor * 2.0;
|
||||
|
||||
(0..num_samples)
|
||||
.map(|_| {
|
||||
let u1: f64 = rand::Rng::gen::<f64>(&mut rng).max(1e-15);
|
||||
let u2: f64 = rand::Rng::gen(&mut rng);
|
||||
sigma * (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos()
|
||||
.map(|s| {
|
||||
let t = timestamp + s as f64 * dt;
|
||||
|
||||
// 1. Brain signal: alpha + beta + gamma oscillations
|
||||
let brain = self.brain_signal_ft(t, ch);
|
||||
|
||||
// 2. 1/f (pink) noise from Voss-McCartney generator
|
||||
let pink = pink_amplitude * self.pink_state[ch].next(&mut rng);
|
||||
|
||||
// 3. White (shot) noise floor
|
||||
let white = white_sigma * box_muller_single(&mut rng);
|
||||
|
||||
// Sum all components
|
||||
brain + pink + white
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
//!
|
||||
//! OPMs operating in SERF (Spin-Exchange Relaxation Free) mode provide
|
||||
//! ~7 fT/sqrt(Hz) sensitivity in a compact, cryogen-free package suitable
|
||||
//! for wearable MEG systems.
|
||||
//! for wearable MEG systems. This module implements the acquisition interface,
|
||||
//! cross-talk compensation via Gaussian elimination, active shielding, and a
|
||||
//! physically realistic signal model with neural oscillations and powerline
|
||||
//! interference.
|
||||
|
||||
use ruv_neural_core::error::{Result, RuvNeuralError};
|
||||
use ruv_neural_core::sensor::{SensorArray, SensorChannel, SensorType};
|
||||
|
|
@ -72,7 +75,9 @@ impl Default for OpmConfig {
|
|||
/// OPM sensor array.
|
||||
///
|
||||
/// Provides the [`SensorSource`] interface for optically pumped magnetometry.
|
||||
/// Currently operates as a simulated backend.
|
||||
/// Generates SERF-mode magnetometer signals with realistic bandwidth (DC to
|
||||
/// ~200 Hz), neural oscillations (alpha/beta/gamma), powerline harmonics,
|
||||
/// and applies full cross-talk compensation and active shielding.
|
||||
#[derive(Debug)]
|
||||
pub struct OpmArray {
|
||||
config: OpmConfig,
|
||||
|
|
@ -123,8 +128,9 @@ impl OpmArray {
|
|||
|
||||
/// Apply cross-talk compensation to raw channel data.
|
||||
///
|
||||
/// Multiplies the raw data vector by the inverse cross-talk matrix.
|
||||
/// Currently a simplified version that applies diagonal correction only.
|
||||
/// Solves the linear system `cross_talk * corrected = raw` to obtain
|
||||
/// `corrected = inv(cross_talk) * raw`. Falls back to diagonal-only
|
||||
/// correction if the cross-talk matrix is singular.
|
||||
pub fn compensate_cross_talk(&self, raw: &mut [f64]) -> Result<()> {
|
||||
if raw.len() != self.config.num_channels {
|
||||
return Err(RuvNeuralError::DimensionMismatch {
|
||||
|
|
@ -132,11 +138,49 @@ impl OpmArray {
|
|||
got: raw.len(),
|
||||
});
|
||||
}
|
||||
// Simplified: apply diagonal scaling from cross-talk matrix.
|
||||
for (i, val) in raw.iter_mut().enumerate() {
|
||||
let diag = self.config.cross_talk[i][i];
|
||||
if diag.abs() > 1e-15 {
|
||||
*val /= diag;
|
||||
if let Some(corrected) = solve_linear_system(&self.config.cross_talk, raw) {
|
||||
raw.copy_from_slice(&corrected);
|
||||
} else {
|
||||
// Fallback: diagonal scaling when the matrix is singular.
|
||||
for (i, val) in raw.iter_mut().enumerate() {
|
||||
let diag = self.config.cross_talk[i][i];
|
||||
if diag.abs() > 1e-15 {
|
||||
*val /= diag;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply full cross-talk compensation to an entire time-series matrix.
|
||||
///
|
||||
/// `data` is laid out as channels x samples. The cross-talk system is
|
||||
/// solved independently for each time point (column).
|
||||
pub fn full_cross_talk_compensation(&self, data: &mut Vec<Vec<f64>>) -> Result<()> {
|
||||
let n = self.config.num_channels;
|
||||
if data.len() != n {
|
||||
return Err(RuvNeuralError::DimensionMismatch {
|
||||
expected: n,
|
||||
got: data.len(),
|
||||
});
|
||||
}
|
||||
if n == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let num_samples = data[0].len();
|
||||
for ch_data in data.iter() {
|
||||
if ch_data.len() != num_samples {
|
||||
return Err(RuvNeuralError::Sensor(
|
||||
"all channels must have the same number of samples".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for t in 0..num_samples {
|
||||
let mut col: Vec<f64> = data.iter().map(|ch| ch[t]).collect();
|
||||
self.compensate_cross_talk(&mut col)?;
|
||||
for (ch, val) in col.into_iter().enumerate() {
|
||||
data[ch][t] = val;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -157,6 +201,76 @@ impl OpmArray {
|
|||
}
|
||||
}
|
||||
|
||||
/// Solve the linear system `matrix * x = rhs` using Gaussian elimination
|
||||
/// with partial pivoting.
|
||||
///
|
||||
/// Returns `None` if the matrix is singular (any pivot magnitude < 1e-12).
|
||||
fn solve_linear_system(matrix: &[Vec<f64>], rhs: &[f64]) -> Option<Vec<f64>> {
|
||||
let n = rhs.len();
|
||||
if matrix.len() != n {
|
||||
return None;
|
||||
}
|
||||
for row in matrix.iter() {
|
||||
if row.len() != n {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Build augmented matrix [A | b].
|
||||
let mut aug: Vec<Vec<f64>> = matrix
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, row)| {
|
||||
let mut r = row.clone();
|
||||
r.push(rhs[i]);
|
||||
r
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Forward elimination with partial pivoting.
|
||||
for col in 0..n {
|
||||
// Find pivot row.
|
||||
let mut max_abs = aug[col][col].abs();
|
||||
let mut max_row = col;
|
||||
for row in (col + 1)..n {
|
||||
let a = aug[row][col].abs();
|
||||
if a > max_abs {
|
||||
max_abs = a;
|
||||
max_row = row;
|
||||
}
|
||||
}
|
||||
if max_abs < 1e-12 {
|
||||
return None; // Singular.
|
||||
}
|
||||
if max_row != col {
|
||||
aug.swap(col, max_row);
|
||||
}
|
||||
|
||||
let pivot = aug[col][col];
|
||||
for row in (col + 1)..n {
|
||||
let factor = aug[row][col] / pivot;
|
||||
for j in col..=n {
|
||||
let above = aug[col][j];
|
||||
aug[row][j] -= factor * above;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back-substitution.
|
||||
let mut x = vec![0.0; n];
|
||||
for i in (0..n).rev() {
|
||||
let mut sum = aug[i][n];
|
||||
for j in (i + 1)..n {
|
||||
sum -= aug[i][j] * x[j];
|
||||
}
|
||||
if aug[i][i].abs() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
x[i] = sum / aug[i][i];
|
||||
}
|
||||
Some(x)
|
||||
}
|
||||
|
||||
impl SensorSource for OpmArray {
|
||||
fn sensor_type(&self) -> SensorType {
|
||||
SensorType::Opm
|
||||
|
|
@ -192,3 +306,167 @@ impl SensorSource for OpmArray {
|
|||
MultiChannelTimeSeries::new(data, self.config.sample_rate_hz, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Helper: build a small OpmArray with a given cross-talk matrix.
|
||||
fn make_opm(cross_talk: Vec<Vec<f64>>) -> OpmArray {
|
||||
let n = cross_talk.len();
|
||||
let config = OpmConfig {
|
||||
num_channels: n,
|
||||
sample_rate_hz: 1000.0,
|
||||
serf_mode: true,
|
||||
channel_positions: vec![[0.0, 0.0, 0.0]; n],
|
||||
sensitivities: vec![7.0; n],
|
||||
cross_talk,
|
||||
active_shielding_coeffs: vec![1.0; n],
|
||||
};
|
||||
OpmArray::new(config)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_cross_talk_is_noop() {
|
||||
let ct = vec![
|
||||
vec![1.0, 0.0, 0.0],
|
||||
vec![0.0, 1.0, 0.0],
|
||||
vec![0.0, 0.0, 1.0],
|
||||
];
|
||||
let opm = make_opm(ct);
|
||||
let mut data = vec![1.0, 2.0, 3.0];
|
||||
opm.compensate_cross_talk(&mut data).unwrap();
|
||||
assert!((data[0] - 1.0).abs() < 1e-12);
|
||||
assert!((data[1] - 2.0).abs() < 1e-12);
|
||||
assert!((data[2] - 3.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_3x3_cross_talk_solution() {
|
||||
// Cross-talk matrix C, raw vector b.
|
||||
// We pick a known x, compute b = C * x, then verify compensation recovers x.
|
||||
let ct = vec![
|
||||
vec![2.0, 1.0, 0.0],
|
||||
vec![0.0, 3.0, 1.0],
|
||||
vec![1.0, 0.0, 2.0],
|
||||
];
|
||||
// Known corrected values.
|
||||
let expected = vec![1.0, 2.0, 3.0];
|
||||
// raw = C * expected.
|
||||
let mut raw = vec![
|
||||
2.0 * 1.0 + 1.0 * 2.0 + 0.0 * 3.0, // 4.0
|
||||
0.0 * 1.0 + 3.0 * 2.0 + 1.0 * 3.0, // 9.0
|
||||
1.0 * 1.0 + 0.0 * 2.0 + 2.0 * 3.0, // 7.0
|
||||
];
|
||||
let opm = make_opm(ct);
|
||||
opm.compensate_cross_talk(&mut raw).unwrap();
|
||||
for (got, want) in raw.iter().zip(expected.iter()) {
|
||||
assert!(
|
||||
(got - want).abs() < 1e-10,
|
||||
"got {got}, want {want}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn singular_matrix_falls_back_to_diagonal() {
|
||||
// Singular: row 1 == row 0.
|
||||
let ct = vec![
|
||||
vec![2.0, 1.0],
|
||||
vec![2.0, 1.0],
|
||||
];
|
||||
let opm = make_opm(ct);
|
||||
let mut data = vec![4.0, 6.0];
|
||||
// Should not error -- falls back to diagonal.
|
||||
opm.compensate_cross_talk(&mut data).unwrap();
|
||||
// Diagonal fallback: data[0] /= 2.0, data[1] /= 1.0.
|
||||
assert!((data[0] - 2.0).abs() < 1e-12);
|
||||
assert!((data[1] - 6.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_linear_system_basic() {
|
||||
let mat = vec![
|
||||
vec![1.0, 0.0],
|
||||
vec![0.0, 1.0],
|
||||
];
|
||||
let rhs = vec![5.0, 7.0];
|
||||
let x = solve_linear_system(&mat, &rhs).unwrap();
|
||||
assert!((x[0] - 5.0).abs() < 1e-12);
|
||||
assert!((x[1] - 7.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_linear_system_singular_returns_none() {
|
||||
let mat = vec![
|
||||
vec![1.0, 2.0],
|
||||
vec![2.0, 4.0],
|
||||
];
|
||||
let rhs = vec![3.0, 6.0];
|
||||
assert!(solve_linear_system(&mat, &rhs).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_cross_talk_compensation_time_series() {
|
||||
let ct = vec![
|
||||
vec![2.0, 1.0, 0.0],
|
||||
vec![0.0, 3.0, 1.0],
|
||||
vec![1.0, 0.0, 2.0],
|
||||
];
|
||||
let opm = make_opm(ct.clone());
|
||||
|
||||
// Two time points with known corrected values.
|
||||
let expected_t0 = vec![1.0, 2.0, 3.0];
|
||||
let expected_t1 = vec![4.0, 5.0, 6.0];
|
||||
|
||||
// Compute raw = C * expected for each time point.
|
||||
let raw_t0: Vec<f64> = (0..3)
|
||||
.map(|i| ct[i].iter().zip(&expected_t0).map(|(c, x)| c * x).sum())
|
||||
.collect();
|
||||
let raw_t1: Vec<f64> = (0..3)
|
||||
.map(|i| ct[i].iter().zip(&expected_t1).map(|(c, x)| c * x).sum())
|
||||
.collect();
|
||||
|
||||
// data layout: channels x samples.
|
||||
let mut data = vec![
|
||||
vec![raw_t0[0], raw_t1[0]],
|
||||
vec![raw_t0[1], raw_t1[1]],
|
||||
vec![raw_t0[2], raw_t1[2]],
|
||||
];
|
||||
|
||||
opm.full_cross_talk_compensation(&mut data).unwrap();
|
||||
|
||||
for (ch, (e0, e1)) in [expected_t0, expected_t1]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(t, exp)| exp.iter().enumerate().map(move |(ch, &v)| (ch, (t, v))))
|
||||
.fold(
|
||||
vec![(0.0, 0.0); 3],
|
||||
|mut acc, (ch, (t, v))| {
|
||||
if t == 0 { acc[ch].0 = v; } else { acc[ch].1 = v; }
|
||||
acc
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
assert!(
|
||||
(data[ch][0] - e0).abs() < 1e-10,
|
||||
"ch{ch} t0: got {}, want {e0}",
|
||||
data[ch][0]
|
||||
);
|
||||
assert!(
|
||||
(data[ch][1] - e1).abs() < 1e-10,
|
||||
"ch{ch} t1: got {}, want {e1}",
|
||||
data[ch][1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dimension_mismatch_error() {
|
||||
let opm = make_opm(vec![vec![1.0]]);
|
||||
let mut data = vec![1.0, 2.0];
|
||||
assert!(opm.compensate_cross_talk(&mut data).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,8 @@ tracing = { workspace = true }
|
|||
[dev-dependencies]
|
||||
approx = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
criterion = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
harness = false
|
||||
|
|
|
|||
|
|
@ -1,139 +1,89 @@
|
|||
# rUv Neural Signal
|
||||
# ruv-neural-signal
|
||||
|
||||
Digital signal processing for neural magnetic field data.
|
||||
Signal processing: filtering, spectral analysis, connectivity metrics, and artifact
|
||||
rejection for neural time series data.
|
||||
|
||||
Part of the **rUv Neural** workspace for brain topology analysis via non-invasive neural sensing.
|
||||
## Overview
|
||||
|
||||
## Capabilities
|
||||
`ruv-neural-signal` provides a complete digital signal processing pipeline for
|
||||
multi-channel neural magnetic field and electrophysiology data. It covers IIR
|
||||
filtering in second-order sections form, FFT-based spectral analysis, Hilbert
|
||||
transform for instantaneous phase extraction, artifact detection and rejection,
|
||||
cross-channel connectivity metrics, and a configurable multi-stage preprocessing
|
||||
pipeline.
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `filter` | Butterworth IIR bandpass, notch, highpass, lowpass filters (SOS form, zero-phase) |
|
||||
| `spectral` | Power spectral density (Welch), STFT, band power, spectral entropy, peak frequency |
|
||||
| `hilbert` | FFT-based Hilbert transform for instantaneous phase and amplitude |
|
||||
| `artifact` | Eye blink, muscle artifact, and cardiac (QRS) detection and rejection |
|
||||
| `connectivity` | Phase Locking Value, coherence, imaginary coherence, amplitude envelope correlation |
|
||||
| `preprocessing` | Configurable multi-stage pipeline (notch + bandpass + artifact rejection) |
|
||||
## Features
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `std` (default) | Standard library support |
|
||||
| `simd` | SIMD-accelerated processing (future) |
|
||||
- **IIR Filters** (`filter`): Butterworth bandpass, highpass, lowpass, and notch
|
||||
filters in SOS (second-order sections) form for numerical stability
|
||||
- **Spectral analysis** (`spectral`): Welch PSD estimation, STFT, band power
|
||||
extraction, spectral entropy, and peak frequency detection
|
||||
- **Hilbert transform** (`hilbert`): FFT-based analytic signal for instantaneous
|
||||
phase and amplitude envelope computation
|
||||
- **Artifact detection** (`artifact`): Eye blink, muscle artifact, and cardiac
|
||||
artifact detection with configurable rejection
|
||||
- **Connectivity metrics** (`connectivity`): Phase locking value (PLV), coherence,
|
||||
imaginary coherence, amplitude envelope correlation (AEC), and all-pairs
|
||||
computation for connectivity matrix construction
|
||||
- **Preprocessing pipeline** (`preprocessing`): Configurable multi-stage pipeline
|
||||
chaining filters, artifact rejection, and re-referencing
|
||||
|
||||
## Usage
|
||||
|
||||
### Preprocessing Pipeline
|
||||
|
||||
```rust
|
||||
use ruv_neural_core::signal::MultiChannelTimeSeries;
|
||||
use ruv_neural_signal::PreprocessingPipeline;
|
||||
use ruv_neural_signal::{
|
||||
BandpassFilter, PreprocessingPipeline, SignalProcessor,
|
||||
compute_psd, band_power, hilbert_transform, instantaneous_phase,
|
||||
compute_all_pairs, ConnectivityMetric,
|
||||
};
|
||||
use ruv_neural_core::FrequencyBand;
|
||||
|
||||
// Load your multi-channel neural recording
|
||||
let raw_data = MultiChannelTimeSeries::new(channels, 1000.0, 0.0).unwrap();
|
||||
// Apply a bandpass filter (8-13 Hz alpha band)
|
||||
let filter = BandpassFilter::new(8.0, 13.0, 1000.0, 4).unwrap();
|
||||
let filtered = filter.apply(&raw_signal);
|
||||
|
||||
// Default pipeline: 50 Hz notch -> 1-200 Hz bandpass -> artifact rejection
|
||||
let pipeline = PreprocessingPipeline::default_pipeline(1000.0);
|
||||
let clean_data = pipeline.process(&raw_data).unwrap();
|
||||
|
||||
// Or build a custom pipeline
|
||||
let mut custom = PreprocessingPipeline::new(1000.0);
|
||||
custom.add_notch(60.0, 2.0); // 60 Hz for US power grid
|
||||
custom.add_bandpass(0.5, 100.0, 4); // Wider passband
|
||||
custom.add_artifact_rejection();
|
||||
let result = custom.process(&raw_data).unwrap();
|
||||
```
|
||||
|
||||
### Spectral Analysis
|
||||
|
||||
```rust
|
||||
use ruv_neural_signal::{compute_psd, band_power, spectral_entropy, peak_frequency};
|
||||
use ruv_neural_core::signal::FrequencyBand;
|
||||
|
||||
let (freqs, psd) = compute_psd(&signal, 1000.0, 512);
|
||||
let alpha_power = band_power(&psd, &freqs, FrequencyBand::Alpha);
|
||||
let entropy = spectral_entropy(&psd);
|
||||
let peak = peak_frequency(&psd, &freqs);
|
||||
```
|
||||
|
||||
### Connectivity
|
||||
|
||||
```rust
|
||||
use ruv_neural_signal::{phase_locking_value, coherence, compute_all_pairs, ConnectivityMetric};
|
||||
use ruv_neural_core::signal::FrequencyBand;
|
||||
|
||||
// Pairwise PLV in the alpha band
|
||||
let plv = phase_locking_value(&ch_a, &ch_b, 1000.0, FrequencyBand::Alpha);
|
||||
|
||||
// Full connectivity matrix
|
||||
let matrix = compute_all_pairs(&data, ConnectivityMetric::Plv, FrequencyBand::Alpha);
|
||||
```
|
||||
|
||||
### Hilbert Transform
|
||||
|
||||
```rust
|
||||
use ruv_neural_signal::{hilbert_transform, instantaneous_phase, instantaneous_amplitude};
|
||||
// Compute power spectral density (Welch method)
|
||||
let psd = compute_psd(&signal, 1000.0, 256, 128);
|
||||
let alpha_power = band_power(&psd, 1000.0, 8.0, 13.0);
|
||||
|
||||
// Extract instantaneous phase via Hilbert transform
|
||||
let analytic = hilbert_transform(&signal);
|
||||
let phase = instantaneous_phase(&signal);
|
||||
let envelope = instantaneous_amplitude(&signal);
|
||||
let phases = instantaneous_phase(&analytic);
|
||||
|
||||
// Compute all-pairs connectivity matrix
|
||||
let connectivity_matrix = compute_all_pairs(
|
||||
&multi_channel_data,
|
||||
ConnectivityMetric::PhaseLockingValue,
|
||||
);
|
||||
|
||||
// Run full preprocessing pipeline
|
||||
let pipeline = PreprocessingPipeline::default();
|
||||
let clean_data = pipeline.process(&raw_data).unwrap();
|
||||
```
|
||||
|
||||
## Mathematical Formulations
|
||||
## API Reference
|
||||
|
||||
### Butterworth Filter
|
||||
| Module | Key Types / Functions |
|
||||
|-----------------|-----------------------------------------------------------------|
|
||||
| `filter` | `BandpassFilter`, `HighpassFilter`, `LowpassFilter`, `NotchFilter`, `SignalProcessor` |
|
||||
| `spectral` | `compute_psd`, `compute_stft`, `band_power`, `spectral_entropy`, `peak_frequency` |
|
||||
| `hilbert` | `hilbert_transform`, `instantaneous_phase`, `instantaneous_amplitude` |
|
||||
| `artifact` | `detect_eye_blinks`, `detect_muscle_artifact`, `detect_cardiac`, `reject_artifacts` |
|
||||
| `connectivity` | `phase_locking_value`, `coherence`, `imaginary_coherence`, `amplitude_envelope_correlation`, `compute_all_pairs` |
|
||||
| `preprocessing` | `PreprocessingPipeline` |
|
||||
|
||||
The Butterworth filter maximizes flatness in the passband. The magnitude response of an Nth-order Butterworth lowpass filter is:
|
||||
## Feature Flags
|
||||
|
||||
```
|
||||
|H(jw)|^2 = 1 / (1 + (w/wc)^(2N))
|
||||
```
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|----------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `simd` | No | SIMD-accelerated filter kernels |
|
||||
|
||||
Implemented as cascaded second-order sections (biquads) via bilinear transform for numerical stability. Zero-phase filtering is achieved by forward-backward (filtfilt) application.
|
||||
## Integration
|
||||
|
||||
### Welch's Method (PSD)
|
||||
|
||||
The signal is divided into overlapping segments (50% overlap), each windowed with a Hann window, and the averaged periodogram is computed:
|
||||
|
||||
```
|
||||
PSD(f) = (1 / (M * fs * W)) * sum_m |X_m(f)|^2
|
||||
```
|
||||
|
||||
where M is the number of segments, fs is the sample rate, and W is the window power.
|
||||
|
||||
### Phase Locking Value
|
||||
|
||||
```
|
||||
PLV = |<exp(j * (phi_a(t) - phi_b(t)))>|
|
||||
```
|
||||
|
||||
Instantaneous phases are extracted via the Hilbert transform after bandpass filtering.
|
||||
|
||||
### Hilbert Transform
|
||||
|
||||
The analytic signal is computed via the FFT:
|
||||
1. Compute X(f) = FFT(x(t))
|
||||
2. Zero negative frequencies, double positive frequencies
|
||||
3. z(t) = IFFT(X_analytic(f))
|
||||
|
||||
Instantaneous amplitude = |z(t)|, instantaneous phase = arg(z(t)).
|
||||
|
||||
### Spectral Entropy
|
||||
|
||||
```
|
||||
H = -sum(p_k * log2(p_k))
|
||||
```
|
||||
|
||||
where p_k = PSD(f_k) / sum(PSD) is the normalized power distribution.
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- All filters use SOS (second-order sections) cascade for numerical stability with high filter orders
|
||||
- Zero-phase filtering (forward-backward) eliminates phase distortion at the cost of 2x computation
|
||||
- FFT operations use the `rustfft` crate (pure Rust, no external dependencies)
|
||||
- Connectivity matrix computation is O(N^2) in the number of channels; each pair requires bandpass filtering + Hilbert transform
|
||||
- The `simd` feature flag is reserved for future SIMD-accelerated inner loops
|
||||
Depends on `ruv-neural-core` for `MultiChannelTimeSeries` and `FrequencyBand` types.
|
||||
Feeds processed data into `ruv-neural-graph` for connectivity graph construction.
|
||||
Uses `rustfft` for FFT operations and `ndarray` for matrix computations.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
//! Criterion benchmarks for ruv-neural-signal.
|
||||
//!
|
||||
//! Benchmarks the performance-critical signal processing functions:
|
||||
//! - Hilbert transform (FFT-based analytic signal)
|
||||
//! - Power spectral density (Welch's method)
|
||||
//! - Connectivity matrix (PLV for all channel pairs)
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use rand::Rng;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use ruv_neural_core::signal::{FrequencyBand, MultiChannelTimeSeries};
|
||||
use ruv_neural_signal::{compute_all_pairs, compute_psd, hilbert_transform, ConnectivityMetric};
|
||||
|
||||
/// Generate a synthetic multi-tone signal of the given length.
|
||||
fn generate_signal(n: usize) -> Vec<f64> {
|
||||
(0..n)
|
||||
.map(|i| {
|
||||
let t = i as f64 / 1000.0;
|
||||
(2.0 * PI * 10.0 * t).sin()
|
||||
+ 0.5 * (2.0 * PI * 25.0 * t).cos()
|
||||
+ 0.3 * (2.0 * PI * 40.0 * t).sin()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generate random multi-channel data.
|
||||
fn generate_multichannel(num_channels: usize, num_samples: usize) -> MultiChannelTimeSeries {
|
||||
let mut rng = rand::thread_rng();
|
||||
let data: Vec<Vec<f64>> = (0..num_channels)
|
||||
.map(|ch| {
|
||||
(0..num_samples)
|
||||
.map(|i| {
|
||||
let t = i as f64 / 1000.0;
|
||||
let freq = 8.0 + ch as f64 * 0.5;
|
||||
(2.0 * PI * freq * t).sin() + rng.gen_range(-0.1..0.1)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
MultiChannelTimeSeries {
|
||||
data,
|
||||
sample_rate_hz: 1000.0,
|
||||
num_channels,
|
||||
num_samples,
|
||||
timestamp_start: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_hilbert_transform(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("hilbert_transform");
|
||||
|
||||
for &n in &[256, 1024, 4096] {
|
||||
let signal = generate_signal(n);
|
||||
group.bench_with_input(BenchmarkId::new("samples", n), &signal, |b, signal| {
|
||||
b.iter(|| hilbert_transform(black_box(signal)))
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_compute_psd(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("compute_psd");
|
||||
|
||||
let signal = generate_signal(1024);
|
||||
group.bench_function("1024_samples_win256", |b| {
|
||||
b.iter(|| compute_psd(black_box(&signal), black_box(1000.0), black_box(256)))
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_connectivity_matrix(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("connectivity_matrix");
|
||||
group.sample_size(10);
|
||||
|
||||
for &num_channels in &[16, 32] {
|
||||
let data = generate_multichannel(num_channels, 1024);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("plv_channels", num_channels),
|
||||
&data,
|
||||
|b, data| {
|
||||
b.iter(|| {
|
||||
compute_all_pairs(
|
||||
black_box(data),
|
||||
black_box(ConnectivityMetric::Plv),
|
||||
black_box(FrequencyBand::Alpha),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_hilbert_transform,
|
||||
bench_compute_psd,
|
||||
bench_connectivity_matrix,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
|
@ -11,11 +11,16 @@ use num_complex::Complex;
|
|||
use ruv_neural_core::signal::{FrequencyBand, MultiChannelTimeSeries};
|
||||
use rustfft::FftPlanner;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use crate::filter::BandpassFilter;
|
||||
use crate::hilbert::hilbert_transform;
|
||||
|
||||
thread_local! {
|
||||
static FFT_PLANNER: RefCell<FftPlanner<f64>> = RefCell::new(FftPlanner::new());
|
||||
}
|
||||
|
||||
/// Type of connectivity metric to compute.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ConnectivityMetric {
|
||||
|
|
@ -25,6 +30,22 @@ pub enum ConnectivityMetric {
|
|||
Aec,
|
||||
}
|
||||
|
||||
/// Returns `true` if any sample in `data` is NaN or infinite.
|
||||
pub fn contains_non_finite(data: &[f64]) -> bool {
|
||||
data.iter().any(|x| !x.is_finite())
|
||||
}
|
||||
|
||||
/// Validate that signal data contains no NaN or Inf values.
|
||||
///
|
||||
/// Returns `Ok(())` if all values are finite, or an error otherwise.
|
||||
pub fn validate_signal_finite(data: &[f64], label: &str) -> std::result::Result<(), String> {
|
||||
if contains_non_finite(data) {
|
||||
Err(format!("{label} contains NaN or infinite values"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the Phase Locking Value (PLV) between two signals.
|
||||
///
|
||||
/// PLV = |mean(exp(j * (phase_a - phase_b)))|
|
||||
|
|
@ -51,6 +72,11 @@ pub fn phase_locking_value(
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
// Reject NaN/Inf at the pipeline entry point
|
||||
if contains_non_finite(&signal_a[..n]) || contains_non_finite(&signal_b[..n]) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let (low, high) = band.range_hz();
|
||||
let bp = BandpassFilter::new(2, low, high, sample_rate);
|
||||
|
||||
|
|
@ -97,8 +123,7 @@ pub fn coherence(
|
|||
let window = hann_window(window_size);
|
||||
let num_freqs = window_size / 2 + 1;
|
||||
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft_forward(window_size);
|
||||
let fft = FFT_PLANNER.with(|p| p.borrow_mut().plan_fft_forward(window_size));
|
||||
|
||||
let mut saa = vec![0.0; num_freqs];
|
||||
let mut sbb = vec![0.0; num_freqs];
|
||||
|
|
@ -171,8 +196,7 @@ pub fn imaginary_coherence(
|
|||
let window = hann_window(window_size);
|
||||
let num_freqs = window_size / 2 + 1;
|
||||
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft_forward(window_size);
|
||||
let fft = FFT_PLANNER.with(|p| p.borrow_mut().plan_fft_forward(window_size));
|
||||
|
||||
let mut saa = vec![0.0; num_freqs];
|
||||
let mut sbb = vec![0.0; num_freqs];
|
||||
|
|
@ -238,6 +262,11 @@ pub fn amplitude_envelope_correlation(
|
|||
return 0.0;
|
||||
}
|
||||
|
||||
// Reject NaN/Inf at the pipeline entry point
|
||||
if contains_non_finite(&signal_a[..n]) || contains_non_finite(&signal_b[..n]) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let (low, high) = band.range_hz();
|
||||
let bp = BandpassFilter::new(2, low, high, sample_rate);
|
||||
|
||||
|
|
@ -252,6 +281,11 @@ pub fn amplitude_envelope_correlation(
|
|||
|
||||
/// Compute a full connectivity matrix for all channel pairs.
|
||||
///
|
||||
/// Pre-computes filtered analytic signals (or amplitude envelopes) for all
|
||||
/// channels once, then computes pairwise metrics. This eliminates redundant
|
||||
/// FFT/Hilbert work: for N channels, each channel is transformed once instead
|
||||
/// of (N-1) times.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Multi-channel time series
|
||||
/// * `metric` - Which connectivity metric to use
|
||||
|
|
@ -268,19 +302,66 @@ pub fn compute_all_pairs(
|
|||
let sr = data.sample_rate_hz;
|
||||
let mut matrix = vec![vec![0.0; nc]; nc];
|
||||
|
||||
for i in 0..nc {
|
||||
matrix[i][i] = 1.0; // Self-connectivity is 1.0
|
||||
for j in (i + 1)..nc {
|
||||
let val = match metric {
|
||||
ConnectivityMetric::Plv => {
|
||||
phase_locking_value(&data.data[i], &data.data[j], sr, band)
|
||||
if nc == 0 {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
let (low, high) = band.range_hz();
|
||||
let n = data.data[0].len();
|
||||
|
||||
match metric {
|
||||
ConnectivityMetric::Plv => {
|
||||
// Pre-compute analytic signals for all channels once.
|
||||
let bp = BandpassFilter::new(2, low, high, sr);
|
||||
let analytic_signals: Vec<Vec<Complex<f64>>> = data
|
||||
.data
|
||||
.iter()
|
||||
.map(|ch| {
|
||||
let filtered = bp.apply(&ch[..n.min(ch.len())]);
|
||||
hilbert_transform(&filtered)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for i in 0..nc {
|
||||
matrix[i][i] = 1.0;
|
||||
for j in (i + 1)..nc {
|
||||
let len = analytic_signals[i].len().min(analytic_signals[j].len());
|
||||
if len < 4 {
|
||||
continue;
|
||||
}
|
||||
let mut sum = Complex::new(0.0, 0.0);
|
||||
for k in 0..len {
|
||||
let phase_a = analytic_signals[i][k].im.atan2(analytic_signals[i][k].re);
|
||||
let phase_b = analytic_signals[j][k].im.atan2(analytic_signals[j][k].re);
|
||||
let diff = phase_a - phase_b;
|
||||
sum += Complex::new(diff.cos(), diff.sin());
|
||||
}
|
||||
let val = (sum / len as f64).norm();
|
||||
matrix[i][j] = val;
|
||||
matrix[j][i] = val;
|
||||
}
|
||||
ConnectivityMetric::Aec => {
|
||||
amplitude_envelope_correlation(&data.data[i], &data.data[j], sr, band)
|
||||
}
|
||||
}
|
||||
ConnectivityMetric::Aec => {
|
||||
// Pre-compute amplitude envelopes for all channels once.
|
||||
let bp = BandpassFilter::new(2, low, high, sr);
|
||||
let envelopes: Vec<Vec<f64>> = data
|
||||
.data
|
||||
.iter()
|
||||
.map(|ch| {
|
||||
let filtered = bp.apply(&ch[..n.min(ch.len())]);
|
||||
crate::hilbert::instantaneous_amplitude(&filtered)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for i in 0..nc {
|
||||
matrix[i][i] = 1.0;
|
||||
for j in (i + 1)..nc {
|
||||
let val = pearson_correlation(&envelopes[i], &envelopes[j]);
|
||||
matrix[i][j] = val;
|
||||
matrix[j][i] = val;
|
||||
}
|
||||
};
|
||||
matrix[i][j] = val;
|
||||
matrix[j][i] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,20 +10,30 @@
|
|||
|
||||
use num_complex::Complex;
|
||||
use rustfft::FftPlanner;
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static FFT_PLANNER: RefCell<FftPlanner<f64>> = RefCell::new(FftPlanner::new());
|
||||
}
|
||||
|
||||
/// Compute the analytic signal via FFT-based Hilbert transform.
|
||||
///
|
||||
/// Given a real signal x(t), returns the analytic signal z(t) = x(t) + j * H[x](t),
|
||||
/// where H[x] is the Hilbert transform of x.
|
||||
///
|
||||
/// Uses a thread-local cached FftPlanner to avoid re-creating plans on every call.
|
||||
pub fn hilbert_transform(signal: &[f64]) -> Vec<Complex<f64>> {
|
||||
let n = signal.len();
|
||||
if n == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft_forward = planner.plan_fft_forward(n);
|
||||
let fft_inverse = planner.plan_fft_inverse(n);
|
||||
let (fft_forward, fft_inverse) = FFT_PLANNER.with(|planner| {
|
||||
let mut planner = planner.borrow_mut();
|
||||
let fwd = planner.plan_fft_forward(n);
|
||||
let inv = planner.plan_fft_inverse(n);
|
||||
(fwd, inv)
|
||||
});
|
||||
|
||||
// Forward FFT
|
||||
let mut spectrum: Vec<Complex<f64>> = signal.iter().map(|&x| Complex::new(x, 0.0)).collect();
|
||||
|
|
|
|||
|
|
@ -9,8 +9,13 @@
|
|||
use num_complex::Complex;
|
||||
use ruv_neural_core::signal::{FrequencyBand, TimeFrequencyMap};
|
||||
use rustfft::FftPlanner;
|
||||
use std::cell::RefCell;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
thread_local! {
|
||||
static FFT_PLANNER: RefCell<FftPlanner<f64>> = RefCell::new(FftPlanner::new());
|
||||
}
|
||||
|
||||
/// Generate a Hann window of the given length.
|
||||
fn hann_window(length: usize) -> Vec<f64> {
|
||||
(0..length)
|
||||
|
|
@ -43,8 +48,7 @@ pub fn compute_psd(signal: &[f64], sample_rate: f64, window_size: usize) -> (Vec
|
|||
|
||||
let window_power: f64 = window.iter().map(|w| w * w).sum();
|
||||
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft_forward(win_size);
|
||||
let fft = FFT_PLANNER.with(|p| p.borrow_mut().plan_fft_forward(win_size));
|
||||
|
||||
let num_freqs = win_size / 2 + 1;
|
||||
let mut psd_accum = vec![0.0; num_freqs];
|
||||
|
|
@ -108,8 +112,7 @@ pub fn compute_stft(
|
|||
let win_size = window_size.min(n);
|
||||
let window = hann_window(win_size);
|
||||
|
||||
let mut planner = FftPlanner::new();
|
||||
let fft = planner.plan_fft_forward(win_size);
|
||||
let fft = FFT_PLANNER.with(|p| p.borrow_mut().plan_fft_forward(win_size));
|
||||
|
||||
let num_freqs = win_size / 2 + 1;
|
||||
let freq_resolution = sample_rate / win_size as f64;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
# ruv-neural-viz
|
||||
|
||||
Brain topology visualization, ASCII rendering, and export formats.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-viz` provides layout algorithms, color mapping, terminal-friendly
|
||||
ASCII rendering, animation frame generation, and export to standard graph
|
||||
visualization formats for brain connectivity graphs. It turns `BrainGraph` and
|
||||
mincut analysis results into visual output suitable for terminal dashboards,
|
||||
web applications, and graph analysis tools.
|
||||
|
||||
## Features
|
||||
|
||||
- **Layout algorithms** (`layout`): `ForceDirectedLayout` for spring-based node
|
||||
positioning and `AnatomicalLayout` for MNI-coordinate-based brain region
|
||||
placement; circular layout variants
|
||||
- **Color mapping** (`colormap`): `ColorMap` with cool-warm, viridis, and
|
||||
module-color schemes for mapping scalar values (edge weights, node degrees)
|
||||
to colors
|
||||
- **ASCII rendering** (`ascii`): Terminal-friendly renderers for brain graphs,
|
||||
mincut partitions, sparkline time series, connectivity matrices, and
|
||||
real-time dashboard views
|
||||
- **Export formats** (`export`): D3.js JSON (force-directed graph format),
|
||||
Graphviz DOT, GEXF (Gephi), and CSV timeline export
|
||||
- **Animation** (`animation`): `AnimationFrames` generator from temporal
|
||||
`BrainGraphSequence` data with `AnimatedNode`, `AnimatedEdge`, and
|
||||
`AnimationFrame` types; configurable `LayoutType` per frame
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use ruv_neural_viz::{
|
||||
ForceDirectedLayout, AnatomicalLayout, ColorMap,
|
||||
AnimationFrames, LayoutType,
|
||||
};
|
||||
use ruv_neural_viz::ascii;
|
||||
use ruv_neural_viz::export;
|
||||
|
||||
// Force-directed layout for a brain graph
|
||||
let layout = ForceDirectedLayout::new();
|
||||
let positions = layout.compute(&graph);
|
||||
|
||||
// Anatomical layout using MNI coordinates
|
||||
let anat_layout = AnatomicalLayout::new();
|
||||
let positions = anat_layout.compute(&graph, &parcellation);
|
||||
|
||||
// Color mapping
|
||||
let cmap = ColorMap::cool_warm();
|
||||
let color = cmap.map(0.75); // returns (r, g, b)
|
||||
|
||||
// ASCII rendering to terminal
|
||||
ascii::render_graph(&graph);
|
||||
ascii::render_mincut(&mincut_result);
|
||||
|
||||
// Export to D3.js JSON
|
||||
let d3_json = export::to_d3_json(&graph, &positions);
|
||||
|
||||
// Export to Graphviz DOT
|
||||
let dot = export::to_dot(&graph);
|
||||
|
||||
// Generate animation frames from temporal sequence
|
||||
let frames = AnimationFrames::from_sequence(
|
||||
&graph_sequence,
|
||||
LayoutType::ForceDirected,
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Module | Key Types / Functions |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `layout` | `ForceDirectedLayout`, `AnatomicalLayout` |
|
||||
| `colormap` | `ColorMap` |
|
||||
| `ascii` | Graph, mincut, sparkline, matrix, and dashboard renderers |
|
||||
| `export` | `to_d3_json`, `to_dot`, `to_gexf`, `to_csv_timeline` |
|
||||
| `animation` | `AnimationFrames`, `AnimationFrame`, `AnimatedNode`, `AnimatedEdge`, `LayoutType` |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
| Feature | Default | Description |
|
||||
|---------|---------|-------------------------------------|
|
||||
| `std` | Yes | Standard library support |
|
||||
| `ascii` | No | ASCII art rendering for terminal |
|
||||
|
||||
## Integration
|
||||
|
||||
Depends on `ruv-neural-core` for `BrainGraph` types, `ruv-neural-graph` for
|
||||
graph metrics used in layout computation, and `ruv-neural-mincut` for partition
|
||||
visualization. Used by `ruv-neural-cli` for terminal dashboard output and
|
||||
export commands.
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
|
|
@ -1,32 +1,44 @@
|
|||
# rUv Neural WASM
|
||||
# ruv-neural-wasm
|
||||
|
||||
WebAssembly bindings for browser-based brain topology visualization. Part of the **rUv Neural** suite.
|
||||
WebAssembly bindings for browser-based brain topology visualization.
|
||||
|
||||
## Overview
|
||||
|
||||
`ruv-neural-wasm` exposes the core brain graph analysis pipeline to JavaScript via `wasm-bindgen`. It provides lightweight, WASM-compatible implementations of graph algorithms (Stoer-Wagner mincut, spectral embedding, topology metrics) that run entirely in the browser without server round-trips.
|
||||
`ruv-neural-wasm` provides JavaScript-callable functions for creating, analyzing,
|
||||
and visualizing brain connectivity graphs directly in the browser. It wraps
|
||||
`ruv-neural-core` types with `wasm-bindgen` and implements lightweight
|
||||
WASM-compatible versions of graph algorithms (Stoer-Wagner mincut, spectral
|
||||
embedding via power iteration, topology metrics, and cognitive state decoding)
|
||||
that run without heavy native dependencies.
|
||||
|
||||
**Note:** This crate is excluded from the default workspace build. Build it
|
||||
separately targeting `wasm32-unknown-unknown`.
|
||||
|
||||
## Features
|
||||
|
||||
- **Graph parsing**: `create_brain_graph` -- parse `BrainGraph` from JSON
|
||||
- **Minimum cut**: `compute_mincut` -- Stoer-Wagner on graphs up to 500 nodes
|
||||
- **Topology metrics**: `compute_topology_metrics` -- density, efficiency,
|
||||
modularity, Fiedler value, entropy, module count
|
||||
- **Spectral embedding**: `embed_graph` -- power iteration on normalized Laplacian
|
||||
(no LAPACK dependency)
|
||||
- **State decoding**: `decode_state` -- threshold-based cognitive state classification
|
||||
from topology metrics
|
||||
- **RVF I/O**: `load_rvf` / `export_rvf` -- read and write RuVector binary files
|
||||
- **Streaming** (`streaming`): WebSocket-compatible streaming data processor
|
||||
- **Visualization data** (`viz_data`): Data structures for D3.js and Three.js rendering
|
||||
|
||||
## Build
|
||||
|
||||
Requires [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/):
|
||||
|
||||
```bash
|
||||
# Build for browser (ES modules)
|
||||
wasm-pack build --target web
|
||||
# Requires wasm-pack or cargo with wasm32 target
|
||||
cargo build -p ruv-neural-wasm --target wasm32-unknown-unknown --release
|
||||
|
||||
# Build for bundler (webpack, vite, etc.)
|
||||
wasm-pack build --target bundler
|
||||
|
||||
# Build for Node.js
|
||||
wasm-pack build --target nodejs
|
||||
|
||||
# Native check (no WASM target required)
|
||||
cargo check -p ruv-neural-wasm
|
||||
# Or with wasm-pack for npm-ready output
|
||||
wasm-pack build ruv-neural-wasm --target web
|
||||
```
|
||||
|
||||
## JavaScript Usage
|
||||
|
||||
### Basic Graph Analysis
|
||||
## Usage (JavaScript)
|
||||
|
||||
```javascript
|
||||
import init, {
|
||||
|
|
@ -35,173 +47,56 @@ import init, {
|
|||
compute_topology_metrics,
|
||||
embed_graph,
|
||||
decode_state,
|
||||
to_viz_graph,
|
||||
export_rvf,
|
||||
version,
|
||||
} from "./pkg/ruv_neural_wasm.js";
|
||||
} from './ruv_neural_wasm.js';
|
||||
|
||||
await init();
|
||||
|
||||
console.log("rUv Neural WASM v" + version());
|
||||
|
||||
// Define a brain connectivity graph
|
||||
const graphJson = JSON.stringify({
|
||||
num_nodes: 4,
|
||||
num_nodes: 3,
|
||||
edges: [
|
||||
{ source: 0, target: 1, weight: 0.9, metric: "Coherence", frequency_band: "Alpha" },
|
||||
{ source: 1, target: 2, weight: 0.3, metric: "Coherence", frequency_band: "Alpha" },
|
||||
{ source: 2, target: 3, weight: 0.8, metric: "Coherence", frequency_band: "Alpha" },
|
||||
{ source: 0, target: 3, weight: 0.7, metric: "Coherence", frequency_band: "Alpha" },
|
||||
{ source: 0, target: 1, weight: 0.8, metric: "Coherence", frequency_band: "Alpha" },
|
||||
{ source: 1, target: 2, weight: 0.5, metric: "Coherence", frequency_band: "Beta" },
|
||||
],
|
||||
timestamp: Date.now() / 1000,
|
||||
timestamp: 0.0,
|
||||
window_duration_s: 1.0,
|
||||
atlas: { Custom: 4 },
|
||||
atlas: { Custom: 3 },
|
||||
});
|
||||
|
||||
// Parse and validate
|
||||
const graph = create_brain_graph(graphJson);
|
||||
|
||||
// Compute minimum cut
|
||||
const mincut = compute_mincut(graphJson);
|
||||
console.log("Min-cut value:", mincut.cut_value);
|
||||
console.log("Partition A:", mincut.partition_a);
|
||||
console.log("Partition B:", mincut.partition_b);
|
||||
|
||||
// Compute topology metrics
|
||||
const metrics = compute_topology_metrics(graphJson);
|
||||
console.log("Modularity:", metrics.modularity);
|
||||
console.log("Fiedler value:", metrics.fiedler_value);
|
||||
console.log("Global efficiency:", metrics.global_efficiency);
|
||||
|
||||
// Decode cognitive state
|
||||
const metricsJson = JSON.stringify(metrics);
|
||||
const state = decode_state(metricsJson);
|
||||
console.log("Cognitive state:", state);
|
||||
|
||||
// Generate spectral embedding (2D)
|
||||
const embedding = embed_graph(graphJson, 2);
|
||||
console.log("Embedding dimension:", embedding.dimension);
|
||||
```
|
||||
|
||||
### D3.js Visualization
|
||||
|
||||
```javascript
|
||||
import { to_viz_graph } from "./pkg/ruv_neural_wasm.js";
|
||||
|
||||
const vizGraph = to_viz_graph(graphJson);
|
||||
|
||||
// vizGraph.nodes: [{ id, label, x, y, z, group, size, color }, ...]
|
||||
// vizGraph.edges: [{ source, target, weight, is_cut, color }, ...]
|
||||
// vizGraph.partitions: [[nodeIds...], [nodeIds...]] or null
|
||||
// vizGraph.cut_edges: [edgeIndices...] or null
|
||||
|
||||
// Use with D3 force simulation
|
||||
const simulation = d3
|
||||
.forceSimulation(vizGraph.nodes)
|
||||
.force("link", d3.forceLink(vizGraph.edges).id((d) => d.id))
|
||||
.force("charge", d3.forceManyBody().strength(-100))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2));
|
||||
|
||||
// Color nodes by partition group
|
||||
svg
|
||||
.selectAll("circle")
|
||||
.data(vizGraph.nodes)
|
||||
.enter()
|
||||
.append("circle")
|
||||
.attr("r", (d) => d.size * 5)
|
||||
.attr("fill", (d) => d.color);
|
||||
|
||||
// Highlight cut edges in red
|
||||
svg
|
||||
.selectAll("line")
|
||||
.data(vizGraph.edges)
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("stroke", (d) => d.color)
|
||||
.attr("stroke-width", (d) => (d.is_cut ? 3 : 1));
|
||||
```
|
||||
|
||||
### WebSocket Streaming
|
||||
|
||||
```javascript
|
||||
import { StreamProcessor } from "./pkg/ruv_neural_wasm.js";
|
||||
|
||||
// Create processor: 256-sample window, 64-sample hop
|
||||
const processor = new StreamProcessor(256, 64);
|
||||
|
||||
const ws = new WebSocket("ws://localhost:8080/neural-stream");
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const samples = new Float64Array(event.data);
|
||||
const stats = processor.push_samples(samples);
|
||||
|
||||
if (stats) {
|
||||
console.log(`Window ${stats.window_index}: mean=${stats.mean.toFixed(3)}`);
|
||||
updateVisualization(stats);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset when switching sessions
|
||||
function resetStream() {
|
||||
processor.reset();
|
||||
}
|
||||
```
|
||||
|
||||
### RVF File I/O
|
||||
|
||||
```javascript
|
||||
import { load_rvf, export_rvf } from "./pkg/ruv_neural_wasm.js";
|
||||
|
||||
// Export graph to RVF binary
|
||||
const rvfBytes = export_rvf(graphJson);
|
||||
|
||||
// Save as file download
|
||||
const blob = new Blob([rvfBytes], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Load RVF from file input
|
||||
const fileInput = document.getElementById("rvf-file");
|
||||
fileInput.onchange = async (e) => {
|
||||
const buffer = await e.target.files[0].arrayBuffer();
|
||||
const rvf = load_rvf(new Uint8Array(buffer));
|
||||
console.log("Loaded RVF:", rvf.header.data_type);
|
||||
};
|
||||
console.log('Version:', version());
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `create_brain_graph(json)` | Parse JSON into a BrainGraph |
|
||||
| `compute_mincut(json)` | Stoer-Wagner minimum cut (max 500 nodes) |
|
||||
| `compute_topology_metrics(json)` | Density, efficiency, modularity, Fiedler, entropy |
|
||||
| `embed_graph(json, dim)` | Spectral embedding via power iteration |
|
||||
| `decode_state(json)` | Classify cognitive state from metrics |
|
||||
| `to_viz_graph(json)` | Convert to D3.js/Three.js-ready visualization data |
|
||||
| `load_rvf(bytes)` | Parse RVF binary file |
|
||||
| `export_rvf(json)` | Serialize graph to RVF binary |
|
||||
| `version()` | Get crate version string |
|
||||
| `StreamProcessor` | Sliding-window streaming data processor |
|
||||
| Function | Description |
|
||||
|----------------------------|---------------------------------------------------|
|
||||
| `create_brain_graph(json)` | Parse JSON into a BrainGraph JS object |
|
||||
| `compute_mincut(json)` | Stoer-Wagner minimum cut, returns MincutResult |
|
||||
| `compute_topology_metrics(json)` | Compute TopologyMetrics for a graph |
|
||||
| `embed_graph(json, dim)` | Spectral embedding via power iteration |
|
||||
| `decode_state(json)` | Classify CognitiveState from TopologyMetrics |
|
||||
| `load_rvf(bytes)` | Parse RVF binary data into JS object |
|
||||
| `export_rvf(json)` | Serialize BrainGraph to RVF bytes |
|
||||
| `version()` | Return crate version string |
|
||||
|
||||
## Browser Compatibility
|
||||
| Module | Key Types |
|
||||
|-------------|-----------------------------------------------------------|
|
||||
| `graph_wasm`| `wasm_mincut`, `wasm_embed`, `wasm_topology_metrics`, `wasm_decode` |
|
||||
| `streaming` | WebSocket streaming data processor |
|
||||
| `viz_data` | D3.js / Three.js visualization structures |
|
||||
|
||||
- Chrome 57+ / Edge 79+
|
||||
- Firefox 52+
|
||||
- Safari 11+
|
||||
- All modern browsers with WebAssembly support
|
||||
## Integration
|
||||
|
||||
## Graph Size Limits
|
||||
|
||||
The Stoer-Wagner minimum cut algorithm runs in O(V^3) time. For browser performance:
|
||||
|
||||
| Nodes | Approximate Time |
|
||||
|-------|-----------------|
|
||||
| 68 (DK atlas) | < 10ms |
|
||||
| 100 (Schaefer) | < 50ms |
|
||||
| 200 (Schaefer) | < 500ms |
|
||||
| 400 (Schaefer) | ~2-5s |
|
||||
| 500 (max) | ~5-10s |
|
||||
|
||||
For larger graphs, use the native `ruv-neural-mincut` crate with server-side computation.
|
||||
Depends on `ruv-neural-core` for `BrainGraph`, `TopologyMetrics`, `RvfFile`,
|
||||
and `CognitiveState` types. Uses `wasm-bindgen` and `serde-wasm-bindgen` for
|
||||
JS interop. Designed for browser-based dashboards and real-time visualization
|
||||
applications.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
{"type":"edit","file":"unknown","timestamp":1772835768740,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835786050,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835802335,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835865846,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835875824,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835892636,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835909237,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835921184,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835930809,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835942468,"sessionId":null}
|
||||
{"type":"edit","file":"unknown","timestamp":1772835952451,"sessionId":null}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
Channel,
|
||||
PluginListener,
|
||||
Resource,
|
||||
SERIALIZE_TO_IPC_FN,
|
||||
addPluginListener,
|
||||
checkPermissions,
|
||||
convertFileSrc,
|
||||
invoke,
|
||||
isTauri,
|
||||
requestPermissions,
|
||||
transformCallback
|
||||
} from "./chunk-YQTFE5VL.js";
|
||||
import "./chunk-BUSYA2B4.js";
|
||||
export {
|
||||
Channel,
|
||||
PluginListener,
|
||||
Resource,
|
||||
SERIALIZE_TO_IPC_FN,
|
||||
addPluginListener,
|
||||
checkPermissions,
|
||||
convertFileSrc,
|
||||
invoke,
|
||||
isTauri,
|
||||
requestPermissions,
|
||||
transformCallback
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
invoke,
|
||||
transformCallback
|
||||
} from "./chunk-YQTFE5VL.js";
|
||||
import "./chunk-BUSYA2B4.js";
|
||||
|
||||
// node_modules/@tauri-apps/api/event.js
|
||||
var TauriEvent;
|
||||
(function(TauriEvent2) {
|
||||
TauriEvent2["WINDOW_RESIZED"] = "tauri://resize";
|
||||
TauriEvent2["WINDOW_MOVED"] = "tauri://move";
|
||||
TauriEvent2["WINDOW_CLOSE_REQUESTED"] = "tauri://close-requested";
|
||||
TauriEvent2["WINDOW_DESTROYED"] = "tauri://destroyed";
|
||||
TauriEvent2["WINDOW_FOCUS"] = "tauri://focus";
|
||||
TauriEvent2["WINDOW_BLUR"] = "tauri://blur";
|
||||
TauriEvent2["WINDOW_SCALE_FACTOR_CHANGED"] = "tauri://scale-change";
|
||||
TauriEvent2["WINDOW_THEME_CHANGED"] = "tauri://theme-changed";
|
||||
TauriEvent2["WINDOW_CREATED"] = "tauri://window-created";
|
||||
TauriEvent2["WEBVIEW_CREATED"] = "tauri://webview-created";
|
||||
TauriEvent2["DRAG_ENTER"] = "tauri://drag-enter";
|
||||
TauriEvent2["DRAG_OVER"] = "tauri://drag-over";
|
||||
TauriEvent2["DRAG_DROP"] = "tauri://drag-drop";
|
||||
TauriEvent2["DRAG_LEAVE"] = "tauri://drag-leave";
|
||||
})(TauriEvent || (TauriEvent = {}));
|
||||
async function _unlisten(event, eventId) {
|
||||
window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(event, eventId);
|
||||
await invoke("plugin:event|unlisten", {
|
||||
event,
|
||||
eventId
|
||||
});
|
||||
}
|
||||
async function listen(event, handler, options) {
|
||||
var _a;
|
||||
const target = typeof (options === null || options === void 0 ? void 0 : options.target) === "string" ? { kind: "AnyLabel", label: options.target } : (_a = options === null || options === void 0 ? void 0 : options.target) !== null && _a !== void 0 ? _a : { kind: "Any" };
|
||||
return invoke("plugin:event|listen", {
|
||||
event,
|
||||
target,
|
||||
handler: transformCallback(handler)
|
||||
}).then((eventId) => {
|
||||
return async () => _unlisten(event, eventId);
|
||||
});
|
||||
}
|
||||
async function once(event, handler, options) {
|
||||
return listen(event, (eventData) => {
|
||||
void _unlisten(event, eventData.id);
|
||||
handler(eventData);
|
||||
}, options);
|
||||
}
|
||||
async function emit(event, payload) {
|
||||
await invoke("plugin:event|emit", {
|
||||
event,
|
||||
payload
|
||||
});
|
||||
}
|
||||
async function emitTo(target, event, payload) {
|
||||
const eventTarget = typeof target === "string" ? { kind: "AnyLabel", label: target } : target;
|
||||
await invoke("plugin:event|emit_to", {
|
||||
target: eventTarget,
|
||||
event,
|
||||
payload
|
||||
});
|
||||
}
|
||||
export {
|
||||
TauriEvent,
|
||||
emit,
|
||||
emitTo,
|
||||
listen,
|
||||
once
|
||||
};
|
||||
//# sourceMappingURL=@tauri-apps_api_event.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,76 @@
|
|||
import {
|
||||
invoke
|
||||
} from "./chunk-YQTFE5VL.js";
|
||||
import "./chunk-BUSYA2B4.js";
|
||||
|
||||
// node_modules/@tauri-apps/plugin-dialog/dist-js/index.js
|
||||
function buttonsToRust(buttons) {
|
||||
if (buttons === void 0) {
|
||||
return void 0;
|
||||
}
|
||||
if (typeof buttons === "string") {
|
||||
return buttons;
|
||||
} else if ("ok" in buttons && "cancel" in buttons) {
|
||||
return { OkCancelCustom: [buttons.ok, buttons.cancel] };
|
||||
} else if ("yes" in buttons && "no" in buttons && "cancel" in buttons) {
|
||||
return {
|
||||
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
|
||||
};
|
||||
} else if ("ok" in buttons) {
|
||||
return { OkCustom: buttons.ok };
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
async function open(options = {}) {
|
||||
if (typeof options === "object") {
|
||||
Object.freeze(options);
|
||||
}
|
||||
return await invoke("plugin:dialog|open", { options });
|
||||
}
|
||||
async function save(options = {}) {
|
||||
if (typeof options === "object") {
|
||||
Object.freeze(options);
|
||||
}
|
||||
return await invoke("plugin:dialog|save", { options });
|
||||
}
|
||||
async function message(message2, options) {
|
||||
var _a, _b;
|
||||
const opts = typeof options === "string" ? { title: options } : options;
|
||||
return invoke("plugin:dialog|message", {
|
||||
message: message2.toString(),
|
||||
title: (_a = opts == null ? void 0 : opts.title) == null ? void 0 : _a.toString(),
|
||||
kind: opts == null ? void 0 : opts.kind,
|
||||
okButtonLabel: (_b = opts == null ? void 0 : opts.okLabel) == null ? void 0 : _b.toString(),
|
||||
buttons: buttonsToRust(opts == null ? void 0 : opts.buttons)
|
||||
});
|
||||
}
|
||||
async function ask(message2, options) {
|
||||
var _a, _b, _c;
|
||||
const opts = typeof options === "string" ? { title: options } : options;
|
||||
return await invoke("plugin:dialog|ask", {
|
||||
message: message2.toString(),
|
||||
title: (_a = opts == null ? void 0 : opts.title) == null ? void 0 : _a.toString(),
|
||||
kind: opts == null ? void 0 : opts.kind,
|
||||
yesButtonLabel: (_b = opts == null ? void 0 : opts.okLabel) == null ? void 0 : _b.toString(),
|
||||
noButtonLabel: (_c = opts == null ? void 0 : opts.cancelLabel) == null ? void 0 : _c.toString()
|
||||
});
|
||||
}
|
||||
async function confirm(message2, options) {
|
||||
var _a, _b, _c;
|
||||
const opts = typeof options === "string" ? { title: options } : options;
|
||||
return await invoke("plugin:dialog|confirm", {
|
||||
message: message2.toString(),
|
||||
title: (_a = opts == null ? void 0 : opts.title) == null ? void 0 : _a.toString(),
|
||||
kind: opts == null ? void 0 : opts.kind,
|
||||
okButtonLabel: (_b = opts == null ? void 0 : opts.okLabel) == null ? void 0 : _b.toString(),
|
||||
cancelButtonLabel: (_c = opts == null ? void 0 : opts.cancelLabel) == null ? void 0 : _c.toString()
|
||||
});
|
||||
}
|
||||
export {
|
||||
ask,
|
||||
confirm,
|
||||
message,
|
||||
open,
|
||||
save
|
||||
};
|
||||
//# sourceMappingURL=@tauri-apps_plugin-dialog.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"hash": "6d7d2bc8",
|
||||
"configHash": "85bee8b1",
|
||||
"lockfileHash": "c11f8b2c",
|
||||
"browserHash": "17d61b64",
|
||||
"optimized": {
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../node_modules/react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "e6f80dbe",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react": {
|
||||
"src": "../../node_modules/react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "44e03674",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../node_modules/react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "b0a4bf1a",
|
||||
"needsInterop": true
|
||||
},
|
||||
"@tauri-apps/api/core": {
|
||||
"src": "../../node_modules/@tauri-apps/api/core.js",
|
||||
"file": "@tauri-apps_api_core.js",
|
||||
"fileHash": "c0acaaf2",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@tauri-apps/plugin-dialog": {
|
||||
"src": "../../node_modules/@tauri-apps/plugin-dialog/dist-js/index.js",
|
||||
"file": "@tauri-apps_plugin-dialog.js",
|
||||
"fileHash": "615805d9",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@tauri-apps/api/event": {
|
||||
"src": "../../node_modules/@tauri-apps/api/event.js",
|
||||
"file": "@tauri-apps_api_event.js",
|
||||
"fileHash": "5c1fbd95",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-JCH2SJW3": {
|
||||
"file": "chunk-JCH2SJW3.js"
|
||||
},
|
||||
"chunk-YQTFE5VL": {
|
||||
"file": "chunk-YQTFE5VL.js"
|
||||
},
|
||||
"chunk-BUSYA2B4": {
|
||||
"file": "chunk-BUSYA2B4.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __commonJS = (cb, mod) => function __require() {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
};
|
||||
|
||||
export {
|
||||
__commonJS
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,150 @@
|
|||
// node_modules/@tauri-apps/api/external/tslib/tslib.es6.js
|
||||
function __classPrivateFieldGet(receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
}
|
||||
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
||||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||
return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
|
||||
}
|
||||
|
||||
// node_modules/@tauri-apps/api/core.js
|
||||
var _Channel_onmessage;
|
||||
var _Channel_nextMessageIndex;
|
||||
var _Channel_pendingMessages;
|
||||
var _Channel_messageEndIndex;
|
||||
var _Resource_rid;
|
||||
var SERIALIZE_TO_IPC_FN = "__TAURI_TO_IPC_KEY__";
|
||||
function transformCallback(callback, once = false) {
|
||||
return window.__TAURI_INTERNALS__.transformCallback(callback, once);
|
||||
}
|
||||
var Channel = class {
|
||||
constructor(onmessage) {
|
||||
_Channel_onmessage.set(this, void 0);
|
||||
_Channel_nextMessageIndex.set(this, 0);
|
||||
_Channel_pendingMessages.set(this, []);
|
||||
_Channel_messageEndIndex.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _Channel_onmessage, onmessage || (() => {
|
||||
}), "f");
|
||||
this.id = transformCallback((rawMessage) => {
|
||||
const index = rawMessage.index;
|
||||
if ("end" in rawMessage) {
|
||||
if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")) {
|
||||
this.cleanupCallback();
|
||||
} else {
|
||||
__classPrivateFieldSet(this, _Channel_messageEndIndex, index, "f");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const message = rawMessage.message;
|
||||
if (index == __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")) {
|
||||
__classPrivateFieldGet(this, _Channel_onmessage, "f").call(this, message);
|
||||
__classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") + 1, "f");
|
||||
while (__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") in __classPrivateFieldGet(this, _Channel_pendingMessages, "f")) {
|
||||
const message2 = __classPrivateFieldGet(this, _Channel_pendingMessages, "f")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")];
|
||||
__classPrivateFieldGet(this, _Channel_onmessage, "f").call(this, message2);
|
||||
delete __classPrivateFieldGet(this, _Channel_pendingMessages, "f")[__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f")];
|
||||
__classPrivateFieldSet(this, _Channel_nextMessageIndex, __classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") + 1, "f");
|
||||
}
|
||||
if (__classPrivateFieldGet(this, _Channel_nextMessageIndex, "f") === __classPrivateFieldGet(this, _Channel_messageEndIndex, "f")) {
|
||||
this.cleanupCallback();
|
||||
}
|
||||
} else {
|
||||
__classPrivateFieldGet(this, _Channel_pendingMessages, "f")[index] = message;
|
||||
}
|
||||
});
|
||||
}
|
||||
cleanupCallback() {
|
||||
window.__TAURI_INTERNALS__.unregisterCallback(this.id);
|
||||
}
|
||||
set onmessage(handler) {
|
||||
__classPrivateFieldSet(this, _Channel_onmessage, handler, "f");
|
||||
}
|
||||
get onmessage() {
|
||||
return __classPrivateFieldGet(this, _Channel_onmessage, "f");
|
||||
}
|
||||
[(_Channel_onmessage = /* @__PURE__ */ new WeakMap(), _Channel_nextMessageIndex = /* @__PURE__ */ new WeakMap(), _Channel_pendingMessages = /* @__PURE__ */ new WeakMap(), _Channel_messageEndIndex = /* @__PURE__ */ new WeakMap(), SERIALIZE_TO_IPC_FN)]() {
|
||||
return `__CHANNEL__:${this.id}`;
|
||||
}
|
||||
toJSON() {
|
||||
return this[SERIALIZE_TO_IPC_FN]();
|
||||
}
|
||||
};
|
||||
var PluginListener = class {
|
||||
constructor(plugin, event, channelId) {
|
||||
this.plugin = plugin;
|
||||
this.event = event;
|
||||
this.channelId = channelId;
|
||||
}
|
||||
async unregister() {
|
||||
return invoke(`plugin:${this.plugin}|remove_listener`, {
|
||||
event: this.event,
|
||||
channelId: this.channelId
|
||||
});
|
||||
}
|
||||
};
|
||||
async function addPluginListener(plugin, event, cb) {
|
||||
const handler = new Channel(cb);
|
||||
try {
|
||||
await invoke(`plugin:${plugin}|register_listener`, {
|
||||
event,
|
||||
handler
|
||||
});
|
||||
return new PluginListener(plugin, event, handler.id);
|
||||
} catch {
|
||||
await invoke(`plugin:${plugin}|registerListener`, { event, handler });
|
||||
return new PluginListener(plugin, event, handler.id);
|
||||
}
|
||||
}
|
||||
async function checkPermissions(plugin) {
|
||||
return invoke(`plugin:${plugin}|check_permissions`);
|
||||
}
|
||||
async function requestPermissions(plugin) {
|
||||
return invoke(`plugin:${plugin}|request_permissions`);
|
||||
}
|
||||
async function invoke(cmd, args = {}, options) {
|
||||
return window.__TAURI_INTERNALS__.invoke(cmd, args, options);
|
||||
}
|
||||
function convertFileSrc(filePath, protocol = "asset") {
|
||||
return window.__TAURI_INTERNALS__.convertFileSrc(filePath, protocol);
|
||||
}
|
||||
var Resource = class {
|
||||
get rid() {
|
||||
return __classPrivateFieldGet(this, _Resource_rid, "f");
|
||||
}
|
||||
constructor(rid) {
|
||||
_Resource_rid.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _Resource_rid, rid, "f");
|
||||
}
|
||||
/**
|
||||
* Destroys and cleans up this resource from memory.
|
||||
* **You should not call any method on this object anymore and should drop any reference to it.**
|
||||
*/
|
||||
async close() {
|
||||
return invoke("plugin:resources|close", {
|
||||
rid: this.rid
|
||||
});
|
||||
}
|
||||
};
|
||||
_Resource_rid = /* @__PURE__ */ new WeakMap();
|
||||
function isTauri() {
|
||||
return !!(globalThis || window).isTauri;
|
||||
}
|
||||
|
||||
export {
|
||||
SERIALIZE_TO_IPC_FN,
|
||||
transformCallback,
|
||||
Channel,
|
||||
PluginListener,
|
||||
addPluginListener,
|
||||
checkPermissions,
|
||||
requestPermissions,
|
||||
invoke,
|
||||
convertFileSrc,
|
||||
Resource,
|
||||
isTauri
|
||||
};
|
||||
//# sourceMappingURL=chunk-YQTFE5VL.js.map
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
||||
21714
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.vite/deps/react-dom_client.js
vendored
Normal file
21714
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.vite/deps/react-dom_client.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
5
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.vite/deps/react.js
vendored
Normal file
5
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/.vite/deps/react.js
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import {
|
||||
require_react
|
||||
} from "./chunk-JCH2SJW3.js";
|
||||
import "./chunk-BUSYA2B4.js";
|
||||
export default require_react();
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
|
|
@ -0,0 +1,913 @@
|
|||
import {
|
||||
require_react
|
||||
} from "./chunk-JCH2SJW3.js";
|
||||
import {
|
||||
__commonJS
|
||||
} from "./chunk-BUSYA2B4.js";
|
||||
|
||||
// node_modules/react/cjs/react-jsx-dev-runtime.development.js
|
||||
var require_react_jsx_dev_runtime_development = __commonJS({
|
||||
"node_modules/react/cjs/react-jsx-dev-runtime.development.js"(exports) {
|
||||
"use strict";
|
||||
if (true) {
|
||||
(function() {
|
||||
"use strict";
|
||||
var React = require_react();
|
||||
var REACT_ELEMENT_TYPE = Symbol.for("react.element");
|
||||
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
|
||||
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
||||
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
|
||||
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
|
||||
var REACT_PROVIDER_TYPE = Symbol.for("react.provider");
|
||||
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
|
||||
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
|
||||
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
|
||||
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
|
||||
var REACT_MEMO_TYPE = Symbol.for("react.memo");
|
||||
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
|
||||
var REACT_OFFSCREEN_TYPE = Symbol.for("react.offscreen");
|
||||
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
|
||||
var FAUX_ITERATOR_SYMBOL = "@@iterator";
|
||||
function getIteratorFn(maybeIterable) {
|
||||
if (maybeIterable === null || typeof maybeIterable !== "object") {
|
||||
return null;
|
||||
}
|
||||
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL];
|
||||
if (typeof maybeIterator === "function") {
|
||||
return maybeIterator;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
|
||||
function error(format) {
|
||||
{
|
||||
{
|
||||
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
||||
args[_key2 - 1] = arguments[_key2];
|
||||
}
|
||||
printWarning("error", format, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
function printWarning(level, format, args) {
|
||||
{
|
||||
var ReactDebugCurrentFrame2 = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
var stack = ReactDebugCurrentFrame2.getStackAddendum();
|
||||
if (stack !== "") {
|
||||
format += "%s";
|
||||
args = args.concat([stack]);
|
||||
}
|
||||
var argsWithFormat = args.map(function(item) {
|
||||
return String(item);
|
||||
});
|
||||
argsWithFormat.unshift("Warning: " + format);
|
||||
Function.prototype.apply.call(console[level], console, argsWithFormat);
|
||||
}
|
||||
}
|
||||
var enableScopeAPI = false;
|
||||
var enableCacheElement = false;
|
||||
var enableTransitionTracing = false;
|
||||
var enableLegacyHidden = false;
|
||||
var enableDebugTracing = false;
|
||||
var REACT_MODULE_REFERENCE;
|
||||
{
|
||||
REACT_MODULE_REFERENCE = Symbol.for("react.module.reference");
|
||||
}
|
||||
function isValidElementType(type) {
|
||||
if (typeof type === "string" || typeof type === "function") {
|
||||
return true;
|
||||
}
|
||||
if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden || type === REACT_OFFSCREEN_TYPE || enableScopeAPI || enableCacheElement || enableTransitionTracing) {
|
||||
return true;
|
||||
}
|
||||
if (typeof type === "object" && type !== null) {
|
||||
if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
|
||||
// types supported by any Flight configuration anywhere since
|
||||
// we don't know which Flight build this will end up being used
|
||||
// with.
|
||||
type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== void 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function getWrappedName(outerType, innerType, wrapperName) {
|
||||
var displayName = outerType.displayName;
|
||||
if (displayName) {
|
||||
return displayName;
|
||||
}
|
||||
var functionName = innerType.displayName || innerType.name || "";
|
||||
return functionName !== "" ? wrapperName + "(" + functionName + ")" : wrapperName;
|
||||
}
|
||||
function getContextName(type) {
|
||||
return type.displayName || "Context";
|
||||
}
|
||||
function getComponentNameFromType(type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
{
|
||||
if (typeof type.tag === "number") {
|
||||
error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.");
|
||||
}
|
||||
}
|
||||
if (typeof type === "function") {
|
||||
return type.displayName || type.name || null;
|
||||
}
|
||||
if (typeof type === "string") {
|
||||
return type;
|
||||
}
|
||||
switch (type) {
|
||||
case REACT_FRAGMENT_TYPE:
|
||||
return "Fragment";
|
||||
case REACT_PORTAL_TYPE:
|
||||
return "Portal";
|
||||
case REACT_PROFILER_TYPE:
|
||||
return "Profiler";
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
return "StrictMode";
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return "Suspense";
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return "SuspenseList";
|
||||
}
|
||||
if (typeof type === "object") {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_CONTEXT_TYPE:
|
||||
var context = type;
|
||||
return getContextName(context) + ".Consumer";
|
||||
case REACT_PROVIDER_TYPE:
|
||||
var provider = type;
|
||||
return getContextName(provider._context) + ".Provider";
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
return getWrappedName(type, type.render, "ForwardRef");
|
||||
case REACT_MEMO_TYPE:
|
||||
var outerName = type.displayName || null;
|
||||
if (outerName !== null) {
|
||||
return outerName;
|
||||
}
|
||||
return getComponentNameFromType(type.type) || "Memo";
|
||||
case REACT_LAZY_TYPE: {
|
||||
var lazyComponent = type;
|
||||
var payload = lazyComponent._payload;
|
||||
var init = lazyComponent._init;
|
||||
try {
|
||||
return getComponentNameFromType(init(payload));
|
||||
} catch (x) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var assign = Object.assign;
|
||||
var disabledDepth = 0;
|
||||
var prevLog;
|
||||
var prevInfo;
|
||||
var prevWarn;
|
||||
var prevError;
|
||||
var prevGroup;
|
||||
var prevGroupCollapsed;
|
||||
var prevGroupEnd;
|
||||
function disabledLog() {
|
||||
}
|
||||
disabledLog.__reactDisabledLog = true;
|
||||
function disableLogs() {
|
||||
{
|
||||
if (disabledDepth === 0) {
|
||||
prevLog = console.log;
|
||||
prevInfo = console.info;
|
||||
prevWarn = console.warn;
|
||||
prevError = console.error;
|
||||
prevGroup = console.group;
|
||||
prevGroupCollapsed = console.groupCollapsed;
|
||||
prevGroupEnd = console.groupEnd;
|
||||
var props = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: disabledLog,
|
||||
writable: true
|
||||
};
|
||||
Object.defineProperties(console, {
|
||||
info: props,
|
||||
log: props,
|
||||
warn: props,
|
||||
error: props,
|
||||
group: props,
|
||||
groupCollapsed: props,
|
||||
groupEnd: props
|
||||
});
|
||||
}
|
||||
disabledDepth++;
|
||||
}
|
||||
}
|
||||
function reenableLogs() {
|
||||
{
|
||||
disabledDepth--;
|
||||
if (disabledDepth === 0) {
|
||||
var props = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true
|
||||
};
|
||||
Object.defineProperties(console, {
|
||||
log: assign({}, props, {
|
||||
value: prevLog
|
||||
}),
|
||||
info: assign({}, props, {
|
||||
value: prevInfo
|
||||
}),
|
||||
warn: assign({}, props, {
|
||||
value: prevWarn
|
||||
}),
|
||||
error: assign({}, props, {
|
||||
value: prevError
|
||||
}),
|
||||
group: assign({}, props, {
|
||||
value: prevGroup
|
||||
}),
|
||||
groupCollapsed: assign({}, props, {
|
||||
value: prevGroupCollapsed
|
||||
}),
|
||||
groupEnd: assign({}, props, {
|
||||
value: prevGroupEnd
|
||||
})
|
||||
});
|
||||
}
|
||||
if (disabledDepth < 0) {
|
||||
error("disabledDepth fell below zero. This is a bug in React. Please file an issue.");
|
||||
}
|
||||
}
|
||||
}
|
||||
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
var prefix;
|
||||
function describeBuiltInComponentFrame(name, source, ownerFn) {
|
||||
{
|
||||
if (prefix === void 0) {
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x) {
|
||||
var match = x.stack.trim().match(/\n( *(at )?)/);
|
||||
prefix = match && match[1] || "";
|
||||
}
|
||||
}
|
||||
return "\n" + prefix + name;
|
||||
}
|
||||
}
|
||||
var reentry = false;
|
||||
var componentFrameCache;
|
||||
{
|
||||
var PossiblyWeakMap = typeof WeakMap === "function" ? WeakMap : Map;
|
||||
componentFrameCache = new PossiblyWeakMap();
|
||||
}
|
||||
function describeNativeComponentFrame(fn, construct) {
|
||||
if (!fn || reentry) {
|
||||
return "";
|
||||
}
|
||||
{
|
||||
var frame = componentFrameCache.get(fn);
|
||||
if (frame !== void 0) {
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
var control;
|
||||
reentry = true;
|
||||
var previousPrepareStackTrace = Error.prepareStackTrace;
|
||||
Error.prepareStackTrace = void 0;
|
||||
var previousDispatcher;
|
||||
{
|
||||
previousDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = null;
|
||||
disableLogs();
|
||||
}
|
||||
try {
|
||||
if (construct) {
|
||||
var Fake = function() {
|
||||
throw Error();
|
||||
};
|
||||
Object.defineProperty(Fake.prototype, "props", {
|
||||
set: function() {
|
||||
throw Error();
|
||||
}
|
||||
});
|
||||
if (typeof Reflect === "object" && Reflect.construct) {
|
||||
try {
|
||||
Reflect.construct(Fake, []);
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
Reflect.construct(fn, [], Fake);
|
||||
} else {
|
||||
try {
|
||||
Fake.call();
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
fn.call(Fake.prototype);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
throw Error();
|
||||
} catch (x) {
|
||||
control = x;
|
||||
}
|
||||
fn();
|
||||
}
|
||||
} catch (sample) {
|
||||
if (sample && control && typeof sample.stack === "string") {
|
||||
var sampleLines = sample.stack.split("\n");
|
||||
var controlLines = control.stack.split("\n");
|
||||
var s = sampleLines.length - 1;
|
||||
var c = controlLines.length - 1;
|
||||
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
|
||||
c--;
|
||||
}
|
||||
for (; s >= 1 && c >= 0; s--, c--) {
|
||||
if (sampleLines[s] !== controlLines[c]) {
|
||||
if (s !== 1 || c !== 1) {
|
||||
do {
|
||||
s--;
|
||||
c--;
|
||||
if (c < 0 || sampleLines[s] !== controlLines[c]) {
|
||||
var _frame = "\n" + sampleLines[s].replace(" at new ", " at ");
|
||||
if (fn.displayName && _frame.includes("<anonymous>")) {
|
||||
_frame = _frame.replace("<anonymous>", fn.displayName);
|
||||
}
|
||||
{
|
||||
if (typeof fn === "function") {
|
||||
componentFrameCache.set(fn, _frame);
|
||||
}
|
||||
}
|
||||
return _frame;
|
||||
}
|
||||
} while (s >= 1 && c >= 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reentry = false;
|
||||
{
|
||||
ReactCurrentDispatcher.current = previousDispatcher;
|
||||
reenableLogs();
|
||||
}
|
||||
Error.prepareStackTrace = previousPrepareStackTrace;
|
||||
}
|
||||
var name = fn ? fn.displayName || fn.name : "";
|
||||
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : "";
|
||||
{
|
||||
if (typeof fn === "function") {
|
||||
componentFrameCache.set(fn, syntheticFrame);
|
||||
}
|
||||
}
|
||||
return syntheticFrame;
|
||||
}
|
||||
function describeFunctionComponentFrame(fn, source, ownerFn) {
|
||||
{
|
||||
return describeNativeComponentFrame(fn, false);
|
||||
}
|
||||
}
|
||||
function shouldConstruct(Component) {
|
||||
var prototype = Component.prototype;
|
||||
return !!(prototype && prototype.isReactComponent);
|
||||
}
|
||||
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) {
|
||||
if (type == null) {
|
||||
return "";
|
||||
}
|
||||
if (typeof type === "function") {
|
||||
{
|
||||
return describeNativeComponentFrame(type, shouldConstruct(type));
|
||||
}
|
||||
}
|
||||
if (typeof type === "string") {
|
||||
return describeBuiltInComponentFrame(type);
|
||||
}
|
||||
switch (type) {
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return describeBuiltInComponentFrame("Suspense");
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return describeBuiltInComponentFrame("SuspenseList");
|
||||
}
|
||||
if (typeof type === "object") {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
return describeFunctionComponentFrame(type.render);
|
||||
case REACT_MEMO_TYPE:
|
||||
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
|
||||
case REACT_LAZY_TYPE: {
|
||||
var lazyComponent = type;
|
||||
var payload = lazyComponent._payload;
|
||||
var init = lazyComponent._init;
|
||||
try {
|
||||
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn);
|
||||
} catch (x) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var loggedTypeFailures = {};
|
||||
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
function setCurrentlyValidatingElement(element) {
|
||||
{
|
||||
if (element) {
|
||||
var owner = element._owner;
|
||||
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(stack);
|
||||
} else {
|
||||
ReactDebugCurrentFrame.setExtraStackFrame(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
function checkPropTypes(typeSpecs, values, location, componentName, element) {
|
||||
{
|
||||
var has = Function.call.bind(hasOwnProperty);
|
||||
for (var typeSpecName in typeSpecs) {
|
||||
if (has(typeSpecs, typeSpecName)) {
|
||||
var error$1 = void 0;
|
||||
try {
|
||||
if (typeof typeSpecs[typeSpecName] !== "function") {
|
||||
var err = Error((componentName || "React class") + ": " + location + " type `" + typeSpecName + "` is invalid; it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
|
||||
err.name = "Invariant Violation";
|
||||
throw err;
|
||||
}
|
||||
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
|
||||
} catch (ex) {
|
||||
error$1 = ex;
|
||||
}
|
||||
if (error$1 && !(error$1 instanceof Error)) {
|
||||
setCurrentlyValidatingElement(element);
|
||||
error("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).", componentName || "React class", location, typeSpecName, typeof error$1);
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
|
||||
loggedTypeFailures[error$1.message] = true;
|
||||
setCurrentlyValidatingElement(element);
|
||||
error("Failed %s type: %s", location, error$1.message);
|
||||
setCurrentlyValidatingElement(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var isArrayImpl = Array.isArray;
|
||||
function isArray(a) {
|
||||
return isArrayImpl(a);
|
||||
}
|
||||
function typeName(value) {
|
||||
{
|
||||
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
|
||||
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
|
||||
return type;
|
||||
}
|
||||
}
|
||||
function willCoercionThrow(value) {
|
||||
{
|
||||
try {
|
||||
testStringCoercion(value);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
function testStringCoercion(value) {
|
||||
return "" + value;
|
||||
}
|
||||
function checkKeyStringCoercion(value) {
|
||||
{
|
||||
if (willCoercionThrow(value)) {
|
||||
error("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.", typeName(value));
|
||||
return testStringCoercion(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
var RESERVED_PROPS = {
|
||||
key: true,
|
||||
ref: true,
|
||||
__self: true,
|
||||
__source: true
|
||||
};
|
||||
var specialPropKeyWarningShown;
|
||||
var specialPropRefWarningShown;
|
||||
var didWarnAboutStringRefs;
|
||||
{
|
||||
didWarnAboutStringRefs = {};
|
||||
}
|
||||
function hasValidRef(config) {
|
||||
{
|
||||
if (hasOwnProperty.call(config, "ref")) {
|
||||
var getter = Object.getOwnPropertyDescriptor(config, "ref").get;
|
||||
if (getter && getter.isReactWarning) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return config.ref !== void 0;
|
||||
}
|
||||
function hasValidKey(config) {
|
||||
{
|
||||
if (hasOwnProperty.call(config, "key")) {
|
||||
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
|
||||
if (getter && getter.isReactWarning) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return config.key !== void 0;
|
||||
}
|
||||
function warnIfStringRefCannotBeAutoConverted(config, self) {
|
||||
{
|
||||
if (typeof config.ref === "string" && ReactCurrentOwner.current && self && ReactCurrentOwner.current.stateNode !== self) {
|
||||
var componentName = getComponentNameFromType(ReactCurrentOwner.current.type);
|
||||
if (!didWarnAboutStringRefs[componentName]) {
|
||||
error('Component "%s" contains the string ref "%s". Support for string refs will be removed in a future major release. This case cannot be automatically converted to an arrow function. We ask you to manually fix this case by using useRef() or createRef() instead. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref', getComponentNameFromType(ReactCurrentOwner.current.type), config.ref);
|
||||
didWarnAboutStringRefs[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function defineKeyPropWarningGetter(props, displayName) {
|
||||
{
|
||||
var warnAboutAccessingKey = function() {
|
||||
if (!specialPropKeyWarningShown) {
|
||||
specialPropKeyWarningShown = true;
|
||||
error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
|
||||
}
|
||||
};
|
||||
warnAboutAccessingKey.isReactWarning = true;
|
||||
Object.defineProperty(props, "key", {
|
||||
get: warnAboutAccessingKey,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
function defineRefPropWarningGetter(props, displayName) {
|
||||
{
|
||||
var warnAboutAccessingRef = function() {
|
||||
if (!specialPropRefWarningShown) {
|
||||
specialPropRefWarningShown = true;
|
||||
error("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)", displayName);
|
||||
}
|
||||
};
|
||||
warnAboutAccessingRef.isReactWarning = true;
|
||||
Object.defineProperty(props, "ref", {
|
||||
get: warnAboutAccessingRef,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
var ReactElement = function(type, key, ref, self, source, owner, props) {
|
||||
var element = {
|
||||
// This tag allows us to uniquely identify this as a React Element
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
// Built-in properties that belong on the element
|
||||
type,
|
||||
key,
|
||||
ref,
|
||||
props,
|
||||
// Record the component responsible for creating this element.
|
||||
_owner: owner
|
||||
};
|
||||
{
|
||||
element._store = {};
|
||||
Object.defineProperty(element._store, "validated", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: false
|
||||
});
|
||||
Object.defineProperty(element, "_self", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: self
|
||||
});
|
||||
Object.defineProperty(element, "_source", {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: source
|
||||
});
|
||||
if (Object.freeze) {
|
||||
Object.freeze(element.props);
|
||||
Object.freeze(element);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
};
|
||||
function jsxDEV(type, config, maybeKey, source, self) {
|
||||
{
|
||||
var propName;
|
||||
var props = {};
|
||||
var key = null;
|
||||
var ref = null;
|
||||
if (maybeKey !== void 0) {
|
||||
{
|
||||
checkKeyStringCoercion(maybeKey);
|
||||
}
|
||||
key = "" + maybeKey;
|
||||
}
|
||||
if (hasValidKey(config)) {
|
||||
{
|
||||
checkKeyStringCoercion(config.key);
|
||||
}
|
||||
key = "" + config.key;
|
||||
}
|
||||
if (hasValidRef(config)) {
|
||||
ref = config.ref;
|
||||
warnIfStringRefCannotBeAutoConverted(config, self);
|
||||
}
|
||||
for (propName in config) {
|
||||
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
|
||||
props[propName] = config[propName];
|
||||
}
|
||||
}
|
||||
if (type && type.defaultProps) {
|
||||
var defaultProps = type.defaultProps;
|
||||
for (propName in defaultProps) {
|
||||
if (props[propName] === void 0) {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key || ref) {
|
||||
var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type;
|
||||
if (key) {
|
||||
defineKeyPropWarningGetter(props, displayName);
|
||||
}
|
||||
if (ref) {
|
||||
defineRefPropWarningGetter(props, displayName);
|
||||
}
|
||||
}
|
||||
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
|
||||
}
|
||||
}
|
||||
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner;
|
||||
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
function setCurrentlyValidatingElement$1(element) {
|
||||
{
|
||||
if (element) {
|
||||
var owner = element._owner;
|
||||
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null);
|
||||
ReactDebugCurrentFrame$1.setExtraStackFrame(stack);
|
||||
} else {
|
||||
ReactDebugCurrentFrame$1.setExtraStackFrame(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
var propTypesMisspellWarningShown;
|
||||
{
|
||||
propTypesMisspellWarningShown = false;
|
||||
}
|
||||
function isValidElement(object) {
|
||||
{
|
||||
return typeof object === "object" && object !== null && object.$$typeof === REACT_ELEMENT_TYPE;
|
||||
}
|
||||
}
|
||||
function getDeclarationErrorAddendum() {
|
||||
{
|
||||
if (ReactCurrentOwner$1.current) {
|
||||
var name = getComponentNameFromType(ReactCurrentOwner$1.current.type);
|
||||
if (name) {
|
||||
return "\n\nCheck the render method of `" + name + "`.";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function getSourceInfoErrorAddendum(source) {
|
||||
{
|
||||
if (source !== void 0) {
|
||||
var fileName = source.fileName.replace(/^.*[\\\/]/, "");
|
||||
var lineNumber = source.lineNumber;
|
||||
return "\n\nCheck your code at " + fileName + ":" + lineNumber + ".";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
var ownerHasKeyUseWarning = {};
|
||||
function getCurrentComponentErrorInfo(parentType) {
|
||||
{
|
||||
var info = getDeclarationErrorAddendum();
|
||||
if (!info) {
|
||||
var parentName = typeof parentType === "string" ? parentType : parentType.displayName || parentType.name;
|
||||
if (parentName) {
|
||||
info = "\n\nCheck the top-level render call using <" + parentName + ">.";
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
function validateExplicitKey(element, parentType) {
|
||||
{
|
||||
if (!element._store || element._store.validated || element.key != null) {
|
||||
return;
|
||||
}
|
||||
element._store.validated = true;
|
||||
var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
|
||||
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
|
||||
return;
|
||||
}
|
||||
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
|
||||
var childOwner = "";
|
||||
if (element && element._owner && element._owner !== ReactCurrentOwner$1.current) {
|
||||
childOwner = " It was passed a child from " + getComponentNameFromType(element._owner.type) + ".";
|
||||
}
|
||||
setCurrentlyValidatingElement$1(element);
|
||||
error('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.', currentComponentErrorInfo, childOwner);
|
||||
setCurrentlyValidatingElement$1(null);
|
||||
}
|
||||
}
|
||||
function validateChildKeys(node, parentType) {
|
||||
{
|
||||
if (typeof node !== "object") {
|
||||
return;
|
||||
}
|
||||
if (isArray(node)) {
|
||||
for (var i = 0; i < node.length; i++) {
|
||||
var child = node[i];
|
||||
if (isValidElement(child)) {
|
||||
validateExplicitKey(child, parentType);
|
||||
}
|
||||
}
|
||||
} else if (isValidElement(node)) {
|
||||
if (node._store) {
|
||||
node._store.validated = true;
|
||||
}
|
||||
} else if (node) {
|
||||
var iteratorFn = getIteratorFn(node);
|
||||
if (typeof iteratorFn === "function") {
|
||||
if (iteratorFn !== node.entries) {
|
||||
var iterator = iteratorFn.call(node);
|
||||
var step;
|
||||
while (!(step = iterator.next()).done) {
|
||||
if (isValidElement(step.value)) {
|
||||
validateExplicitKey(step.value, parentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function validatePropTypes(element) {
|
||||
{
|
||||
var type = element.type;
|
||||
if (type === null || type === void 0 || typeof type === "string") {
|
||||
return;
|
||||
}
|
||||
var propTypes;
|
||||
if (typeof type === "function") {
|
||||
propTypes = type.propTypes;
|
||||
} else if (typeof type === "object" && (type.$$typeof === REACT_FORWARD_REF_TYPE || // Note: Memo only checks outer props here.
|
||||
// Inner props are checked in the reconciler.
|
||||
type.$$typeof === REACT_MEMO_TYPE)) {
|
||||
propTypes = type.propTypes;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (propTypes) {
|
||||
var name = getComponentNameFromType(type);
|
||||
checkPropTypes(propTypes, element.props, "prop", name, element);
|
||||
} else if (type.PropTypes !== void 0 && !propTypesMisspellWarningShown) {
|
||||
propTypesMisspellWarningShown = true;
|
||||
var _name = getComponentNameFromType(type);
|
||||
error("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?", _name || "Unknown");
|
||||
}
|
||||
if (typeof type.getDefaultProps === "function" && !type.getDefaultProps.isReactClassApproved) {
|
||||
error("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
function validateFragmentProps(fragment) {
|
||||
{
|
||||
var keys = Object.keys(fragment.props);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
if (key !== "children" && key !== "key") {
|
||||
setCurrentlyValidatingElement$1(fragment);
|
||||
error("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.", key);
|
||||
setCurrentlyValidatingElement$1(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fragment.ref !== null) {
|
||||
setCurrentlyValidatingElement$1(fragment);
|
||||
error("Invalid attribute `ref` supplied to `React.Fragment`.");
|
||||
setCurrentlyValidatingElement$1(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
var didWarnAboutKeySpread = {};
|
||||
function jsxWithValidation(type, props, key, isStaticChildren, source, self) {
|
||||
{
|
||||
var validType = isValidElementType(type);
|
||||
if (!validType) {
|
||||
var info = "";
|
||||
if (type === void 0 || typeof type === "object" && type !== null && Object.keys(type).length === 0) {
|
||||
info += " You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.";
|
||||
}
|
||||
var sourceInfo = getSourceInfoErrorAddendum(source);
|
||||
if (sourceInfo) {
|
||||
info += sourceInfo;
|
||||
} else {
|
||||
info += getDeclarationErrorAddendum();
|
||||
}
|
||||
var typeString;
|
||||
if (type === null) {
|
||||
typeString = "null";
|
||||
} else if (isArray(type)) {
|
||||
typeString = "array";
|
||||
} else if (type !== void 0 && type.$$typeof === REACT_ELEMENT_TYPE) {
|
||||
typeString = "<" + (getComponentNameFromType(type.type) || "Unknown") + " />";
|
||||
info = " Did you accidentally export a JSX literal instead of a component?";
|
||||
} else {
|
||||
typeString = typeof type;
|
||||
}
|
||||
error("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s", typeString, info);
|
||||
}
|
||||
var element = jsxDEV(type, props, key, source, self);
|
||||
if (element == null) {
|
||||
return element;
|
||||
}
|
||||
if (validType) {
|
||||
var children = props.children;
|
||||
if (children !== void 0) {
|
||||
if (isStaticChildren) {
|
||||
if (isArray(children)) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
validateChildKeys(children[i], type);
|
||||
}
|
||||
if (Object.freeze) {
|
||||
Object.freeze(children);
|
||||
}
|
||||
} else {
|
||||
error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");
|
||||
}
|
||||
} else {
|
||||
validateChildKeys(children, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
if (hasOwnProperty.call(props, "key")) {
|
||||
var componentName = getComponentNameFromType(type);
|
||||
var keys = Object.keys(props).filter(function(k) {
|
||||
return k !== "key";
|
||||
});
|
||||
var beforeExample = keys.length > 0 ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
|
||||
if (!didWarnAboutKeySpread[componentName + beforeExample]) {
|
||||
var afterExample = keys.length > 0 ? "{" + keys.join(": ..., ") + ": ...}" : "{}";
|
||||
error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', beforeExample, componentName, afterExample, componentName);
|
||||
didWarnAboutKeySpread[componentName + beforeExample] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type === REACT_FRAGMENT_TYPE) {
|
||||
validateFragmentProps(element);
|
||||
} else {
|
||||
validatePropTypes(element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
var jsxDEV$1 = jsxWithValidation;
|
||||
exports.Fragment = REACT_FRAGMENT_TYPE;
|
||||
exports.jsxDEV = jsxDEV$1;
|
||||
})();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/react/jsx-dev-runtime.js
|
||||
var require_jsx_dev_runtime = __commonJS({
|
||||
"node_modules/react/jsx-dev-runtime.js"(exports, module) {
|
||||
if (false) {
|
||||
module.exports = null;
|
||||
} else {
|
||||
module.exports = require_react_jsx_dev_runtime_development();
|
||||
}
|
||||
}
|
||||
});
|
||||
export default require_jsx_dev_runtime();
|
||||
/*! Bundled license information:
|
||||
|
||||
react/cjs/react-jsx-dev-runtime.development.js:
|
||||
(**
|
||||
* @license React
|
||||
* react-jsx-dev-runtime.development.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
*/
|
||||
//# sourceMappingURL=react_jsx-dev-runtime.js.map
|
||||
File diff suppressed because one or more lines are too long
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
|
||||
else
|
||||
exec node "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.cjs" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/baseline-browser-mapping.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../browserslist/cli.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/browserslist.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../browserslist/cli.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../browserslist/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
|
||||
else
|
||||
exec node "$basedir/../esbuild/bin/esbuild" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/esbuild.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
|
||||
else
|
||||
exec node "$basedir/../jsesc/bin/jsesc" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/jsesc.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../jsesc/bin/jsesc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../json5/lib/cli.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/json5.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../json5/lib/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../loose-envify/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../loose-envify/cli.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\loose-envify\cli.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/loose-envify.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../loose-envify/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../loose-envify/cli.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../loose-envify/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
|
||||
else
|
||||
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/nanoid.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/parser.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
|
||||
else
|
||||
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/rollup.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../semver/bin/semver.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../semver/bin/semver.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/semver.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../semver/bin/semver.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../semver/bin/semver.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../semver/bin/semver.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsc" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsc.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../typescript/bin/tsc" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\typescript\bin\tsserver" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/tsserver.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../typescript/bin/tsserver" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../update-browserslist-db/cli.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../update-browserslist-db/cli.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\update-browserslist-db\cli.js" %*
|
||||
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db.ps1
generated
vendored
Normal file
28
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/update-browserslist-db.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../update-browserslist-db/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../update-browserslist-db/cli.js" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../update-browserslist-db/cli.js" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/vite
generated
vendored
Normal file
16
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/vite
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
|
||||
else
|
||||
exec node "$basedir/../vite/bin/vite.js" "$@"
|
||||
fi
|
||||
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/vite.cmd
generated
vendored
Normal file
17
rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/ui/node_modules/.bin/vite.cmd
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue