diff --git a/about.html b/about.html index 9a72ef5..20edd2d 100644 --- a/about.html +++ b/about.html @@ -1,93 +1,159 @@ + - About - MEDWEAR + About | MEDWEAR - + + +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
+ MEDWEAR
-
-
-
-

About the Project

-

Wearable devices are rapidly transforming healthcare by enabling continuous, real-time monitoring of patients outside clinical settings. MEDWEAR is a community-driven initiative to create open standards for structuring and exchanging high-frequency data from medical wearables.

-

We aim to address the fragmentation of the digital health ecosystem by building upon existing standardsβ€”such as Open mHealth, HL7 FHIR, IEEE 11073, and openEHRβ€”to define lightweight, extensible schemas suitable for modern sensing devices and middleware systems like ROS 2 and MQTT.

-
-
-

Why Standardization Matters

-

Although wearable technologies have become integral to modern healthcareβ€”supporting use cases from chronic disease management to preventive careβ€”the lack of common data formats hinders integration with EHRs, AI systems, and clinical decision tools. Over 80% of healthcare data remains unstructured and difficult to reuse.

-

Standardizing wearable data supports interoperability, clinical utility, patient safety, and research reproducibility. MEDWEAR provides schemas and APIs that support high-frequency data, real-time streaming, and device-agnostic modeling to facilitate broad adoption and integration.

-
+
+
+

About MEDWEAR

+

Community-driven standards for wearable health data

+
+
+ +
+
+ +
+

About the Project

+

+ Wearable devices are rapidly transforming healthcare by enabling continuous, real-time monitoring + of patients outside clinical settings. MEDWEAR is a community-driven initiative funded by ETH Zurich's + Open Research Data initiative to create open standards for structuring and exchanging high-frequency + data from medical wearables. +

+

+ We address the fragmentation of the digital health ecosystem by building on existing standards - + Open mHealth, HL7 FHIR, IEEE 11073, and openEHR - to define lightweight, extensible schemas + suitable for modern sensing devices and middleware systems like ROS 2 and MQTT. +

+
-
-

Collaborating Entities

-
    -
  • ETH Zurich - Open Research Data Initiative
  • -
  • SCAI Lab, ETH Zurich
  • -
  • DART Lab, Lake Lucerne Institute
  • -
  • UZH
  • -
  • SUPSI
  • -
-
+
+

Why Standardization Matters

+

+ Although wearable technologies have become integral to modern healthcare - supporting use cases + from chronic disease management to preventive care - the lack of common data formats hinders + integration with EHRs, AI systems, and clinical decision tools. Over 80% of healthcare data + remains unstructured and difficult to reuse. +

+

+ Standardizing wearable data supports interoperability, clinical utility, patient safety, and + research reproducibility. MEDWEAR provides schemas and APIs that support high-frequency data, + real-time streaming, and device-agnostic modeling. +

+

Key challenges addressed

+
    +
  • Fragmented systems with incompatible data formats
  • +
  • Lack of EHR integration
  • +
  • Data privacy and regulatory constraints
  • +
  • Barriers to real-time, high-frequency data streaming
  • +
  • Limited clinical adoption and ROI evidence
  • +
+
-
-

Challenges in Wearable Health Data

-
    -
  • Fragmented systems with incompatible formats
  • -
  • Lack of EHR integration
  • -
  • Data privacy and regulatory constraints
  • -
  • Low patient engagement and digital divide
  • -
  • Limited clinical adoption and ROI evidence
  • -
  • Barriers to real-time, high-frequency data streaming
  • -
-
+
+

Standards & Design Principles

+

+ MEDWEAR schemas follow the + IEEE 1752-2021 + Standard + and are compatible with the Open + mHealth + framework, extending both to support high-frequency raw sensor streams. +

+
    +
  • Vendor-agnostic - no dependency on proprietary device formats.
  • +
  • FAIR - Findable, Accessible, Interoperable, and Reusable.
  • +
  • Modular - signal schemas are self-contained and composable.
  • +
  • Middleware-ready - definitions ship as both JSON Schemas and ROS 2 .msg + files.
  • +
  • Extensible - new signal types can be added without breaking existing consumers.
  • +
-
-

Comparative Overview of Standards

-

We evaluate four key initiatives that inspire the MEDWEAR schema design:

+

Relationship to existing standards

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StandardScopeMEDWEAR relationship
Open mHealthStructured JSON schemas for health dataBase schema format; MEDWEAR extends it for raw, high-frequency signals
IEEE + 1752.1-2021Mobile health data quality and interoperabilityNormative reference for schema design and quality requirements
HL7 FHIRClinical data exchange (EHR integration)Target interoperability layer for clinical deployment
openEHRSemantic clinical modelsReference for clinical semantic precision
IEEE 11073-PHD + Personal health device communicationInforms device-level metadata modeling
+
-
    -
  • Open mHealth: Simple, modular JSON schemas for structured, contextual data.
  • -
  • HL7 FHIR: Granular RESTful resources widely used in clinical settings.
  • -
  • IEEE 11073-PHD: Device-specific standards for personal health devices, well-suited for discrete measures.
  • -
  • openEHR: Robust clinical models with semantic precision.
  • -
+
+

Collaborating Institutions

+
    +
  • ETH Zurich - Open Research Data Initiative
  • +
  • SCAI Lab, ETH Zurich
  • +
  • DART Lab, Lake Lucerne Institute
  • +
  • University of Zurich (UZH)
  • +
  • SUPSI
  • +
+
-
-
-
-
-

Β© 2025 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch

- + + + - + + \ No newline at end of file diff --git a/assets/images/dart_logo.png b/assets/images/dart_logo.png deleted file mode 100644 index 82021ce..0000000 Binary files a/assets/images/dart_logo.png and /dev/null differ diff --git a/assets/images/ethz_logo.png b/assets/images/ethz_logo.png old mode 100644 new mode 100755 index bc07b3f..0d3c5be Binary files a/assets/images/ethz_logo.png and b/assets/images/ethz_logo.png differ diff --git a/assets/images/hero-about.jpg b/assets/images/hero-about.jpg new file mode 100755 index 0000000..9ca5b9c Binary files /dev/null and b/assets/images/hero-about.jpg differ diff --git a/assets/images/hero-home.jpg b/assets/images/hero-home.jpg new file mode 100644 index 0000000..29f6b27 Binary files /dev/null and b/assets/images/hero-home.jpg differ diff --git a/assets/images/hero-schemas.jpg b/assets/images/hero-schemas.jpg new file mode 100755 index 0000000..adeb714 Binary files /dev/null and b/assets/images/hero-schemas.jpg differ diff --git a/contribute.html b/contribute.html index 76ff4dd..0d65d64 100644 --- a/contribute.html +++ b/contribute.html @@ -6,60 +6,93 @@ Contribute | MEDWEAR + + +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
+ MEDWEAR
+
+
+

Contribute

+

Help shape open wearable data standards

+
+
+
-
-

Contribute to MEDWEAR

-

- MEDWEAR is a community-driven project that brings together researchers, industry experts, and developers to create open standards everyone can use. -

-

- We welcome contributions to improve the schemas, tools, and documentation. Your input helps shape interoperable wearable data standards that can accelerate digital health innovation. -

+
+
+

Contribute to MEDWEAR

+

+ MEDWEAR is a community-driven project that brings together researchers, industry experts, + and developers to create open standards everyone can use. + We welcome contributions to the schemas, ROS messages, platform, and documentation. +

+
-

How to Contribute

-
    -
  1. Fork our GitHub repository.
  2. -
  3. Make your changes or additions in your fork, including detailed justifications and documentation.
  4. -
  5. Submit a Pull Request describing your changes clearly.
  6. -
  7. We will review your submission and provide feedback.
  8. -
+
+

Choose what to contribute to

+
-
- + + - + + \ No newline at end of file diff --git a/documentation.html b/documentation.html index 25195e7..081c969 100644 --- a/documentation.html +++ b/documentation.html @@ -6,48 +6,78 @@ Documentation | MEDWEAR - +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
- + MEDWEAR
-
-
-
-

Usage Examples

+
+
+

Documentation

+

Guides for using MEDWEAR schemas and the data collection platform

+
+
+ +
+
+ + -

Using MEDWEAR JSON Schemas

-

The schemas define the data structure for wearable sensor data. You can validate your data against these schemas to ensure interoperability.

+ +

Using the Data Schemas

-

Example: ECG Data JSON

-
{
+      
+

Integration Options

+

+ MEDWEAR schemas are available in two formats. Choose based on your application: + JSON Schemas for general data storage and validation, or ROS 2 messages for + real-time streaming and robotics middleware. +

+ +
+ +
+
+ Option A +

JSON Schemas

+

Storage Β· validation Β· non-ROS applications

+
+
+

When to use

+

Your application stores, validates, or processes wearable data outside a ROS environment β€” e.g. a web + backend, mobile app, or data pipeline.

+

Install

+
git clone https://github.com/SCAI-Lab/ord_schemas
+

Schema structure

+

Each schema defines a physiological signal. Minimal ECG example:

+
{
   "schema_id": "org.medwear.biosignal.ecg",
   "version": "1.0",
   "data": {
-    "ecg": [0.12, 0.15, 0.11, ...],
+    "ecg": [0.12, 0.15, 0.11],
     "sample_rate": 250,
     "units": "mV"
   },
@@ -56,89 +86,331 @@ 

Example: ECG Data JSON

"timestamp": "2025-06-30T15:30:00Z" } }
- -

Loading MEDWEAR Schemas in Python

-
import json
+              

Validate in Python

+
import json
 from jsonschema import validate
 
-with open('ecg_schema.json') as schema_file:
-    schema = json.load(schema_file)
-
-with open('ecg_data.json') as data_file:
-    data = json.load(data_file)
+with open('ecg_schema.json') as f:
+    schema = json.load(f)
+with open('ecg_data.json') as f:
+    data = json.load(f)
 
 validate(instance=data, schema=schema)
-print("Data is valid according to MEDWEAR ECG schema.")
- -

Further Resources

- -
- -
-

Documentation & Resources

- -

πŸ“š User Guides

- - -

πŸ“„ JSON Schemas

-

- Our schemas extend Open mHealth to include raw sensor formats.
- View all schemas on a dedicated page | View on GitHub -

- -

πŸ€– ROS 2 Messages

-

- Download ROS 2 `.msg` definitions for integration with healthcare robots and assistive devices.
- View ROS 2 message package -

- -

πŸ” Conversion Tools

-

- Command-line tools and notebooks to convert CSV/JSON/wearable data to MEDWEAR-compliant format.
- Explore conversion tools -

- -

πŸ“Š Example Datasets

-

- Sample anonymized datasets for development and testing.
- Download examples -

- -

🧠 Publications

-
    -
  • "MEDWEAR: Data Standardization of Wearable Devices for Healthcare Applications" (in preparation)
  • -
  • Symposium [PDF]
  • -
- -

πŸ› οΈ Contribute

-

- Found a bug or want to add support for new sensors?
- See our contributing guide. -

- -

❓ FAQs

-

- Coming soon: Frequently asked questions about MEDWEAR schemas and implementation. -

-
-
-
+print("Data is valid.") +

Resources

+

+ Browse schemas on the Schemas page or download the full set from + GitHub. +

+
+ + +
+
+ Option B +

ROS 2 Messages

+

Real-time streaming Β· robotics

+
+
+

When to use

+

Your application uses ROS 2 β€” e.g. a healthcare robot, assistive device, or real-time signal processing + pipeline that publishes sensor data as ROS topics.

+

Package

+

+ The healthcare_msgs package provides .msg definitions for all + MEDWEAR signal types. Debian packages are available for Humble, Jazzy, and Rolling on + amd64 and arm64. Supported signals: +

+
+ ECG + BCG + PPG + EEG + EDA + IMU + HR + HRV +
+

Install via APT

+

Replace humble / amd64 / jammy with your distro and + architecture:

+
# Add GPG key
+sudo curl -fsSL \
+  https://scai-lab.github.io/healthcare_msgs/humble-amd64/KEY.gpg \
+  -o /etc/apt/trusted.gpg.d/healthcare-msgs.gpg
+
+# Add APT source
+echo "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/healthcare-msgs.gpg] \
+  https://scai-lab.github.io/healthcare_msgs/humble-amd64 humble-jammy main" \
+  | sudo tee /etc/apt/sources.list.d/healthcare-msgs.list
+
+# Install
+sudo apt update && sudo apt install ros-humble-healthcare-msgs
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ROS distroOSArchitectures
HumbleUbuntu 22.04 Jammyamd64, arm64
JazzyUbuntu 24.04 Nobleamd64, arm64
RollingUbuntu 24.10 Resoluteamd64, arm64
+

Resources

+

+ View the full + package on GitHub +

+
+
+ + + + +
+

Streaming & Conversion Tools

+

+ Command-line tools and notebooks are available to convert CSV, JSON, and raw wearable files + into MEDWEAR-compliant format. You can also use the provided libraries to stream the messages in MQTT(S), UDP, + websockets or BLE. These tools are ideal for testing, prototyping, and integrating MEDWEAR schemas into + existing workflows. +

+

Explore conversion + tools on GitHub

+
+ +
+

Resources

+ +

External References

+ +
+ + +

Using the MEDWEAR Data Collection Platform

+ +
+

Overview

+

+ MED-API is the backend service of the MEDWEAR platform - a secure REST API for ingesting, + querying, and managing wearable health data. It runs alongside MinIO, InfluxDB, PostgreSQL, + Keycloak, Dagster, Grafana, and pgAdmin as a single-server Docker stack. + For the full API reference, see the MED-API page. +

+
+ +
+

Installation

+

Clone the MEDWEAR platform repository from GitHub:

+
git clone https://github.com/SCAI-Lab/medwear
+cd medwear
+
+ +
+

Quick Setup

+
./setup.sh
+

This will automatically:

+ +

After setup, view your credentials:

+
cat .credentials.txt
+
PostgreSQL     : medwear / xK7mP2qR9nL4wY8j
+MinIO root     : minioadmin / hT3vB6cN1mX5pQ2r
+MinIO API key  : medwear-api / dF8yA4kW7nJ2mR6s...
+Keycloak admin : admin / hJ9nQ3vB6cN1mX5p
+InfluxDB       : admin / wY8jxK7mP2qR9nL4
+Grafana        : admin / T3vB6cN1mX5pQ2rd
+pgAdmin        : admin@medwear.local / F8yA4kW7nJ2mR6s
+

Note: Do not commit .credentials.txt to version control. It is already listed + in .gitignore.

+
+ +
+

Start & Stop

+
# First time or after code changes
+docker compose -f docker-compose.medwear-minimal.yml build
+docker compose -f docker-compose.medwear-minimal.yml up -d
+
+# Subsequent runs (no code changes)
+docker compose -f docker-compose.medwear-minimal.yml up -d
+

After starting, Keycloak takes ~30 seconds to be ready.

+
# Restart a single service (e.g. after API code change)
+docker compose -f docker-compose.medwear-minimal.yml restart med-api
+
+# Stop all services
+docker compose -f docker-compose.medwear-minimal.yml down
+
+ +
+

Service Access

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceURLCredentials
MED-APIhttps://localhost/v1/healthToken required
API Docshttps://localhost/docs-
Frontendhttps://localhostKeycloak login
Keycloakhttp://localhost:8180/adminadmin / see .credentials.txt
pgAdminhttp://localhost:5050admin@medwear.com / see .credentials.txt
InfluxDBhttp://localhost:8086admin / see .credentials.txt
MinIOhttp://localhost:9001minioadmin / see .credentials.txt
Grafanahttp://localhost:3001admin / see .credentials.txt
Dagsterhttp://localhost:3000-
+

The MedWear platform is deployed at https://172.20.83.11. Frontend and API are accessible + directly. Admin services require SSH tunnel or direct server access.

+
+ Remote access via SSH tunnel +
ssh -L 443:localhost:443 \
+    -L 80:localhost:80 \
+    -L 8180:localhost:8180 \
+    -L 9001:localhost:9001 \
+    -L 8086:localhost:8086 \
+    -L 3001:localhost:3001 \
+    -L 5050:localhost:5050 \
+    user@your-server.com
+
+
+ +
+

Authentication

+

All endpoints require a Keycloak JWT token.

+
+ Get a token via API proxy (Recommended) +
curl -k -X POST https://localhost/v1/data/auth/token \
+  -H "Content-Type: application/json" \
+  -d '{"username":"<user>","password":"<pass>"}'
+
+
+ Get a token directly from Keycloak (Internal/testing only) +
curl -X POST http://localhost:8180/realms/medwear/protocol/openid-connect/token \
+  -H "Content-Type: application/x-www-form-urlencoded" \
+  -d "grant_type=password&client_id=med-api&client_secret=<secret>&username=<user>&password=<pass>"
+
+
+ Use the token +
curl -k -H "Authorization: Bearer <access_token>" https://localhost/v1/me
+
+
+ Health check +
curl -k https://localhost/v1/health
+
+
+ +
+

Testing

+
cd /path/to/repo/medwear_med_api
+source venv/bin/activate
+
+python3 scripts/test_phase3.py   # Batch upload, CSV, validation
+python3 scripts/test_phase4.py   # Query, delete, restore, admin, multi-project
+python3 scripts/test_phase5.py   # Error handling: 46/46 tests
+
+ Quick smoke test +
# 1. Get a token
+TOKEN=$(curl -sk -X POST https://localhost/v1/data/auth/token \
+  -H "Content-Type: application/json" \
+  -d '{"username":"<your-username>","password":"<your-password>"}' | jq -r .access_token)
+
+# 2. Check health
+curl -sk https://localhost/v1/health | jq .
+
+# 3. Query recent batches
+curl -sk -H "Authorization: Bearer $TOKEN" \
+  "https://localhost/v1/data?limit=5" | jq .
+
+
+ +
+

Backup

+

Backs up PostgreSQL, InfluxDB, and MinIO to a compressed archive. Backups older than 30 days are deleted + automatically.

+
# Run manually
+./scripts/backup.sh /opt/medwear_backups
+
+# Add to crontab (daily at 2 AM)
+0 2 * * * /path/to/med-api/scripts/backup.sh /opt/medwear_backups >> /var/log/medwear_backup.log 2>&1
+
+ + + + - + \ No newline at end of file diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..4049b1f --- /dev/null +++ b/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + M + diff --git a/index.html b/index.html index adb7f75..0768dd5 100644 --- a/index.html +++ b/index.html @@ -1,316 +1,111 @@ + - + MEDWEAR: Open Standard for Wearable Health Data - + + +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
+ MEDWEAR
-
-
-
-

Welcome to MEDWEAR

-

- MEDWEAR is a collaborative project funded by ETH Zurich’s Open Research Data initiative. - We aim to define open standards for raw wearable sensor data that are vendor-agnostic and FAIR (Findable, Accessible, Interoperable, and Reusable). -

-

- Through community-driven development, we are creating: +

+
+

Open Standards for
Medical Wearables

+

Vendor-agnostic, FAIR-compliant schemas for the next generation of wearable health data.

+ +
+
+ +
+
+ +
+

What is MEDWEAR?

+

+ MEDWEAR is a collaborative project funded by Open Research Data of the ETH Board. + We define open standards for raw wearable sensor data that are vendor-agnostic and + FAIR (Findable, Accessible, Interoperable, and Reusable). +

+

Through community-driven development, we are creating:

    -
  • Expanding Open mHealth schemas (based on IEEE 1752).
  • -
  • Online conversion to ROS 2 message for robotic applications.
  • -
  • Libraries for real time data stream support and educational materials.
  • +
  • A data collection platform and REST API for managing wearable health data at scale.
  • +
  • Expanded Open mHealth schemas based on IEEE 1752, covering high-frequency biosignals.
  • +
  • Conversion tools from JSON schemas to ROS 2 message definitions for robotic applications.
  • +
  • Libraries for real-time data stream support and educational materials.
-

-
-
-

Installation

-

To use MEDWEAR schemas and libraries, clone our repository (fork from Open mHealth) from GitHub:

-
git clone https://github.com/SCAI-Lab/ord_schemas
-# Follow further instructions in README.md
-
- -
-

Ballistocardiogram (BCG)

-

- The BCG schema represents waveform data from a specific lead with signal quality and temporal context. - The BCGInfo schema describes metadata about the sensor and acquisition parameters. -

- -

JSON Schemas

-
- BCG Schema -
{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "BCG Data",
-  "description": "This schema represents bcg waveform data from a specific lead with signal quality and temporal context",
-  "type": "object",
-  "definitions": {
-    "time_frame": { "$ref": "time-frame-1.0.json" },
-    "time_interval": { "$ref": "time-interval-1.0.json" }
-  },
-  "properties": {
-    "session_id": {
-      "type": "string",
-      "description": "Session identifier matching BCGInfo's session_id"
-    },
-    "sample_size": {
-      "type": "integer",
-      "minimum": 0,
-      "maximum": 65535,
-      "description": "Number of samples in bcg and signal_quality arrays"
-    },
-    "bcg": {
-      "type": "array",
-      "items": { "type": "number" },
-      "description": "Raw or filtered BCG values"
-    },
-    "signal_quality": {
-      "type": "array",
-      "items": {
-        "type": "number",
-        "minimum": 0,
-        "maximum": 1
-      },
-      "description": "Signal quality values from 0 (poor) to 1 (excellent)"
-    }
-  },
-  "required": ["session_id", "sample_size", "bcg", "signal_quality"]
-}
-
- -
- BCGInfo Schema -
{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "BCGInfo",
-  "description": "Metadata describing BCG data acquisition parameters and sensor characteristics.",
-  "type": "object",
-  "definitions": {
-    "device_info": {
-      "type": "object",
-      "description": "Device information details",
-      "properties": {
-        "manufacturer": { "type": "string" },
-        "model": { "type": "string" },
-        "serial_number": { "type": "string" }
-      },
-      "required": ["manufacturer", "model"]
-    },
-    "filter_base": {
-      "type": "object",
-      "description": "Filter metadata",
-      "properties": {
-        "filter_type": { "type": "string" },
-        "cutoff_frequency_hz": { "type": "number" },
-        "order": { "type": "integer" }
-      }
-    }
-  },
-  "properties": {
-    "device_info": { "$ref": "#/definitions/device_info" },
-    "session_id": {
-      "type": "string",
-      "description": "Session identifier linking to BCG data"
-    },
-    "sampling_frequency": {
-      "type": "number",
-      "minimum": 0,
-      "description": "Sampling frequency in Hz"
-    },
-    "window_size_samples": {
-      "type": "integer",
-      "minimum": 1,
-      "description": "Number of samples per BCG message"
-    },
-    "quality_window_size_samples": {
-      "type": "integer",
-      "minimum": 1,
-      "description": "Number of samples per quality value"
-    },
-    "units": {
-      "type": "string",
-      "enum": ["UNIT_UNKNOWN", "UNIT_MV", "UNIT_UV", "UNIT_COUNTS", "UNIT_M_S2", "UNIT_NEWTON"],
-      "description": "Units for the BCG data"
-    },
-    "gain": {
-      "type": "number",
-      "description": "Multiplicative factor (e.g., Β΅V/count); set to 1.0 if signal is already scaled"
-    },
-    "adc_resolution": {
-      "type": "integer",
-      "minimum": 1,
-      "maximum": 32,
-      "description": "ADC resolution in bits"
-    },
-    "sensor_placement": {
-      "type": "string",
-      "enum": [
-        "PLACEMENT_UNKNOWN", "PLACEMENT_CHEST", "PLACEMENT_BACK", "PLACEMENT_MATTRESS",
-        "PLACEMENT_CHAIR", "PLACEMENT_ANKLE", "PLACEMENT_FOOT", "PLACEMENT_WRIST",
-        "PLACEMENT_ARM", "PLACEMENT_DESK", "PLACEMENT_CLOTHING", "PLACEMENT_CUSTOM"
-      ],
-      "description": "Physical placement of the sensor"
-    },
-    "sensing_direction": {
-      "type": "string",
-      "enum": [
-        "DIRECTION_UNKNOWN", "DIRECTION_DORSO_VENTRAL", "DIRECTION_LONGITUDINAL",
-        "DIRECTION_MEDIO_LATERAL", "DIRECTION_CUSTOM"
-      ],
-      "description": "Direction of sensing axis"
-    },
-    "sensing_modality": {
-      "type": "string",
-      "enum": [
-        "MODALITY_UNKNOWN", "MODALITY_PIEZOELECTRIC_FILM", "MODALITY_FORCEPLATE",
-        "MODALITY_ACCELEROMETER", "MODALITY_PIEZOELECTRIC_CRYSTAL", "MODALITY_CUSTOM"
-      ],
-      "description": "Sensor modality"
-    },
-    "measurement_system": {
-      "type": "string",
-      "enum": ["SYSTEM_UNKNOWN", "SYSTEM_STARR", "SYSTEM_NICKERSON", "SYSTEM_DOCK", "SYSTEM_CUSTOM"],
-      "description": "Measurement system used"
-    },
-    "filter": { "$ref": "#/definitions/filter_base" }
-  },
-  "required": [
-    "device_info", "session_id", "sampling_frequency", "window_size_samples",
-    "quality_window_size_samples", "units", "gain", "adc_resolution",
-    "sensor_placement", "sensing_direction", "sensing_modality", "measurement_system"
-  ]
-}
-
- -

ROS 2 Message Definitions

-
- BCG.msg -
std_msgs/Header header
-
-string session_id                     # Match BCGInfo's session_id
-
-uint16 sample_size
-
-float32[] bcg                         # Raw or filtered BCG values
-float32[] signal_quality              # From 0 (poor) to 1 (excellent)
-
-
- -
- BCGInfo.msg -
DeviceInfo device_info
-
-string session_id
-
-float32 sampling_frequency            # Hz
-uint32 window_size_samples            # Number of samples per BCG.msg
-uint32 quality_window_size_samples    # Number of samples per quality value
-
-# Units Enum
-uint8 UNIT_UNKNOWN = 0
-uint8 UNIT_MV = 1                    # Millivolts (less common for BCG)
-uint8 UNIT_UV = 2                    # Microvolts (biopotential signals, maybe less common for BCG)
-uint8 UNIT_COUNTS = 3                # Raw ADC counts
-uint8 UNIT_M_S2 = 4                  # Meters per second squared
-uint8 UNIT_NEWTON = 5                # Newtons (force)
-
-uint8 units                          # e.g., UNIT_MV
-float32 gain                         # Multiplicative factor
-uint8 adc_resolution                 # Bits (e.g., 12, 16)
-
-# Sensor Placement Enum
-uint8 PLACEMENT_UNKNOWN = 0
-uint8 PLACEMENT_CHEST = 1
-uint8 PLACEMENT_BACK = 2
-uint8 PLACEMENT_MATTRESS = 3
-uint8 PLACEMENT_CHAIR = 4
-uint8 PLACEMENT_ANKLE = 5
-uint8 PLACEMENT_FOOT = 6
-uint8 PLACEMENT_WRIST = 7
-uint8 PLACEMENT_ARM = 8
-uint8 PLACEMENT_DESK = 9
-uint8 PLACEMENT_CLOTHING = 10
-uint8 PLACEMENT_CUSTOM = 255
-
-uint8 sensor_placement
-
-# Sensing Direction Enum
-uint8 DIRECTION_UNKNOWN = 0
-uint8 DIRECTION_DORSO_VENTRAL = 1      # Front to back
-uint8 DIRECTION_LONGITUDINAL = 2       # Head to toe
-uint8 DIRECTION_MEDIO_LATERAL = 3      # Side to side
-uint8 DIRECTION_CUSTOM = 255
-
-uint8 sensing_direction
-
-# Sensing Modality Enum
-uint8 MODALITY_UNKNOWN = 0
-uint8 MODALITY_PIEZOELECTRIC_FILM = 1
-uint8 MODALITY_FORCEPLATE = 2
-uint8 MODALITY_ACCELEROMETER = 3
-uint8 MODALITY_PIEZOELECTRIC_CRYSTAL = 4
-uint8 MODALITY_CUSTOM = 255
-
-uint8 sensing_modality
-
-# Measurement System Enum
-uint8 SYSTEM_UNKNOWN = 0
-uint8 SYSTEM_STARR = 1
-uint8 SYSTEM_NICKERSON = 2
-uint8 SYSTEM_DOCK = 3
-uint8 SYSTEM_CUSTOM = 255
-
-uint8 measurement_system
-
-FilterBase filter
-
-
-
+
+ +
+

Explore MEDWEAR

+ +
+ +
+

Recent Highlights

+
    +
  • πŸ€– BioRob 2026 + Workshop MED-ROB - Medical Wearables for Robotics. 1st August, 2026 in Edmonton, Canada
  • +
  • πŸ₯ Symposium: "Toward the Next Standard for Medical Wearables in Switzerland. 6th May, 2025 @ ETH Zurich" +
  • +
  • πŸ“Š E-Survey with 31 participants from academia, clinics, and industry.
  • +
  • πŸ›  GitHub repository containing new physiological signal data schemas.
  • +
+
-
-

Recent Highlights

-
    -
  • πŸ₯ Symposium: "Toward the Next Standard for Medical Wearables in Switzerland."
  • -
  • πŸ“Š E-Survey with 31 participants from academia, clinics, and industry.
  • -
  • πŸ›  GitHub repo containing new data schemas.
  • -
-
-
-
- + + diff --git a/med-api.html b/med-api.html index 1a01c6a..b21ccd8 100644 --- a/med-api.html +++ b/med-api.html @@ -1,190 +1,148 @@ + - MED-API - MEDWEAR + MED-API | MEDWEAR - + +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
+ MEDWEAR
-
-
- -
-

MED-API

-

- MED-API is the backend service of the MEDWEAR platform. It provides a secure REST API for ingesting, - querying, and managing wearable health data. It is deployed alongside MinIO, InfluxDB, PostgreSQL, - Keycloak, Dagster, Grafana, and pgAdmin as a single-server stack. -

-

Full documentation is available on Google Drive:

-

β†’ Open Full Documentation

-
- -
-

Quick Setup

-
./setup.sh
-

This will automatically:

-
    -
  • Generate random secure credentials for all services (PostgreSQL, MinIO, Grafana, Keycloak, InfluxDB)
  • -
  • Create .env and .env.medwear files
  • -
  • Start all Docker services
  • -
  • Initialize InfluxDB and PostgreSQL tables
  • -
  • Save all generated credentials to .credentials.txt
  • -
-

After setup, view your credentials:

-
cat .credentials.txt
-
PostgreSQL     : medwear / xK7mP2qR9nL4wY8j
-MinIO root     : minioadmin / hT3vB6cN1mX5pQ2r
-MinIO API key  : medwear-api / dF8yA4kW7nJ2mR6s...
-Keycloak admin : admin / hJ9nQ3vB6cN1mX5p
-InfluxDB       : admin / wY8jxK7mP2qR9nL4
-Grafana        : admin / T3vB6cN1mX5pQ2rd
-pgAdmin        : admin@medwear.local / F8yA4kW7nJ2mR6s
-

Note: Do not commit .credentials.txt to version control. It is already listed in .gitignore.

-
- -
-

Start & Stop

-
# First time or after code changes
-docker compose -f docker-compose.medwear-minimal.yml build
-docker compose -f docker-compose.medwear-minimal.yml up -d
-
-# Subsequent runs (no code changes)
-docker compose -f docker-compose.medwear-minimal.yml up -d
-

After starting, Keycloak takes ~30 seconds to be ready.

-
# Restart a single service (e.g. after API code change)
-docker compose -f docker-compose.medwear-minimal.yml restart med-api
-
-# Stop all services
-docker compose -f docker-compose.medwear-minimal.yml down
-
- -
-

Access

- - - - - - - - - - - - - - - -
ServiceURLCredentials
MED-APIhttps://localhost/v1/healthToken required
API Docshttps://localhost/docsβ€”
Frontendhttps://localhostKeycloak login
Keycloakhttp://localhost:8180/adminadmin / see .credentials.txt
pgAdminhttp://localhost:5050admin@medwear.com / see .credentials.txt
InfluxDBhttp://localhost:8086admin / see .credentials.txt
MinIOhttp://localhost:9001minioadmin / see .credentials.txt
Grafanahttp://localhost:3001admin / see .credentials.txt
Dagsterhttp://localhost:3000β€”
-

The MedWear platform is deployed at https://172.20.83.11. Frontend and API are accessible directly. Admin services require SSH tunnel or direct server access.

-
- Remote access via SSH tunnel -
ssh -L 443:localhost:443 \
-    -L 80:localhost:80 \
-    -L 8180:localhost:8180 \
-    -L 9001:localhost:9001 \
-    -L 8086:localhost:8086 \
-    -L 3001:localhost:3001 \
-    -L 5050:localhost:5050 \
-    user@your-server.com
-
-
- -
-

Authentication

-

All endpoints require a Keycloak JWT token.

-
- Get a token (via API proxy β€” recommended) -
curl -k -X POST https://localhost/v1/data/auth/token \
-  -H "Content-Type: application/json" \
-  -d '{"username":"<user>","password":"<pass>"}'
-
-
- Get a token (direct Keycloak β€” for internal/testing use only) -
curl -X POST http://localhost:8180/realms/medwear/protocol/openid-connect/token \
-  -H "Content-Type: application/x-www-form-urlencoded" \
-  -d "grant_type=password&client_id=med-api&client_secret=<secret>&username=<user>&password=<pass>"
-
-
- Use the token -
curl -k -H "Authorization: Bearer <access_token>" https://localhost/v1/me
-
-
- Health check -
curl -k https://localhost/v1/health
-
-
- -
-

Roles

- - - - - - - - - - - -
RoleAccess
deviceIngest data only
collaboratorRead data from assigned projects
researcherRead data, download files
project_adminManage users within their project
global_adminFull access: all projects, user/project management
-
- -
-

Web Interface

-

Available at https://localhost. Login with Keycloak credentials.

- - - - - - - - - - - - - -
TabAccessDescription
DashboardAll rolesBatch statistics, recent uploads
Uploadcollaborator+JSON, CSV, raw file upload (.cwa / .json / .csv)
Query Datacollaborator+Filter and search batches, view/download signals
File Managercollaborator+MinIO files: list, download, soft-delete, restore
Activitiesproject_admin, global_adminAudit log β€” All Activities + File Activities tabs
Adminproject_admin, global_adminUser and project management
Health CheckAll rolesReal-time service status
-
- -
-

Data Ingestion

+
+
+

MED-API

+

REST API reference for wearable health data ingestion and management

+
+
-

POST /v1/data/batch β€” Structured sensor data

-

Stores metadata in PostgreSQL and signals in InfluxDB. Supports JSON and CSV.

-

Supported signal types: ECG, BCG, PPG, IMU, EEG, EDA, HR, HRV

-
- Example request -
curl -k -X POST https://localhost/v1/data/batch \
+  
+
+ +
+

Overview

+

+ MED-API is the backend service of the MEDWEAR platform. It provides a secure REST API for ingesting, + querying, and managing wearable health data, deployed alongside MinIO, InfluxDB, PostgreSQL, + Keycloak, Dagster, Grafana, and pgAdmin as a single-server stack. +

+

+ For installation, Docker setup, authentication, and testing, see the + MED-API Setup section in Documentation. + Full documentation is also available on + Google Drive. +

+
+ +
+

Roles

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAccess
deviceIngest data only
collaboratorRead data from assigned projects
researcherRead data, download files
project_adminManage users within their project
global_adminFull access: all projects, user/project management
+
+ +
+

Web Interface

+

Available at https://localhost. Login with Keycloak credentials.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TabAccessDescription
DashboardAll rolesBatch statistics, recent uploads
Uploadcollaborator+JSON, CSV, raw file upload (.cwa / .json / .csv)
Query Datacollaborator+Filter and search batches, view/download signals
File Managercollaborator+MinIO files: list, download, soft-delete, restore
Activitiesproject_admin, global_adminAudit log - All Activities + File Activities tabs
Adminproject_admin, global_adminUser and project management
Health CheckAll rolesReal-time service status
+
+ +
+

Data Ingestion

+ +

POST /v1/data/batch - Structured sensor data

+

Stores metadata in PostgreSQL and signals in InfluxDB. Supports JSON and CSV.

+

Supported signal types: ECG, BCG, PPG, IMU, EEG, EDA, HR, HRV

+
+ Example request +
curl -k -X POST https://localhost/v1/data/batch \
   -H "Authorization: Bearer <token>" \
   -H "Content-Type: application/json" \
   -d '{
@@ -209,275 +167,462 @@ 

POST /v1/data/batch β€” Structured sensor data

"idempotency_key": "polar-h10-01-20260312-001" }] }'
-
+
-

POST /v1/data/upload β€” Raw file upload

-

Stores raw files (.cwa, .json, .csv) in MinIO. Max 1 GB.

-
- Example request -
curl -k -X POST https://localhost/v1/data/upload \
+        

POST /v1/data/upload - Raw file upload

+

Stores raw files (.cwa, .json, .csv) in MinIO. Max 1 GB.

+
+ Example request +
curl -k -X POST https://localhost/v1/data/upload \
   -H "Authorization: Bearer <token>" \
   -H "Content-Type: application/octet-stream" \
   -H "X-Filename: ecg_data.csv" \
   --data-binary @ecg_data.csv
-
-
- -
-

Query Endpoints

- - - - - - - - - - - - - - - - - -
MethodEndpointDescription
GET/v1/dataList/filter batches
GET/v1/data/{uuid}Single batch (metadata + signals)
GET/v1/data/{uuid}/metadataMetadata only
GET/v1/data/{uuid}/signalsSignals only
GET/v1/data/patients/{patient_id}All batches for a patient
DELETE/v1/data/{uuid}Soft delete batch
POST/v1/data/{uuid}/restoreRestore soft-deleted batch
GET/v1/data/filesList MinIO files
GET/v1/data/files/{path}Download file
DELETE/v1/data/files/{path}Soft delete file
POST/v1/data/files/{path}/restoreRestore deleted file
-

Query parameters for /v1/data: patient_ids, signal_type, start, end, device_id, project_id, limit, offset, metadata_only, show_deleted, format (json/csv), downsample

-
- -
-

Admin Endpoints

- - - - - - - - - - - - - - - -
MethodEndpointRole requiredDescription
POST/v1/data/admin/usersproject_admin+Create user in Keycloak and assign to project
GET/v1/data/admin/usersproject_admin+List users
DELETE/v1/data/admin/user-projectproject_admin+Remove user from project
POST/v1/data/admin/projectsglobal_adminCreate new project
GET/v1/data/admin/projectsproject_admin+List projects
DELETE/v1/data/admin/projects/{name}global_adminSoft delete project
POST/v1/data/admin/projects/{name}/restoreglobal_adminRestore deleted project
GET/v1/data/admin/activitiesproject_admin+Audit log
POST/v1/data/auth/change-passwordanyChange temporary password
-
- -
-

Error Responses

-

All API errors return a consistent JSON format:

-
{
+        
+      
+ +
+

Query Endpoints

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointDescription
GET/v1/dataList/filter batches
GET/v1/data/{uuid}Single batch (metadata + signals)
GET/v1/data/{uuid}/metadataMetadata only
GET/v1/data/{uuid}/signalsSignals only
GET/v1/data/patients/{patient_id}All batches for a patient
DELETE/v1/data/{uuid}Soft delete batch
POST/v1/data/{uuid}/restoreRestore soft-deleted batch
GET/v1/data/filesList MinIO files
GET/v1/data/files/{path}Download file
DELETE/v1/data/files/{path}Soft delete file
POST/v1/data/files/{path}/restoreRestore deleted file
+

Query parameters for /v1/data: patient_ids, signal_type, + start, end, device_id, project_id, limit, + offset, metadata_only, show_deleted, format (json/csv), + downsample +

+
+ +
+

Admin Endpoints

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointRole requiredDescription
POST/v1/data/admin/usersproject_admin+Create user in Keycloak and assign to project
GET/v1/data/admin/usersproject_admin+List users
DELETE/v1/data/admin/user-projectproject_admin+Remove user from project
POST/v1/data/admin/projectsglobal_adminCreate new project
GET/v1/data/admin/projectsproject_admin+List projects
DELETE/v1/data/admin/projects/{name}global_adminSoft delete project
POST/v1/data/admin/projects/{name}/restoreglobal_adminRestore deleted project
GET/v1/data/admin/activitiesproject_admin+Audit log
POST/v1/data/auth/change-passwordanyChange temporary password
+
+ +
+

Error Responses

+

All API errors return a consistent JSON format:

+
{
   "detail": {
     "error": "UPPER_SNAKE_CODE",
     "detail": "Human-readable message",
     "timestamp": "2026-03-31T20:00:00Z"
   }
 }
- - - - - - - - - - - - - - - - - - - - - - -
HTTPError CodeMeaning
400INVALID_JSONMalformed JSON body
400SCHEMA_VALIDATION_FAILUREMissing or invalid fields
400CHECKSUM_MISMATCHSHA-256 checksum did not match
400CSV_PARSE_ERRORCSV could not be parsed
400UNSUPPORTED_FILE_TYPEFile extension not allowed
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENInsufficient role
404NOT_FOUNDResource does not exist
409DUPLICATE_BATCHIdempotency key already used
409DUPLICATE_FILEFile already exists in MinIO
413PAYLOAD_TOO_LARGEBatch exceeds 10 MB limit
422INVALID_TIMESTAMP_ORDERend_date_time ≀ start_date_time
422MISSING_SAMPLING_RATEsampling_rate = 0 and cannot be inferred
429RATE_LIMIT_EXCEEDED100 requests/minute per token exceeded
500STORAGE_ERRORMinIO / InfluxDB write failure
500INTERNAL_SERVER_ERRORUnexpected server error
-
- -
-

Rate Limiting

- - - - - - - - - -
EndpointLimit
POST /v1/data/batch50 / minute
POST /v1/data/upload20 / minute
All other endpoints100 / minute
-
- -
-

Multi-Project Architecture

-

Each project has its own PostgreSQL database (medwear_project1, medwear_project2, etc.).

-
    -
  • Tables per project: measurement_batch, audit_log, user_project, deleted_files
  • -
  • Central postgres DB tracks soft-deleted projects in deleted_projects
  • -
  • global_admin can create and delete projects via the Admin tab or API
  • -
  • Grafana datasources are auto-provisioned for each project on startup and sync hourly
  • -
-
- -
-

Soft Delete & Purge

-

All deletes are soft β€” data is marked deleted and permanently removed after 24 hours.

- - - - - - - - - -
ResourceSoft deleteHard delete
Batch (metadata + signals)is_deleted=true in PostgreSQLPurge removes PostgreSQL row + InfluxDB data
FileEntry in deleted_files tablePurge removes from MinIO + table
ProjectEntry in deleted_projects tablePurge drops database
-

The purge loop runs every hour. Deleted items can be restored before the 24h window via the frontend or API.

-
- -
-

Two-Factor Authentication (2FA)

-

Optional TOTP-based 2FA using any authenticator app (e.g. Google Authenticator).

- - - - - - - - - - - -
MethodEndpointDescription
GET/v1/auth/2fa/statusCheck if 2FA is enabled
POST/v1/auth/2fa/setupGenerate secret and QR code
POST/v1/auth/2fa/enableVerify code and activate 2FA
POST/v1/auth/2fa/disableVerify code and deactivate 2FA
POST/v1/auth/2fa/verifyVerify a TOTP code after login
-
- -
-

GDPR / Privacy

-

MedWear implements GDPR compliance via /v1/data/gdpr/* endpoints. All write operations require project_admin or global_admin role.

- - - - - - - - - - - -
ArticleEndpointDescription
Art. 7POST /v1/data/gdpr/consent/{patient_id}Grant consent
Art. 7 Β§3DELETE /v1/data/gdpr/consent/{patient_id}/{consent_type}Revoke consent
Art. 7GET /v1/data/gdpr/consent/{patient_id}List all consents for a patient
Art. 17DELETE /v1/data/gdpr/erasure/{patient_id}Right to erasure β€” cascades through PostgreSQL β†’ InfluxDB β†’ MinIO
Art. 20GET /v1/data/gdpr/export/{patient_id}Data portability β€” exports all patient data as a JSON archive
-
- -
-

CARDAMON Integration

-

- The MEDWEAR platform is integrated with the CARDAMON study. A dedicated conversion script - imports CARDAMON study wearable data (Corsano wristband recordings) directly into the MEDWEAR database. -

- -

Directory structure

-
<cardamon-root>/
-  0/
-    2/
-      <participantID>/          ← patient ID (integer folder name)
-        <timestampFolder>/
-          *.txt                 ← ECG (JSON-log lines, 128 Hz)
-          Corsano_bioz/         ← .wiff files (bioz + acc) + Emography.csv
-          Corsano_ppg/          ← .wiff files (ppg + acc)
- -

Import full CARDAMON tree

-
python medwear_med_api/scripts/cardamon_to_medwear.py \
-  --cardamon-root /path/to/CARDAMON \
-  --device-id corsano-cw \
-  --api-url https://localhost \
-  --token <JWT> \
-  --project-id medwear_cardamon
- -

Import a single participant

-
python medwear_med_api/scripts/cardamon_to_medwear.py \
-  --cardamon-root /path/to/CARDAMON \
-  --participant 123456 \
-  --device-id corsano-cw \
-  --token <JWT> \
-  --project-id medwear_cardamon
- -

Signal mappings

- - - - - - - - - - - - - - - -
SourceMEDWEAR signal typeNotes
*.txtECGJSON-log format, 128 Hz
Corsano_bioz/*.wiffEDA + IMUParsed by mmt_parse_wiff.py
Corsano_ppg/*.wiffPPG + IMUParsed by mmt_parse_wiff.py
Corsano_bioz/Emography.csvEDASkin conductance
ppg2_*.csvPPGGrouped by chunk_index
acc.csvIMUX, Y, Z axes as separate batches
bioz.csvEDABioimpedance
rr_interval.csvECGRR intervals in ms
temperature.csv, activity.csv, sleep.csv, questionnaire*, adl*β€”Raw file upload to MinIO
-
- -
-

CLAID Server Ingestion

-

- The CLAID server (172.20.83.11) receives live Corsano data from phones via the CLAID framework. - Data lands at: /home/scai/CLAID-Server/receivedFiles/cardio/{phone_name}/{study_id}/{clinic_id}/{participant_id}/ -

-
- Run directly on the server -
python medwear_med_api/scripts/claid_to_medwear.py \
-  --claid-root /home/scai/CLAID-Server/receivedFiles \
-  --device-id corsano-cw \
-  --api-url https://172.20.83.11 \
-  --token <JWT> \
-  --project-id medwear_cardamon
-
-
- Run locally after rsync -
# Sync data from server (SPZ VPN must be active)
-rsync -avz --progress \
-  scai@172.20.83.11:/home/scai/CLAID-Server/receivedFiles/cardio/ \
-  /local/claid_data/cardio/
-
-# Or use --sync flag (calls rsync automatically)
-python medwear_med_api/scripts/claid_to_medwear.py \
-  --claid-root /local/claid_data \
-  --sync \
-  --device-id corsano-cw \
-  --api-url https://<medwear-server> \
-  --token <JWT> \
-  --project-id medwear_cardamon
-
-
- -
-

ROS 2 Real-Time Bridge

-

Streams live biosignal data from ROS 2 topics directly into the MedWear API.

- - - - - - - - - - -
SignalMessage typeUnit
EEGhealthcare_msgs/msg/EEGuV
EDAhealthcare_msgs/msg/EDAuS
HRhealthcare_msgs/msg/HeartRatebpm
HRVhealthcare_msgs/msg/HeartRateVariabilityms
-
- Run with Docker -
docker build -t medwear-ros2-bridge ./ros2_to_medwear
+        
+          
+            
+              
+              
+              
+            
+          
+          
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+            
+              
+              
+              
+            
+          
+        
HTTPError CodeMeaning
400INVALID_JSONMalformed JSON body
400SCHEMA_VALIDATION_FAILUREMissing or invalid fields
400CHECKSUM_MISMATCHSHA-256 checksum did not match
400CSV_PARSE_ERRORCSV could not be parsed
400UNSUPPORTED_FILE_TYPEFile extension not allowed
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENInsufficient role
404NOT_FOUNDResource does not exist
409DUPLICATE_BATCHIdempotency key already used
409DUPLICATE_FILEFile already exists in MinIO
413PAYLOAD_TOO_LARGEBatch exceeds 10 MB limit
422INVALID_TIMESTAMP_ORDERend_date_time ≀ start_date_time
422MISSING_SAMPLING_RATEsampling_rate = 0 and cannot be inferred
429RATE_LIMIT_EXCEEDED100 requests/minute per token exceeded
500STORAGE_ERRORMinIO / InfluxDB write failure
500INTERNAL_SERVER_ERRORUnexpected server error
+
+ +
+

Rate Limiting

+ + + + + + + + + + + + + + + + + + + + + +
EndpointLimit
POST /v1/data/batch50 / minute
POST /v1/data/upload20 / minute
All other endpoints100 / minute
+
+ +
+

Multi-Project Architecture

+

Each project has its own PostgreSQL database (medwear_project1, medwear_project2, + etc.).

+
    +
  • Tables per project: measurement_batch, audit_log, user_project, + deleted_files +
  • +
  • Central postgres DB tracks soft-deleted projects in deleted_projects
  • +
  • global_admin can create and delete projects via the Admin tab or API
  • +
  • Grafana datasources are auto-provisioned for each project on startup and sync hourly
  • +
+
+ +
+

Soft Delete & Purge

+

All deletes are soft - data is marked deleted and permanently removed after 24 hours.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ResourceSoft deleteHard delete
Batch (metadata + signals)is_deleted=true in PostgreSQLPurge removes PostgreSQL row + InfluxDB data
FileEntry in deleted_files tablePurge removes from MinIO + table
ProjectEntry in deleted_projects tablePurge drops database
+

The purge loop runs every hour. Deleted items can be restored before the 24h window via the frontend or API. +

+
+ +
+

Two-Factor Authentication (2FA)

+

Optional TOTP-based 2FA using any authenticator app (e.g. Google Authenticator).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointDescription
GET/v1/auth/2fa/statusCheck if 2FA is enabled
POST/v1/auth/2fa/setupGenerate secret and QR code
POST/v1/auth/2fa/enableVerify code and activate 2FA
POST/v1/auth/2fa/disableVerify code and deactivate 2FA
POST/v1/auth/2fa/verifyVerify a TOTP code after login
+
+ +
+

GDPR / Privacy

+

MedWear implements GDPR compliance via /v1/data/gdpr/* endpoints. All write operations require + project_admin or global_admin role. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArticleEndpointDescription
Art. 7POST /v1/data/gdpr/consent/{patient_id}Grant consent
Art. 7 Β§3DELETE /v1/data/gdpr/consent/{patient_id}/{consent_type}Revoke consent
Art. 7GET /v1/data/gdpr/consent/{patient_id}List all consents for a patient
Art. 17DELETE /v1/data/gdpr/erasure/{patient_id}Right to erasure - cascades through PostgreSQL β†’ InfluxDB β†’ MinIO
Art. 20GET /v1/data/gdpr/export/{patient_id}Data portability - exports all patient data as a JSON archive
+
+ +
+

ROS 2 Real-Time Bridge

+

Streams live biosignal data from ROS 2 topics directly into the MedWear API.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SignalMessage typeUnit
EEGhealthcare_msgs/msg/EEGuV
EDAhealthcare_msgs/msg/EDAuS
HRhealthcare_msgs/msg/HeartRatebpm
HRVhealthcare_msgs/msg/HeartRateVariabilityms
+
+ Run with Docker +
docker build -t medwear-ros2-bridge ./ros2_to_medwear
 
 docker run --rm \
     --network host \
@@ -489,79 +634,44 @@ 

ROS 2 Real-Time Bridge

--device corsano-001 \ --patient <patient_id> \ --project <project_id>
-
-
+ + -
-

Code Structure

-
medwear_med_api/
+      
+

Code Structure

+
medwear_med_api/
   medwear_med_api/
     routers/
-      utils.py       β€” shared helpers
-      batches.py     β€” batch query/delete/restore endpoints
-      files.py       β€” MinIO file endpoints
-      patients.py    β€” patient timeline endpoint
-      admin.py       β€” user/project/activity management
-      totp.py        β€” 2FA (TOTP) endpoints
+      utils.py       - shared helpers
+      batches.py     - batch query/delete/restore endpoints
+      files.py       - MinIO file endpoints
+      patients.py    - patient timeline endpoint
+      admin.py       - user/project/activity management
+      totp.py        - 2FA (TOTP) endpoints
     query_router.py
-    batch_router.py  β€” data ingestion endpoints
-    auth.py          β€” Keycloak JWT validation
-    audit.py         β€” request audit logging middleware
-    db.py            β€” per-project session factory
-    models.py        β€” SQLModel table definitions
-    grafana_utils.py β€” Grafana datasource auto-provisioning
-    influx_reader.py β€” InfluxDB signal queries
-    influx_writer.py β€” InfluxDB signal writes
+    batch_router.py  - data ingestion endpoints
+    auth.py          - Keycloak JWT validation
+    audit.py         - request audit logging middleware
+    db.py            - per-project session factory
+    models.py        - SQLModel table definitions
+    grafana_utils.py - Grafana datasource auto-provisioning
+    influx_reader.py - InfluxDB signal queries
+    influx_writer.py - InfluxDB signal writes
   scripts/
     run_med_api.py
-    cardamon_to_medwear.py
-    claid_to_medwear.py
     test_phase3.py / test_phase4.py / test_phase5.py
-
+
-
-

Testing

-
cd /path/to/repo/medwear_med_api
-source venv/bin/activate
-
-python3 scripts/test_phase3.py   # Batch upload, CSV, validation
-python3 scripts/test_phase4.py   # Query, delete, restore, admin, multi-project
-python3 scripts/test_phase5.py   # Error handling: 46/46 tests
- -

Quick smoke test

-
# 1. Get a token
-TOKEN=$(curl -sk -X POST https://localhost/v1/data/auth/token \
-  -H "Content-Type: application/json" \
-  -d '{"username":"<your-username>","password":"<your-password>"}' | jq -r .access_token)
-
-# 2. Check health
-curl -sk https://localhost/v1/health | jq .
-
-# 3. Query recent batches
-curl -sk -H "Authorization: Bearer $TOKEN" \
-  "https://localhost/v1/data?limit=5" | jq .
-
- -
-

Backup

-

Backs up PostgreSQL, InfluxDB, and MinIO to a compressed archive. Backups older than 30 days are deleted automatically.

-
# Run manually
-./scripts/backup.sh /opt/medwear_backups
-
-# Add to crontab (daily at 2 AM)
-0 2 * * * /path/to/med-api/scripts/backup.sh /opt/medwear_backups >> /var/log/medwear_backup.log 2>&1
-
- -
-
- -
- -

Β© 2025 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch

-
+ +
+ +

Β© 2026 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch Β· monica.perezserrano@hest.ethz.ch

+
- + + \ No newline at end of file diff --git a/schemas.html b/schemas.html index b8c6f1f..e1b7c65 100644 --- a/schemas.html +++ b/schemas.html @@ -6,67 +6,286 @@ Schemas | MEDWEAR - + + +
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
+ MEDWEAR
-
- -
-
-

MEDWEAR Physiological Signal Schemas

-

- Below are the core MEDWEAR message types representing raw sensor data for common physiological signals. - Each links to downloadable JSON Schema and ROS 2 message definitions. -

- - - -

- All schemas and message definitions are maintained in our - GitHub repository. -

-
-
-
+ +
+
+

Schemas

+

JSON and ROS 2 definitions for physiological signals

+
+
+ +
+
+ +
+

MEDWEAR Physiological Signal Schemas

+

+ Below are the core MEDWEAR message types for common physiological signals. + JSON Schemas are maintained in the + ord_schemas + repository. + ROS 2 message definitions are in the + healthcare_msgs + repository. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SignalJSON SchemaROS 2 Message
Ballistocardiogram (BCG)bcg_schema.jsonBCG.msg
Electrocardiogram (ECG)ecg_schema.jsonECG.msg
Electrodermal Activity (EDA)eda_schema.jsonEDA.msg
Electroencephalogram (EEG)eeg_schema.jsonEEG.msg
Electromyography (EMG)emg_schema.jsonEMG.msg
Electrooculogram (EOG)eog_schema.jsonEOG.msg
Impedance Cardiogram (ICG)icg_schema.jsonICG.msg
Photoplethysmogram (PPG)ppg_schema.jsonPPG.msg
+
+ +
+

BCG Example Schema Reference

+

+ The BCG schema represents waveform data from a specific lead with signal quality and temporal context. + The BCGInfo schema captures metadata about the sensor and acquisition parameters. + Every BCG data message references a BCGInfo session via session_id. +

+ +

JSON Schemas

+ +
+ BCG.json - waveform data +
{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "BCG Data",
+  "description": "Waveform data from a specific BCG lead with signal quality and temporal context.",
+  "type": "object",
+  "definitions": {
+    "time_frame":    { "$ref": "time-frame-1.0.json" },
+    "time_interval": { "$ref": "time-interval-1.0.json" }
+  },
+  "properties": {
+    "session_id": {
+      "type": "string",
+      "description": "Session identifier matching BCGInfo's session_id"
+    },
+    "sample_size": {
+      "type": "integer",
+      "minimum": 0,
+      "maximum": 65535,
+      "description": "Number of samples in the bcg and signal_quality arrays"
+    },
+    "bcg": {
+      "type": "array",
+      "items": { "type": "number" },
+      "description": "Raw or filtered BCG values"
+    },
+    "signal_quality": {
+      "type": "array",
+      "items": { "type": "number", "minimum": 0, "maximum": 1 },
+      "description": "Signal quality per sample, from 0 (poor) to 1 (excellent)"
+    }
+  },
+  "required": ["session_id", "sample_size", "bcg", "signal_quality"]
+}
+
+ +
+ BCGInfo.json - acquisition metadata +
{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "BCGInfo",
+  "description": "Metadata describing BCG data acquisition parameters and sensor characteristics.",
+  "type": "object",
+  "definitions": {
+    "device_info": {
+      "type": "object",
+      "properties": {
+        "manufacturer":  { "type": "string" },
+        "model":         { "type": "string" },
+        "serial_number": { "type": "string" }
+      },
+      "required": ["manufacturer", "model"]
+    },
+    "filter_base": {
+      "type": "object",
+      "properties": {
+        "filter_type":         { "type": "string" },
+        "cutoff_frequency_hz": { "type": "number" },
+        "order":               { "type": "integer" }
+      }
+    }
+  },
+  "properties": {
+    "device_info": { "$ref": "#/definitions/device_info" },
+    "session_id":  { "type": "string" },
+    "sampling_frequency":          { "type": "number", "minimum": 0 },
+    "window_size_samples":         { "type": "integer", "minimum": 1 },
+    "quality_window_size_samples": { "type": "integer", "minimum": 1 },
+    "units": {
+      "type": "string",
+      "enum": ["UNIT_UNKNOWN","UNIT_MV","UNIT_UV","UNIT_COUNTS","UNIT_M_S2","UNIT_NEWTON"]
+    },
+    "gain":           { "type": "number" },
+    "adc_resolution": { "type": "integer", "minimum": 1, "maximum": 32 },
+    "sensor_placement": {
+      "type": "string",
+      "enum": ["PLACEMENT_UNKNOWN","PLACEMENT_CHEST","PLACEMENT_BACK","PLACEMENT_MATTRESS",
+               "PLACEMENT_CHAIR","PLACEMENT_ANKLE","PLACEMENT_FOOT","PLACEMENT_WRIST",
+               "PLACEMENT_ARM","PLACEMENT_DESK","PLACEMENT_CLOTHING","PLACEMENT_CUSTOM"]
+    },
+    "sensing_direction": {
+      "type": "string",
+      "enum": ["DIRECTION_UNKNOWN","DIRECTION_DORSO_VENTRAL","DIRECTION_LONGITUDINAL",
+               "DIRECTION_MEDIO_LATERAL","DIRECTION_CUSTOM"]
+    },
+    "sensing_modality": {
+      "type": "string",
+      "enum": ["MODALITY_UNKNOWN","MODALITY_PIEZOELECTRIC_FILM","MODALITY_FORCEPLATE",
+               "MODALITY_ACCELEROMETER","MODALITY_PIEZOELECTRIC_CRYSTAL","MODALITY_CUSTOM"]
+    },
+    "measurement_system": {
+      "type": "string",
+      "enum": ["SYSTEM_UNKNOWN","SYSTEM_STARR","SYSTEM_NICKERSON","SYSTEM_DOCK","SYSTEM_CUSTOM"]
+    },
+    "filter": { "$ref": "#/definitions/filter_base" }
+  },
+  "required": [
+    "device_info","session_id","sampling_frequency","window_size_samples",
+    "quality_window_size_samples","units","gain","adc_resolution",
+    "sensor_placement","sensing_direction","sensing_modality","measurement_system"
+  ]
+}
+
+ +

ROS 2 Message Definitions

+ +
+ BCG.msg +
std_msgs/Header header
+
+string session_id                     # Match BCGInfo's session_id
+uint16 sample_size
+float32[] bcg                         # Raw or filtered BCG values
+float32[] signal_quality              # 0 (poor) to 1 (excellent)
+
+ +
+ BCGInfo.msg +
DeviceInfo device_info
+string session_id
+
+float32 sampling_frequency            # Hz
+uint32 window_size_samples
+uint32 quality_window_size_samples
+
+uint8 UNIT_UNKNOWN=0
+uint8 UNIT_MV=1  uint8 UNIT_UV=2  uint8 UNIT_COUNTS=3
+uint8 UNIT_M_S2=4  uint8 UNIT_NEWTON=5
+uint8 units
+
+float32 gain
+uint8   adc_resolution
+
+uint8 PLACEMENT_UNKNOWN=0  uint8 PLACEMENT_CHEST=1  uint8 PLACEMENT_BACK=2
+uint8 PLACEMENT_MATTRESS=3  uint8 PLACEMENT_CHAIR=4  uint8 PLACEMENT_ANKLE=5
+uint8 PLACEMENT_FOOT=6  uint8 PLACEMENT_WRIST=7  uint8 PLACEMENT_ARM=8
+uint8 PLACEMENT_DESK=9  uint8 PLACEMENT_CLOTHING=10  uint8 PLACEMENT_CUSTOM=255
+uint8 sensor_placement
+
+uint8 DIRECTION_UNKNOWN=0  uint8 DIRECTION_DORSO_VENTRAL=1
+uint8 DIRECTION_LONGITUDINAL=2  uint8 DIRECTION_MEDIO_LATERAL=3
+uint8 DIRECTION_CUSTOM=255
+uint8 sensing_direction
+
+uint8 MODALITY_UNKNOWN=0  uint8 MODALITY_PIEZOELECTRIC_FILM=1
+uint8 MODALITY_FORCEPLATE=2  uint8 MODALITY_ACCELEROMETER=3
+uint8 MODALITY_PIEZOELECTRIC_CRYSTAL=4  uint8 MODALITY_CUSTOM=255
+uint8 sensing_modality
+
+uint8 SYSTEM_UNKNOWN=0  uint8 SYSTEM_STARR=1  uint8 SYSTEM_NICKERSON=2
+uint8 SYSTEM_DOCK=3  uint8 SYSTEM_CUSTOM=255
+uint8 measurement_system
+
+FilterBase filter
+
+
+ +
+
+
-

Β© 2025 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch

+

Β© 2026 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch Β· monica.perezserrano@hest.ethz.ch

+ - + \ No newline at end of file diff --git a/standards.html b/standards.html deleted file mode 100644 index f8c8deb..0000000 --- a/standards.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - Standards | MEDWEAR - - - - - -
-
- -
-
-

MEDWEAR

-

Open, Interoperable Standards for Medical Wearables

-
-
-
- 🚧 This website is currently under construction. Some content may be incomplete. 🚧 -
- -
- -
-
-

Standards and Schemas

-

- The MEDWEAR standard schemas have been generated following the - IEEE 1752 Standard - and are compatible with the Open mHealth framework. -

- -
-

Schema Repository

-

- All MEDWEAR JSON schemas are hosted on our - GitHub repository. - These schemas define data structures for physiological signals and metadata to ensure interoperability across devices and platforms. -

-
- -
-

Schema Preview

-

Here are some examples of key schemas:

- -

Each schema is designed to be modular and extensible for multiple sensor types and signals.

-
- -
-

About the Standards

-

- The MEDWEAR standards build on top of Open mHealth, aiming to provide a unified, device-agnostic approach to wearable health data representation. - This approach supports continuous, high-frequency data streams while maintaining compatibility with clinical and research workflows. -

-
-
-
-
- -

Β© 2025 ETH Zurich – MEDWEAR Project | Contact: diego.paez@scai.ethz.ch

-
- - diff --git a/style.css b/style.css index e60d1eb..9586b9e 100644 --- a/style.css +++ b/style.css @@ -1,178 +1,564 @@ -/* Reset margins/paddings on body, no max-width here */ +/* ── Variables ──────────────────────────────────────────────── */ +:root { + --navy: #001428; + --blue: #003366; + --blue-mid: #004d99; + --teal: #0099cc; + --teal-light: #00b8d9; + --white: #ffffff; + --gray-light: #f4f7fa; + --gray-mid: #e2e8f0; + --text: #1a2332; + --text-muted: #5a6a7a; +} + +/* ── Reset & base ───────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; } + body { margin: 0; padding: 0; - box-sizing: border-box; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; - color: #222; - background-color: #fff; - text-align: left; + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + line-height: 1.65; + color: var(--text); + background: var(--white); } -/* Header full width with inner container */ +a { color: var(--teal); } +a:hover { color: var(--teal-light); } + +/* ── Sticky navbar ──────────────────────────────────────────── */ header { + position: sticky; + top: 0; + z-index: 200; width: 100%; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - margin: 0; - padding: 0; - background-color: #003366; + background: var(--navy); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 40px; + height: 60px; + box-shadow: 0 2px 16px rgba(0, 0, 0, 0.4); } -/* Container inside header for max width & centering */ -.header-container { - max-width: 900px; - margin: 0 auto; - padding: 0 15px; +.header-brand { + color: var(--white); + font-size: 1.35rem; + font-weight: 800; + letter-spacing: 0.07em; + text-decoration: none; + flex-shrink: 0; + transition: color 0.18s; +} + +.header-brand:hover { color: var(--teal-light); } + +nav { display: flex; align-items: center; - justify-content: space-between; - flex-wrap: wrap; /* responsive wrap */ + gap: 2px; + flex-wrap: wrap; } -/* Header upper with logo */ -.header-upper { +nav a { + color: rgba(255, 255, 255, 0.75); + text-decoration: none; + padding: 6px 14px; + border-radius: 5px; + font-size: 0.88rem; + font-weight: 500; + transition: background 0.18s, color 0.18s; + white-space: nowrap; +} + +nav a:hover { + background: rgba(255, 255, 255, 0.12); + color: var(--white); +} + +nav a.active { + background: var(--teal); + color: var(--white); +} + +/* ── Hero (homepage only) ───────────────────────────────────── */ +.hero { + width: 100%; + min-height: 520px; + background-image: url('assets/images/hero-home.jpg'); + background-size: cover; + background-position: center 35%; + position: relative; display: flex; align-items: center; justify-content: center; - width: 100%; - padding: 15px 0; - border-bottom: 3px solid #003366; /* dark blue */ - background: #003366; } -/* Logo styles */ -.logo { - max-height: 90px; - background: white; - padding: 10px; - border-radius: 8px; - box-shadow: 0 2px 6px rgba(0,0,0,0.1); - display: block; +.hero::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, + rgba(0, 20, 40, 0.82) 0%, + rgba(0, 51, 102, 0.58) 100%); } -/* Header lower - full width, with centered text and light gray background */ -.header-lower { - width: 100%; - background-color: #003366; /* light gray background */ - color: #f0f0f0; /* dark blue text */ +.hero-content { + position: relative; + z-index: 1; text-align: center; - padding: 20px 15px; + color: var(--white); + max-width: 760px; + padding: 80px 28px; } -/* Header lower title */ -.header-lower h1 { - margin: 0; - font-size: 2.5rem; - font-weight: 700; +.hero-content h1 { + font-size: 3rem; + font-weight: 800; line-height: 1.1; + margin: 0 0 20px; + text-shadow: 0 2px 14px rgba(0, 0, 0, 0.45); } -/* Header lower subtitle */ -.header-lower p { - margin: 8px 0 0; - font-weight: 300; - font-size: 1.2rem; - line-height: 1.3; - color: #f0f0f0; +.hero-content p { + font-size: 1.18rem; + line-height: 1.6; + color: rgba(255, 255, 255, 0.88); + margin: 0 0 38px; } -/* Navigation inside header container */ -nav { - width: 100%; +.hero-actions { display: flex; + gap: 14px; justify-content: center; - margin-top: 10px; flex-wrap: wrap; } -/* Nav links */ -nav a { - color: #f0f0f0; +/* ── Buttons ────────────────────────────────────────────────── */ +.btn-primary { + background: var(--teal); + color: var(--white); + padding: 13px 30px; + border-radius: 6px; text-decoration: none; - margin: 0 15px; font-weight: 600; - font-size: 1rem; - padding: 5px 10px; - border-radius: 4px; - transition: background-color 0.3s ease, color 0.3s ease; + font-size: 0.95rem; + box-shadow: 0 4px 14px rgba(0, 153, 204, 0.38); + transition: background 0.2s, transform 0.15s, box-shadow 0.2s; } -/* Nav hover and active state */ -nav a:hover, -nav a.active { - background-color: #f0f0f0; - color: #003366; +.btn-primary:hover { + background: var(--teal-light); + color: var(--white); + transform: translateY(-2px); + box-shadow: 0 6px 22px rgba(0, 153, 204, 0.48); } -/* Under construction banner - full width */ -.under-construction-banner { +.btn-secondary { + background: transparent; + color: var(--white); + padding: 11px 28px; + border-radius: 6px; + text-decoration: none; + font-weight: 600; + font-size: 0.95rem; + border: 2px solid rgba(255, 255, 255, 0.5); + transition: border-color 0.2s, background 0.2s; +} + +.btn-secondary:hover { + border-color: var(--white); + background: rgba(255, 255, 255, 0.1); + color: var(--white); +} + +/* ── Page banner (inner pages) ──────────────────────────────── */ +.page-banner { width: 100%; - background-color: #ffcc00; - color: #333; - text-align: center; - padding: 10px 0; - font-weight: bold; + height: 400px; + background-size: cover; + background-position: center 30%; + position: relative; + display: flex; + align-items: center; +} + +.page-banner::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, + rgba(0, 20, 40, 0.80) 0%, + rgba(0, 51, 102, 0.60) 100%); +} + +.page-banner-content { + position: relative; + z-index: 1; + max-width: 900px; + margin: 0 auto; + padding: 0 28px; + width: 100%; + color: var(--white); +} + +.page-banner-content h1 { + font-size: 2.2rem; + font-weight: 700; + margin: 0 0 6px; +} + +.page-banner-content p { + margin: 0; + color: rgba(255, 255, 255, 0.78); font-size: 1rem; - border-bottom: 2px solid #e6b800; } -/* Main content container centered with max width, right aligned text */ +/* ── Main content ───────────────────────────────────────────── */ .content-container { max-width: 900px; - margin: 30px auto; - padding: 0 15px; - text-align: left; /* make text left-aligned */ + margin: 48px auto; + padding: 0 28px; +} + +section { margin-bottom: 2.8em; } + +/* ── Typography ─────────────────────────────────────────────── */ +.content-container h2 { + font-size: 1.65rem; + font-weight: 700; + color: var(--blue); + margin: 0 0 20px; + padding-bottom: 12px; + position: relative; +} + +.content-container h2::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 44px; + height: 3px; + background: var(--teal); + border-radius: 2px; +} + +.content-container h3 { + font-size: 1.12rem; + font-weight: 600; + color: var(--blue); + margin: 1.6em 0 0.5em; +} + +.content-container h4 { + font-size: 1rem; + font-weight: 600; + color: var(--text); + margin: 1.2em 0 0.4em; } .content-container p { - max-width: 900px; - margin: 0 auto; /* centers the paragraph block itself */ - text-align: left; /* left align text inside paragraphs */ + margin: 0 0 1em; +} + +.content-container ul, +.content-container ol { + padding-left: 1.4em; + margin: 0 0 1em; } +.content-container li { margin-bottom: 0.38em; } -/* Footer full width */ -footer { +/* ── Documentation block dividers ──────────────────────────── */ +.doc-block-title { + font-size: 2rem !important; + font-weight: 800 !important; + color: var(--navy) !important; + background: linear-gradient(90deg, var(--blue) 0%, var(--teal) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin: 3em 0 1.2em !important; + padding-bottom: 16px !important; + border-bottom: 3px solid var(--teal); + scroll-margin-top: 80px; +} + +.doc-block-title::after { display: none !important; } + +/* ── Quick-link cards ───────────────────────────────────────── */ +.quick-links { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 18px; + margin: 24px 0; +} + +.quick-link-card { + display: block; + background: var(--white); + border: 1px solid var(--gray-mid); + border-left: 4px solid transparent; + border-radius: 8px; + padding: 20px 22px; + text-decoration: none; + color: inherit; + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.06); + transition: border-left-color 0.2s, box-shadow 0.2s, transform 0.18s; +} + +.quick-link-card:hover { + border-left-color: var(--teal); + box-shadow: 0 6px 22px rgba(0, 0, 0, 0.10); + transform: translateY(-3px); +} + +.quick-link-card h3 { + margin: 0 0 6px; + color: var(--blue); + font-size: 1rem; + font-weight: 700; +} + +.quick-link-card p { + margin: 0; + font-size: 0.88rem; + color: var(--text-muted); + line-height: 1.45; +} + +/* ── Code blocks ────────────────────────────────────────────── */ +pre { + background: #0d1926; + color: #c9d8e8; + border-radius: 6px; + padding: 16px 20px; + overflow-x: auto; + font-size: 0.87rem; + line-height: 1.55; + margin: 8px 0 18px; + border: 1px solid #1e3050; +} + +code { + font-family: 'Cascadia Code', 'Fira Code', 'Courier New', monospace; + font-size: 0.9em; +} + +p code, li code, td code { + background: #eef2f7; + color: #1a4a6b; + padding: 1px 6px; + border-radius: 3px; + font-size: 0.875em; +} + +/* ── Tables ─────────────────────────────────────────────────── */ +table { width: 100%; - background-color: #f8f8f8; - box-sizing: border-box; - padding: 0; - margin-top: 40px; + border-collapse: collapse; + margin: 12px 0 22px; + font-size: 0.9rem; + border-radius: 6px; + overflow: hidden; + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08); } -/* Footer inner container for width limit */ -.footer-container { - max-width: 900px; - margin: 0 auto; - padding: 25px 15px; +th, td { + text-align: left; + padding: 11px 14px; + border-bottom: 1px solid var(--gray-mid); + vertical-align: top; +} + +th { + background: var(--blue); + color: var(--white); + font-weight: 600; + font-size: 0.875rem; + letter-spacing: 0.02em; +} + +tr:last-child td { border-bottom: none; } +tr:nth-child(even) td { background: var(--gray-light); } + +/* ── Details / Summary ──────────────────────────────────────── */ +details { + border: 1px solid var(--gray-mid); + border-radius: 6px; + padding: 2px 14px; + margin-bottom: 10px; + background: var(--white); +} + +details[open] { padding-bottom: 12px; } + +summary { + cursor: pointer; + font-weight: 600; + color: var(--blue-mid); + padding: 10px 0; + list-style: none; display: flex; - flex-direction: column; - align-items: flex-start; - gap: 15px; + align-items: center; + gap: 8px; +} + +summary::before { + content: 'β–Ά'; + font-size: 0.65em; + color: var(--teal); + transition: transform 0.2s; +} + +details[open] summary::before { transform: rotate(90deg); } +summary:hover { color: var(--teal); } + +/* ── Footer ─────────────────────────────────────────────────── */ +footer { + width: 100%; + background: var(--navy); + box-sizing: border-box; + padding: 40px 28px 24px; + margin-top: 60px; + text-align: center; } .footer-logos { display: flex; - justify-content: center; /* center logos horizontally */ - gap: 24px; + justify-content: center; + align-items: center; + gap: 36px; width: 100%; + margin-bottom: 20px; + flex-wrap: wrap; } -/* Footer logos style */ .footer-logos img { - height: 40px; - margin: 0; + height: 72px; + width: auto; border: none; box-shadow: none; background: none; + padding: 0; + border-radius: 0; + transition: opacity 0.2s; } -/* Footer text aligned right */ +.footer-logos .ethz-logo { height: 48px; } + footer p { - color: #555; + color: rgba(255, 255, 255, 0.45); margin: 0; - width: 100%; - text-align: center; + font-size: 0.85rem; +} + +/* ── Pilot study cards ──────────────────────────────────────── */ +.pilot-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 24px; + margin: 2em 0; +} + +.pilot-card { + border-radius: 10px; + overflow: hidden; + border: 1px solid var(--gray-mid); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07); +} + +.pilot-card-header { + padding: 22px 24px 18px; + color: var(--white); +} + +.pilot-card--spz .pilot-card-header { background: linear-gradient(135deg, #3c3489 0%, #534ab7 100%); } +.pilot-card--llui .pilot-card-header { background: linear-gradient(135deg, #085041 0%, #0f6e56 100%); } +.pilot-card--json .pilot-card-header { background: linear-gradient(135deg, #003366 0%, #004d99 100%); } +.pilot-card--ros .pilot-card-header { background: linear-gradient(135deg, #006666 0%, #0099aa 100%); } + +.pilot-tag { + display: inline-block; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + background: rgba(255, 255, 255, 0.22); + padding: 3px 10px; + border-radius: 20px; + margin-bottom: 10px; +} + +.pilot-card-header h3 { + margin: 0 0 6px; + font-size: 1.1rem; + font-weight: 700; + color: var(--white); +} + +.pilot-subtitle { + margin: 0; + font-size: 0.82rem; + color: rgba(255, 255, 255, 0.72); + font-family: 'Cascadia Code', 'Fira Code', monospace; +} + +.pilot-card-body { + padding: 20px 24px 22px; + background: var(--white); +} + +.pilot-card-body h4 { + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + margin: 1.4em 0 0.5em; +} + +.pilot-card-body h4:first-child { margin-top: 0; } + +.pilot-card-body p { font-size: 0.92rem; margin: 0 0 0.6em; } +.pilot-card-body ol { font-size: 0.92rem; padding-left: 1.4em; margin: 0 0 0.6em; } +.pilot-card-body li { margin-bottom: 0.45em; } + +.signal-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 6px; } +.signal-tag { + font-size: 0.78rem; + font-weight: 600; + padding: 4px 12px; + border-radius: 20px; + background: var(--gray-light); + color: var(--blue-mid); + border: 1px solid var(--gray-mid); +} + +/* ── Responsive ─────────────────────────────────────────────── */ +@media (max-width: 700px) { + header { + flex-direction: column; + align-items: flex-start; + height: auto; + padding: 12px 16px; + gap: 8px; + } + + nav a { + padding: 5px 10px; + font-size: 0.83rem; + } + + .hero-content h1 { font-size: 2rem; } + .hero { min-height: 380px; } + .content-container { padding: 0 16px; margin: 32px auto; } +} diff --git a/use-cases.html b/use-cases.html new file mode 100644 index 0000000..d0c46c6 --- /dev/null +++ b/use-cases.html @@ -0,0 +1,297 @@ + + + + + + + Use Cases | MEDWEAR + + + + + +
+ MEDWEAR + +
+ +
+
+

Use Cases

+

Pilot studies demonstrating MEDWEAR in clinical and assistive-technology settings

+
+
+ +
+
+ +
+

Overview

+

+ MEDWEAR is validated through two ongoing pilot studies in collaboration with clinical and + robotics research groups at ETH Zurich. Both pilots share the same end destination - + the MEDWEAR data collection platform - but differ in how data flows from the wearable to + that platform: one is offline via a smartphone app, the other is online via ROS 2 middleware. +

+
+ + Two pilot studies: SPZ offline via smartphone, server, MEDWEAR format and platform; LLUI online via + ROS and MEDWEAR platform + Diagram showing two pilots with matching container sizes. SPZ: wearable sends data to a smartphone + running the SCAI app, which sends it to a server, then converted to MEDWEAR format and sent to the MEDWEAR + data collection platform, offline. LLUI: wearable streams PPG, ECG, EDA, IMU in MEDWEAR format via ROS + middleware and MED-API to the MEDWEAR data collection platform, online, used for real-time cognitive load + estimation. + + + + + + + + + SPZ - Pilot 1 + Offline, + via smartphone app + + + Wearable device + PPG, EDA, IMU + + + + + Smartphone - SCAI + app + Collects and sends data + + + + + Server + Intermediate storage + + + + + MEDWEAR format + + + + + MEDWEAR platform + Visualization, roles, export + + + + LLUI - Pilot 2 + Online + cognitive load estimation + + + Wearable device + PPG, ECG, EDA, IMU + + + + + MEDWEAR format + + + + + ROS middleware + + + + + MED-API + + + + + MEDWEAR platform + Visualization, roles, export + +
+ Data flow for the two MEDWEAR pilot studies. Both pilots use MEDWEAR schemas and converge on the same data + collection platform. +
+
+
+ +
+ +
+
+ Pilot 1 +

SPZ - Offline data collection

+
+
+

Context

+

+ Clinical study in collaboration with SPZ (Schweizerisches Paraplegiker-Zentrum). + Participants wear sensors during daily activities outside a lab environment. + The study focuses on capturing physiological signals - PPG, EDA, and IMU - using + consumer-grade wearable devices. +

+

Data flow

+
    +
  1. A wearable device streams PPG, EDA, and IMU signals.
  2. +
  3. A smartphone running the SCAI app collects and buffers the raw data locally.
  4. +
  5. Batches are uploaded to an intermediate server for secure storage.
  6. +
  7. A post-processing pipeline converts the data to MEDWEAR format (JSON Schema + validated).
  8. +
  9. Standardized data is ingested into the MEDWEAR platform for visualization, access + control, and export.
  10. +
+

Why offline?

+

+ Participants may not always have reliable internet connectivity. The smartphone buffers + data locally and syncs when a connection is available, decoupling data collection + from transmission. +

+

Signals

+
+ PPG + EDA + IMU +
+
+
+ +
+
+ Pilot 2 +

LLUI - Online cognitive load estimation

+
+
+

Context

+

+ Research study at ETH Zurich's LLUI (Lower-Limb User Interaction) lab. + Participants wear sensors during human-robot interaction tasks. + The goal is real-time cognitive load estimation to adapt assistive + robot behavior based on the user's physiological state. +

+

Data flow

+
    +
  1. A wearable device streams PPG, ECG, EDA, and IMU signals continuously.
  2. +
  3. Raw data is immediately formatted as MEDWEAR messages at the source.
  4. +
  5. ROS 2 middleware transports the data in real time using healthcare_msgs + message definitions.
  6. +
  7. The MED-API ROS 2 bridge ingests the stream into the platform with low latency.
  8. +
  9. The MEDWEAR platform serves the data for live dashboards, role-based access, and + downstream ML pipelines.
  10. +
+

Why online?

+

+ Cognitive load estimation requires sub-second latency. An online pipeline eliminates + buffering delays and enables closed-loop feedback between the wearable signals and + the robot controller. +

+

Signals

+
+ PPG + ECG + EDA + IMU +
+
+
+ +
+ +
+

What the Pilots Validate

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertySPZ (Offline)LLUI (Online)
Connectivity modelAsynchronous batch uploadSynchronous real-time stream
Transport layerSCAI smartphone appROS 2 + MED-API bridge
Schema formatJSON Schema (post-hoc conversion)ROS 2 healthcare_msgs (native)
Primary signal typesPPG, EDA, IMUPPG, ECG, EDA, IMU
Primary useClinical data collection & exportReal-time cognitive load estimation
Downstream consumerResearcher / clinician dashboardClosed-loop robot controller + dashboard
+
+ +
+
+ + + + + \ No newline at end of file